Browse Source

Implement CollapsingTopBar

Guy Carmeli 7 years ago
parent
commit
37b96449ec
35 changed files with 1113 additions and 164 deletions
  1. 4
    0
      android/app/src/main/java/com/reactnativenavigation/params/BaseScreenParams.java
  2. 6
    0
      android/app/src/main/java/com/reactnativenavigation/params/CollapsingTopBarParams.java
  3. 0
    2
      android/app/src/main/java/com/reactnativenavigation/params/ScreenParams.java
  4. 1
    0
      android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java
  5. 38
    0
      android/app/src/main/java/com/reactnativenavigation/params/parsers/CollapsingTopBarParser.java
  6. 1
    0
      android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java
  7. 60
    0
      android/app/src/main/java/com/reactnativenavigation/screens/CollapsingSingleScreen.java
  8. 11
    3
      android/app/src/main/java/com/reactnativenavigation/screens/Screen.java
  9. 2
    0
      android/app/src/main/java/com/reactnativenavigation/screens/ScreenFactory.java
  10. 6
    3
      android/app/src/main/java/com/reactnativenavigation/screens/SingleScreen.java
  11. 10
    1
      android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java
  12. 62
    8
      android/app/src/main/java/com/reactnativenavigation/views/ContentView.java
  13. 61
    0
      android/app/src/main/java/com/reactnativenavigation/views/Scrim.java
  14. 2
    24
      android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java
  15. 15
    9
      android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  16. 22
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapseAmount.java
  17. 168
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapseCalculator.java
  18. 15
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingContentViewMeasurer.java
  19. 119
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTextView.java
  20. 52
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTitleBar.java
  21. 68
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingToolBar.java
  22. 69
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTopBar.java
  23. 11
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingView.java
  24. 7
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/OnScrollViewAddedListener.java
  25. 50
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollDirection.java
  26. 37
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollListener.java
  27. 39
    0
      android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollViewDelegate.java
  28. 14
    0
      android/app/src/main/java/com/reactnativenavigation/views/utils/ViewMeasurer.java
  29. BIN
      example/img/gyro_header.jpg
  30. 12
    6
      example/src/app.js
  31. 133
    0
      example/src/screens/CollapsingTopBarScreen.android.js
  32. 0
    101
      example/src/screens/ThirdTabScreen.js
  33. 2
    2
      example/src/screens/index.android.js
  34. 0
    2
      example/src/screens/index.ios.js
  35. 16
    3
      src/deprecated/platformSpecificDeprecated.android.js

+ 4
- 0
android/app/src/main/java/com/reactnativenavigation/params/BaseScreenParams.java View File

@@ -33,4 +33,8 @@ public class BaseScreenParams {
33 33
     public String getNavigatorEventId() {
34 34
         return navigationParams.navigatorEventId;
35 35
     }
36
+
37
+    public boolean hasCollapsingTopBar() {
38
+        return styleParams.collapsingTopBarParams != null;
39
+    }
36 40
 }

+ 6
- 0
android/app/src/main/java/com/reactnativenavigation/params/CollapsingTopBarParams.java View File

@@ -0,0 +1,6 @@
1
+package com.reactnativenavigation.params;
2
+
3
+public class CollapsingTopBarParams {
4
+    public String imageUri;
5
+    public StyleParams.Color scrimColor;
6
+}

+ 0
- 2
android/app/src/main/java/com/reactnativenavigation/params/ScreenParams.java View File

@@ -5,7 +5,6 @@ import android.graphics.drawable.Drawable;
5 5
 import java.util.List;
6 6
 
7 7
 public class ScreenParams extends BaseScreenParams {
8
-
9 8
     public String tabLabel;
10 9
     public Drawable tabIcon;
11 10
     public List<PageParams> topTabParams;
@@ -13,5 +12,4 @@ public class ScreenParams extends BaseScreenParams {
13 12
     public boolean hasTopTabs() {
14 13
         return topTabParams != null && !topTabParams.isEmpty();
15 14
     }
16
-
17 15
 }

+ 1
- 0
android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java View File

@@ -41,6 +41,7 @@ public class StyleParams {
41 41
     public Color contextualMenuBackgroundColor;
42 42
 
43 43
     public Color topBarColor;
44
+    public CollapsingTopBarParams collapsingTopBarParams;
44 45
     public boolean topBarHidden;
45 46
     public boolean topTabsHidden;
46 47
     public boolean drawScreenBelowTopBar;

+ 38
- 0
android/app/src/main/java/com/reactnativenavigation/params/parsers/CollapsingTopBarParser.java View File

@@ -0,0 +1,38 @@
1
+package com.reactnativenavigation.params.parsers;
2
+
3
+import android.graphics.Color;
4
+import android.os.Bundle;
5
+
6
+import com.reactnativenavigation.params.CollapsingTopBarParams;
7
+import com.reactnativenavigation.params.StyleParams;
8
+
9
+public class CollapsingTopBarParser extends Parser {
10
+    private Bundle params;
11
+
12
+    public CollapsingTopBarParser(Bundle params) {
13
+        this.params = params;
14
+    }
15
+
16
+    public CollapsingTopBarParams parse() {
17
+        if (!hasLocalImageResource() && !hasImageUrl()) {
18
+            return null;
19
+        }
20
+
21
+        CollapsingTopBarParams result = new CollapsingTopBarParams();
22
+        if (hasLocalImageResource()) {
23
+            result.imageUri = params.getString("collapsingToolBarImage");
24
+        } else {
25
+            result.imageUri = params.getString("collapsingToolBarImage");
26
+        }
27
+        result.scrimColor = getColor(params, "collapsingToolBarCollapsedColor", new StyleParams.Color(Color.WHITE));
28
+        return result;
29
+    }
30
+
31
+    private boolean hasLocalImageResource() {
32
+        return params.containsKey("collapsingToolBarImage");
33
+    }
34
+
35
+    private boolean hasImageUrl() {
36
+        return params.containsKey("collapsingToolBarImageUrl");
37
+    }
38
+}

+ 1
- 0
android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java View File

@@ -25,6 +25,7 @@ public class StyleParamsParser {
25 25
         result.contextualMenuBackgroundColor = getColor("contextualMenuBackgroundColor", getDefaultContextualMenuBackgroundColor());
26 26
 
27 27
         result.topBarColor = getColor("topBarColor", getDefaultTopBarColor());
28
+        result.collapsingTopBarParams = new CollapsingTopBarParser(params).parse();
28 29
         result.titleBarHidden = getBoolean("titleBarHidden", getDefaultTopBarHidden());
29 30
         result.topBarTransparent = getBoolean("topBarTransparent", getDefaultTopBarHidden());
30 31
         result.titleBarTitleColor = getColor("titleBarTitleColor", getDefaultTitleBarColor());

+ 60
- 0
android/app/src/main/java/com/reactnativenavigation/screens/CollapsingSingleScreen.java View File

@@ -0,0 +1,60 @@
1
+package com.reactnativenavigation.screens;
2
+
3
+import android.support.v7.app.AppCompatActivity;
4
+import android.widget.ScrollView;
5
+
6
+import com.reactnativenavigation.params.ScreenParams;
7
+import com.reactnativenavigation.views.ContentView;
8
+import com.reactnativenavigation.views.LeftButtonOnClickListener;
9
+import com.reactnativenavigation.views.collapsingToolbar.CollapsingContentViewMeasurer;
10
+import com.reactnativenavigation.views.collapsingToolbar.CollapsingTopBar;
11
+import com.reactnativenavigation.views.collapsingToolbar.OnScrollViewAddedListener;
12
+import com.reactnativenavigation.views.collapsingToolbar.ScrollListener;
13
+
14
+public class CollapsingSingleScreen extends SingleScreen {
15
+
16
+    public CollapsingSingleScreen(AppCompatActivity activity, ScreenParams screenParams, LeftButtonOnClickListener titleBarBarBackButtonListener) {
17
+        super(activity, screenParams, titleBarBarBackButtonListener);
18
+    }
19
+
20
+    @Override
21
+    protected void createTopBar() {
22
+        final CollapsingTopBar topBar = new CollapsingTopBar(getContext(), styleParams.collapsingTopBarParams);
23
+        topBar.setScrollListener(new ScrollListener(topBar,
24
+                new ScrollListener.OnScrollListener() {
25
+                    @Override
26
+                    public void onScroll(float amount) {
27
+                        contentView.collapse(amount);
28
+                        topBar.collapse(amount);
29
+                    }
30
+                }
31
+        ));
32
+        this.topBar = topBar;
33
+    }
34
+
35
+    @Override
36
+    protected void createContent() {
37
+        contentView = new ContentView(getContext(), screenParams.screenId, screenParams.navigationParams);
38
+        contentView.setViewMeasurer(new CollapsingContentViewMeasurer((CollapsingTopBar) topBar));
39
+        setupScrollDetection((CollapsingTopBar) topBar);
40
+        addView(contentView, createLayoutParams());
41
+    }
42
+
43
+    private void setupScrollDetection(final CollapsingTopBar topBar) {
44
+        contentView.setupScrollDetection(new ScrollListener(topBar,
45
+                new ScrollListener.OnScrollListener() {
46
+                    @Override
47
+                    public void onScroll(float amount) {
48
+                        contentView.collapse(amount);
49
+                        topBar.collapse(amount);
50
+                    }
51
+                }
52
+        ));
53
+        contentView.setOnScrollViewAddedListener(new OnScrollViewAddedListener() {
54
+            @Override
55
+            public void onScrollViewAdded(ScrollView scrollView) {
56
+                topBar.onScrollViewAdded(scrollView);
57
+            }
58
+        });
59
+    }
60
+}

+ 11
- 3
android/app/src/main/java/com/reactnativenavigation/screens/Screen.java View File

@@ -42,7 +42,7 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
42 42
     private final LeftButtonOnClickListener leftButtonOnClickListener;
43 43
     private VisibilityAnimator topBarVisibilityAnimator;
44 44
     private ScreenAnimator screenAnimator;
45
-    private final StyleParams styleParams;
45
+    protected final StyleParams styleParams;
46 46
 
47 47
     public Screen(AppCompatActivity activity, ScreenParams screenParams, LeftButtonOnClickListener leftButtonOnClickListener) {
48 48
         super(activity);
@@ -74,7 +74,7 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
74 74
     }
75 75
 
76 76
     private void createViews() {
77
-        createTopBar();
77
+        createAndAddTopBar();
78 78
         createTitleBar();
79 79
         createContent();
80 80
     }
@@ -98,8 +98,16 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
98 98
                 screenParams.overrideBackPressInJs);
99 99
     }
100 100
 
101
-    private void createTopBar() {
101
+    private void createAndAddTopBar() {
102
+        createTopBar();
103
+        addTopBar();
104
+    }
105
+
106
+    protected void createTopBar() {
102 107
         topBar = new TopBar(getContext());
108
+    }
109
+
110
+    private void addTopBar() {
103 111
         createTopBarVisibilityAnimator();
104 112
         addView(topBar, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
105 113
     }

+ 2
- 0
android/app/src/main/java/com/reactnativenavigation/screens/ScreenFactory.java View File

@@ -13,6 +13,8 @@ public class ScreenFactory {
13 13
             return new FragmentScreen(activity, screenParams, leftButtonOnClickListener);
14 14
         } else if (screenParams.hasTopTabs()) {
15 15
             return new ViewPagerScreen(activity, screenParams, leftButtonOnClickListener);
16
+        } else if (screenParams.hasCollapsingTopBar()) {
17
+            return new CollapsingSingleScreen(activity, screenParams, leftButtonOnClickListener);
16 18
         } else {
17 19
             return new SingleScreen(activity, screenParams, leftButtonOnClickListener);
18 20
         }

+ 6
- 3
android/app/src/main/java/com/reactnativenavigation/screens/SingleScreen.java View File

@@ -9,8 +9,7 @@ import com.reactnativenavigation.views.LeftButtonOnClickListener;
9 9
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
10 10
 
11 11
 public class SingleScreen extends Screen {
12
-
13
-    private ContentView contentView;
12
+    protected ContentView contentView;
14 13
 
15 14
     public SingleScreen(AppCompatActivity activity, ScreenParams screenParams,
16 15
                         LeftButtonOnClickListener titleBarBarBackButtonListener) {
@@ -20,11 +19,15 @@ public class SingleScreen extends Screen {
20 19
     @Override
21 20
     protected void createContent() {
22 21
         contentView = new ContentView(getContext(), screenParams.screenId, screenParams.navigationParams);
22
+        addView(contentView, createLayoutParams());
23
+    }
24
+
25
+    protected LayoutParams createLayoutParams() {
23 26
         LayoutParams params = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
24 27
         if (screenParams.styleParams.drawScreenBelowTopBar) {
25 28
             params.addRule(BELOW, topBar.getId());
26 29
         }
27
-        addView(contentView, 0, params);
30
+        return params;
28 31
     }
29 32
 
30 33
     @Override

+ 10
- 1
android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java View File

@@ -43,6 +43,16 @@ public class ViewUtils {
43 43
         return dp * scale + 0.5f;
44 44
     }
45 45
 
46
+    public static float convertPixelToSp(float pixels) {
47
+        float scaledDensity = NavigationApplication.instance.getResources().getDisplayMetrics().scaledDensity;
48
+        return pixels/scaledDensity;
49
+    }
50
+
51
+    public static float convertSpToPixel(float pixels) {
52
+        float scaledDensity = NavigationApplication.instance.getResources().getDisplayMetrics().scaledDensity;
53
+        return pixels * scaledDensity;
54
+    }
55
+
46 56
     public static int generateViewId() {
47 57
         if (Build.VERSION.SDK_INT >= 17) {
48 58
             return View.generateViewId();
@@ -69,6 +79,5 @@ public class ViewUtils {
69 79
             }
70 80
         }
71 81
     }
72
-
73 82
 }
74 83
 

+ 62
- 8
android/app/src/main/java/com/reactnativenavigation/views/ContentView.java View File

@@ -1,27 +1,58 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3 3
 import android.content.Context;
4
+import android.support.annotation.Nullable;
5
+import android.view.MotionEvent;
4 6
 import android.view.View;
7
+import android.widget.ScrollView;
5 8
 
6 9
 import com.facebook.react.ReactRootView;
7 10
 import com.reactnativenavigation.NavigationApplication;
8 11
 import com.reactnativenavigation.params.NavigationParams;
9 12
 import com.reactnativenavigation.screens.SingleScreen;
10 13
 import com.reactnativenavigation.utils.ViewUtils;
14
+import com.reactnativenavigation.views.collapsingToolbar.OnScrollViewAddedListener;
15
+import com.reactnativenavigation.views.collapsingToolbar.ScrollListener;
16
+import com.reactnativenavigation.views.collapsingToolbar.ScrollViewDelegate;
17
+import com.reactnativenavigation.views.utils.ViewMeasurer;
11 18
 
12 19
 public class ContentView extends ReactRootView {
13
-
14 20
     private final String screenId;
15 21
     private final NavigationParams navigationParams;
16 22
 
17 23
     boolean isContentVisible = false;
18 24
     private SingleScreen.OnDisplayListener onDisplayListener;
25
+    @Nullable private ScrollViewDelegate scrollViewDelegate;
26
+    private ViewMeasurer viewMeasurer;
27
+    private OnScrollViewAddedListener scrollViewAddedListener;
28
+
29
+    public void setOnDisplayListener(SingleScreen.OnDisplayListener onDisplayListener) {
30
+        this.onDisplayListener = onDisplayListener;
31
+    }
32
+
33
+    public void setOnScrollViewAddedListener(OnScrollViewAddedListener scrollViewAddedListener) {
34
+        this.scrollViewAddedListener = scrollViewAddedListener;
35
+    }
19 36
 
20 37
     public ContentView(Context context, String screenId, NavigationParams navigationParams) {
21 38
         super(context);
22 39
         this.screenId = screenId;
23 40
         this.navigationParams = navigationParams;
24 41
         attachToJS();
42
+        viewMeasurer = new ViewMeasurer();
43
+    }
44
+
45
+    public void setupScrollDetection(ScrollListener scrollListener) {
46
+        scrollViewDelegate = new ScrollViewDelegate(scrollListener);
47
+    }
48
+
49
+    public void setViewMeasurer(ViewMeasurer viewMeasurer) {
50
+        this.viewMeasurer = viewMeasurer;
51
+    }
52
+
53
+    private void attachToJS() {
54
+        startReactApplication(NavigationApplication.instance.getReactGateway().getReactInstanceManager(), screenId,
55
+                navigationParams.toBundle());
25 56
     }
26 57
 
27 58
     public String getNavigatorEventId() {
@@ -32,10 +63,38 @@ public class ContentView extends ReactRootView {
32 63
         unmountReactApplication();
33 64
     }
34 65
 
66
+    @Override
67
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
68
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
69
+        setMeasuredDimension(viewMeasurer.getMeasuredWidth(widthMeasureSpec),
70
+                viewMeasurer.getMeasuredHeight(heightMeasureSpec));
71
+    }
72
+
73
+    @Override
74
+    public boolean dispatchTouchEvent(MotionEvent ev) {
75
+        if (scrollViewDelegate != null) {
76
+            boolean consumed = scrollViewDelegate.didInterceptTouchEvent(ev);
77
+            if (consumed) {
78
+                return true;
79
+            }
80
+        }
81
+        return super.dispatchTouchEvent(ev);
82
+    }
83
+
35 84
     @Override
36 85
     public void onViewAdded(final View child) {
37 86
         super.onViewAdded(child);
38 87
         detectContentViewVisible(child);
88
+        if (child instanceof ScrollView) {
89
+            onScrollViewAdded((ScrollView) child);
90
+        }
91
+    }
92
+
93
+    private void onScrollViewAdded(ScrollView child) {
94
+        if (scrollViewDelegate != null) {
95
+            scrollViewDelegate.onScrollViewAdded(child);
96
+            scrollViewAddedListener.onScrollViewAdded(child);
97
+        }
39 98
     }
40 99
 
41 100
     private void detectContentViewVisible(View child) {
@@ -53,12 +112,7 @@ public class ContentView extends ReactRootView {
53 112
         }
54 113
     }
55 114
 
56
-    private void attachToJS() {
57
-        startReactApplication(NavigationApplication.instance.getReactGateway().getReactInstanceManager(), screenId,
58
-                navigationParams.toBundle());
59
-    }
60
-
61
-    public void setOnDisplayListener(SingleScreen.OnDisplayListener onDisplayListener) {
62
-        this.onDisplayListener = onDisplayListener;
115
+    public void collapse(float collapse) {
116
+        setTranslationY(collapse);
63 117
     }
64 118
 }

+ 61
- 0
android/app/src/main/java/com/reactnativenavigation/views/Scrim.java View File

@@ -0,0 +1,61 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+import android.view.View;
5
+import android.view.animation.DecelerateInterpolator;
6
+import android.view.animation.Interpolator;
7
+
8
+import com.reactnativenavigation.params.StyleParams;
9
+
10
+import static com.reactnativenavigation.views.Scrim.State.Invisible;
11
+import static com.reactnativenavigation.views.Scrim.State.Visible;
12
+
13
+public class Scrim extends View {
14
+    enum State {Visible, Invisible}
15
+
16
+    private State state = Invisible;
17
+    private final float collapseThreshold;
18
+    private final static int ANIMATION_DURATION = 600;
19
+    private final Interpolator interpolator;
20
+
21
+    public Scrim(Context context, StyleParams.Color color, float collapseThreshold) {
22
+        super(context);
23
+        this.collapseThreshold = collapseThreshold;
24
+        setBackgroundColor(color.getColor());
25
+        setAlpha(0);
26
+        interpolator = new DecelerateInterpolator();
27
+    }
28
+
29
+    public void collapse(float collapse) {
30
+        if (shouldShowScrim(collapse)) {
31
+            showScrim();
32
+        } else if (shouldHideScrim(collapse)) {
33
+            hideScrim();
34
+        }
35
+
36
+    }
37
+
38
+    private boolean shouldShowScrim(float collapse) {
39
+        return Math.abs(collapse) >= collapseThreshold && state == Invisible;
40
+    }
41
+
42
+    private boolean shouldHideScrim(float collapse) {
43
+        return Math.abs(collapse) < collapseThreshold && state == Visible;
44
+    }
45
+
46
+    private void showScrim() {
47
+        state = Visible;
48
+        animate()
49
+                .alpha(1)
50
+                .setDuration(ANIMATION_DURATION)
51
+                .setInterpolator(interpolator);
52
+    }
53
+
54
+    private void hideScrim() {
55
+        state = Invisible;
56
+        animate()
57
+                .alpha(0)
58
+                .setDuration(ANIMATION_DURATION)
59
+                .setInterpolator(interpolator);
60
+    }
61
+}

+ 2
- 24
android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java View File

@@ -12,7 +12,6 @@ import android.view.View;
12 12
 import android.view.animation.AccelerateDecelerateInterpolator;
13 13
 import android.view.animation.AccelerateInterpolator;
14 14
 
15
-import com.reactnativenavigation.animation.VisibilityAnimator;
16 15
 import com.reactnativenavigation.params.BaseTitleBarButtonParams;
17 16
 import com.reactnativenavigation.params.StyleParams;
18 17
 import com.reactnativenavigation.params.TitleBarButtonParams;
@@ -23,8 +22,6 @@ import java.util.List;
23 22
 
24 23
 public class TitleBar extends Toolbar {
25 24
 
26
-    private boolean hideOnScroll = false;
27
-    private VisibilityAnimator visibilityAnimator;
28 25
     private LeftButton leftButton;
29 26
     private ActionMenuView actionMenuView;
30 27
 
@@ -43,11 +40,9 @@ public class TitleBar extends Toolbar {
43 40
     public void setRightButtons(List<TitleBarButtonParams> rightButtons, String navigatorEventId) {
44 41
         Menu menu = getMenu();
45 42
         menu.clear();
46
-
47 43
         if (rightButtons == null) {
48 44
             return;
49 45
         }
50
-
51 46
         addButtonsToTitleBar(rightButtons, navigatorEventId, menu);
52 47
     }
53 48
 
@@ -80,13 +75,13 @@ public class TitleBar extends Toolbar {
80 75
         return overflowIcon != null && params.titleBarButtonColor.hasColor();
81 76
     }
82 77
 
83
-    private void setTitleTextColor(StyleParams params) {
78
+    protected void setTitleTextColor(StyleParams params) {
84 79
         if (params.titleBarTitleColor.hasColor()) {
85 80
             setTitleTextColor(params.titleBarTitleColor.getColor());
86 81
         }
87 82
     }
88 83
 
89
-    private void setSubtitleTextColor(StyleParams params) {
84
+    protected void setSubtitleTextColor(StyleParams params) {
90 85
         if (params.titleBarSubtitleColor.hasColor()) {
91 86
             setSubtitleTextColor(params.titleBarSubtitleColor.getColor());
92 87
         }
@@ -126,23 +121,6 @@ public class TitleBar extends Toolbar {
126 121
         setNavigationIcon(leftButton);
127 122
     }
128 123
 
129
-    public void setHideOnScroll(boolean hideOnScroll) {
130
-        this.hideOnScroll = hideOnScroll;
131
-    }
132
-
133
-    public void onScrollChanged(ScrollDirectionListener.Direction direction) {
134
-        if (hideOnScroll) {
135
-            if (visibilityAnimator == null) {
136
-                createScrollAnimator();
137
-            }
138
-            visibilityAnimator.onScrollChanged(direction);
139
-        }
140
-    }
141
-
142
-    private void createScrollAnimator() {
143
-        visibilityAnimator = new VisibilityAnimator(this, VisibilityAnimator.HideDirection.Up, getHeight());
144
-    }
145
-
146 124
     public void hide() {
147 125
         hide(null);
148 126
     }

+ 15
- 9
android/app/src/main/java/com/reactnativenavigation/views/TopBar.java View File

@@ -6,7 +6,7 @@ import android.os.Build;
6 6
 import android.support.design.widget.AppBarLayout;
7 7
 import android.support.design.widget.TabLayout;
8 8
 import android.view.ViewGroup;
9
-import android.widget.RelativeLayout;
9
+import android.widget.FrameLayout;
10 10
 
11 11
 import com.facebook.react.bridge.Callback;
12 12
 import com.reactnativenavigation.params.ContextualMenuParams;
@@ -21,21 +21,19 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
21 21
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
22 22
 
23 23
 public class TopBar extends AppBarLayout {
24
-
25
-    private TitleBar titleBar;
24
+    protected TitleBar titleBar;
26 25
     private ContextualMenu contextualMenu;
27
-    private RelativeLayout titleBarAndContextualMenuContainer;
26
+    protected FrameLayout titleBarAndContextualMenuContainer;
28 27
     private TopTabs topTabs;
29 28
 
30 29
     public TopBar(Context context) {
31 30
         super(context);
32
-        setFitsSystemWindows(true);
33 31
         setId(ViewUtils.generateViewId());
34 32
         createLayout();
35 33
     }
36 34
 
37
-    private void createLayout() {
38
-        titleBarAndContextualMenuContainer = new RelativeLayout(getContext());
35
+    protected void createLayout() {
36
+        titleBarAndContextualMenuContainer = new FrameLayout(getContext());
39 37
         addView(titleBarAndContextualMenuContainer);
40 38
     }
41 39
 
@@ -43,8 +41,16 @@ public class TopBar extends AppBarLayout {
43 41
                                          TitleBarLeftButtonParams leftButton,
44 42
                                          LeftButtonOnClickListener leftButtonOnClickListener,
45 43
                                          String navigatorEventId, boolean overrideBackPressInJs) {
46
-        titleBar = new TitleBar(getContext());
47
-        titleBarAndContextualMenuContainer.addView(titleBar, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
44
+        titleBar = createTitleBar();
45
+        titleBarAndContextualMenuContainer.addView(titleBar, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
46
+        addButtons(rightButtons, leftButton, leftButtonOnClickListener, navigatorEventId, overrideBackPressInJs);
47
+    }
48
+
49
+    protected TitleBar createTitleBar() {
50
+        return new TitleBar(getContext());
51
+    }
52
+
53
+    private void addButtons(List<TitleBarButtonParams> rightButtons, TitleBarLeftButtonParams leftButton, LeftButtonOnClickListener leftButtonOnClickListener, String navigatorEventId, boolean overrideBackPressInJs) {
48 54
         titleBar.setRightButtons(rightButtons, navigatorEventId);
49 55
         titleBar.setLeftButton(leftButton, leftButtonOnClickListener, navigatorEventId, overrideBackPressInJs);
50 56
     }

+ 22
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapseAmount.java View File

@@ -0,0 +1,22 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+public class CollapseAmount {
4
+    public static CollapseAmount NONE = new CollapseAmount();
5
+    private Float amount;
6
+
7
+    public CollapseAmount(float amount) {
8
+        this.amount = amount;
9
+    }
10
+
11
+    private CollapseAmount() {
12
+
13
+    }
14
+
15
+    public boolean canCollapse() {
16
+        return amount != null;
17
+    }
18
+
19
+    public float get() {
20
+        return amount;
21
+    }
22
+}

+ 168
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapseCalculator.java View File

@@ -0,0 +1,168 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.support.annotation.NonNull;
4
+import android.support.annotation.Nullable;
5
+import android.view.MotionEvent;
6
+import android.widget.ScrollView;
7
+
8
+import com.reactnativenavigation.utils.ViewUtils;
9
+
10
+class CollapseCalculator {
11
+    private float collapse;
12
+    private MotionEvent previousTouchEvent;
13
+    private float touchDownY = -1;
14
+    private float previousCollapseY = -1;
15
+    private boolean isExpended;
16
+    private boolean isCollapsed = true;
17
+    private boolean canCollapse = true;
18
+    private boolean canExpend = false;
19
+    private CollapsingView view;
20
+    protected ScrollView scrollView;
21
+    private static float finalCollapsedTranslation;
22
+
23
+    CollapseCalculator(final CollapsingView collapsingView) {
24
+        this.view = collapsingView;
25
+        ViewUtils.runOnPreDraw(view.asView(), new Runnable() {
26
+            @Override
27
+            public void run() {
28
+                finalCollapsedTranslation = view.getFinalCollapseValue();
29
+            }
30
+        });
31
+    }
32
+
33
+    void setScrollView(ScrollView scrollView) {
34
+        this.scrollView = scrollView;
35
+    }
36
+
37
+    @NonNull
38
+    CollapseAmount calculate(MotionEvent event) {
39
+        updateInitialTouchY(event);
40
+        if (!isMoveEvent(event)) {
41
+            return CollapseAmount.NONE;
42
+        }
43
+
44
+        if (shouldTranslateTopBarAndScrollView(event)) {
45
+            return calculateCollapse(event);
46
+        } else {
47
+            previousCollapseY = -1;
48
+            previousTouchEvent = MotionEvent.obtain(event);
49
+            return CollapseAmount.NONE;
50
+        }
51
+    }
52
+
53
+    private boolean shouldTranslateTopBarAndScrollView(MotionEvent event) {
54
+        checkCollapseLimits();
55
+        ScrollDirection.Direction direction = getScrollDirection(event.getRawY());
56
+        return isMoveEvent(event) &&
57
+               (isNotCollapsedOrExpended() ||
58
+                (canCollapse && isExpendedAndScrollingUp(direction)) ||
59
+                (canExpend && isCollapsedAndScrollingDown(direction)));
60
+    }
61
+
62
+    private ScrollDirection.Direction getScrollDirection(float y) {
63
+        if (y == (previousCollapseY == -1 ? touchDownY : previousCollapseY)) {
64
+            return ScrollDirection.Direction.None;
65
+        }
66
+        if (previousTouchEvent == null) {
67
+            return ScrollDirection.Direction.None;
68
+        }
69
+        return y < previousTouchEvent.getRawY() ?
70
+                ScrollDirection.Direction.Up :
71
+                ScrollDirection.Direction.Down;
72
+    }
73
+
74
+    private void checkCollapseLimits() {
75
+        float currentTopBarTranslation = view.getCurrentCollapseValue();
76
+        float finalExpendedTranslation = 0;
77
+        isExpended = isExpended(currentTopBarTranslation, finalExpendedTranslation);
78
+        isCollapsed = isCollapsed(currentTopBarTranslation, finalCollapsedTranslation);
79
+        canCollapse = calculateCanCollapse(currentTopBarTranslation, finalExpendedTranslation, finalCollapsedTranslation);
80
+        canExpend = calculateCanExpend(currentTopBarTranslation, finalExpendedTranslation, finalCollapsedTranslation);
81
+    }
82
+
83
+    private boolean calculateCanCollapse(float currentTopBarTranslation, float finalExpendedTranslation, float finalCollapsedTranslation) {
84
+        return currentTopBarTranslation > finalCollapsedTranslation &&
85
+               currentTopBarTranslation <= finalExpendedTranslation;
86
+    }
87
+
88
+    private boolean calculateCanExpend(float currentTopBarTranslation, float finalExpendedTranslation, float finalCollapsedTranslation) {
89
+        return currentTopBarTranslation >= finalCollapsedTranslation &&
90
+               currentTopBarTranslation < finalExpendedTranslation &&
91
+               scrollView.getScrollY() == 0;
92
+    }
93
+
94
+    private boolean isCollapsedAndScrollingDown(ScrollDirection.Direction direction) {
95
+        return isCollapsed && direction == ScrollDirection.Direction.Down;
96
+    }
97
+
98
+    private boolean isExpendedAndScrollingUp(ScrollDirection.Direction direction) {
99
+        return isExpended && direction == ScrollDirection.Direction.Up;
100
+    }
101
+
102
+    private  boolean isNotCollapsedOrExpended() {
103
+        return canExpend && canCollapse;
104
+    }
105
+
106
+    private boolean isCollapsed(float currentTopBarTranslation, float finalCollapsedTranslation) {
107
+        return currentTopBarTranslation == finalCollapsedTranslation;
108
+    }
109
+
110
+    private boolean isExpended(float currentTopBarTranslation, float finalExpendedTranslation) {
111
+        return currentTopBarTranslation == finalExpendedTranslation;
112
+    }
113
+
114
+    private CollapseAmount calculateCollapse(MotionEvent event) {
115
+        float y = event.getRawY();
116
+        if (previousCollapseY == -1) {
117
+            previousCollapseY = y;
118
+        }
119
+        collapse = calculateCollapse(y);
120
+        previousCollapseY = y;
121
+        previousTouchEvent = MotionEvent.obtain(event);
122
+        return new CollapseAmount(collapse);
123
+    }
124
+
125
+    private float calculateCollapse(float y) {
126
+        float translation = y - previousCollapseY + view.getCurrentCollapseValue();
127
+        if (translation < finalCollapsedTranslation) {
128
+            translation = finalCollapsedTranslation;
129
+        }
130
+        final float expendedTranslation = 0;
131
+        if (translation > expendedTranslation) {
132
+            translation = expendedTranslation;
133
+        }
134
+        return translation;
135
+    }
136
+
137
+
138
+    private void updateInitialTouchY(MotionEvent event) {
139
+        if (isTouchDown(previousTouchEvent) && isMoveEvent(event)) {
140
+            saveInitialTouchY(previousTouchEvent);
141
+        } else if (isTouchUp(event) && isMoveEvent(previousTouchEvent)) {
142
+            clearInitialTouchY();
143
+        }
144
+    }
145
+
146
+    private boolean isMoveEvent(@Nullable MotionEvent event) {
147
+        return event != null && event.getActionMasked() == MotionEvent.ACTION_MOVE;
148
+    }
149
+
150
+    private boolean isTouchDown(@Nullable MotionEvent event) {
151
+        return event != null && event.getActionMasked() == MotionEvent.ACTION_DOWN;
152
+    }
153
+
154
+    private boolean isTouchUp(@Nullable MotionEvent event) {
155
+        return event != null && event.getActionMasked() == MotionEvent.ACTION_UP;
156
+    }
157
+
158
+    private void saveInitialTouchY(MotionEvent event) {
159
+        touchDownY = event.getRawY();
160
+        previousCollapseY = touchDownY;
161
+    }
162
+
163
+    private void clearInitialTouchY() {
164
+        touchDownY = -1;
165
+        previousCollapseY = -1;
166
+        collapse = 0;
167
+    }
168
+}

+ 15
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingContentViewMeasurer.java View File

@@ -0,0 +1,15 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import com.reactnativenavigation.views.utils.ViewMeasurer;
4
+
5
+public class CollapsingContentViewMeasurer extends ViewMeasurer {
6
+    private final float titleBarHeight;
7
+    public CollapsingContentViewMeasurer(CollapsingTopBar topBar) {
8
+        titleBarHeight = topBar.getTitleBarHeight();
9
+    }
10
+
11
+    @Override
12
+    public int getMeasuredHeight(int heightMeasureSpec) {
13
+        return (int) (super.getMeasuredHeight(heightMeasureSpec) + CollapsingToolBar.MAX_HEIGHT - titleBarHeight);
14
+    }
15
+}

+ 119
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTextView.java View File

@@ -0,0 +1,119 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.content.Context;
5
+import android.graphics.Canvas;
6
+import android.graphics.Color;
7
+import android.graphics.Paint;
8
+import android.support.annotation.FloatRange;
9
+import android.support.v4.widget.TextViewCompat;
10
+import android.support.v7.widget.TintTypedArray;
11
+import android.view.animation.DecelerateInterpolator;
12
+import android.view.animation.Interpolator;
13
+import android.widget.FrameLayout;
14
+import android.widget.LinearLayout;
15
+import android.widget.TextView;
16
+
17
+import com.reactnativenavigation.params.StyleParams;
18
+import com.reactnativenavigation.utils.ViewUtils;
19
+
20
+public class CollapsingTextView extends FrameLayout {
21
+    private static final float TEXT_SCALE_FACTOR = 1.75f;
22
+    private static final float INTERPOLATOR_EASING_FACTOR = 0.5f;
23
+
24
+    private final int collapsedHeight;
25
+    private TextView dummy;
26
+    private String text;
27
+    private Paint paint;
28
+    private float initialY;
29
+    private float currentY;
30
+    private float collapseY;
31
+    private float collapseFraction;
32
+    private int[] positionOnScreen;
33
+    private Interpolator scaleInterpolator;
34
+    private float expendedTextSize;
35
+    private float collapsedTextSize;
36
+
37
+    public CollapsingTextView(Context context, int collapsedHeight) {
38
+        super(context);
39
+        this.collapsedHeight = collapsedHeight;
40
+        setWillNotDraw(false);
41
+        createDummyTextView(context);
42
+        createTextPaint();
43
+        scaleInterpolator = new DecelerateInterpolator(INTERPOLATOR_EASING_FACTOR);
44
+        setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
45
+    }
46
+
47
+    @SuppressLint("PrivateResource")
48
+    private void createDummyTextView(Context context) {
49
+        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), null,
50
+                android.support.v7.appcompat.R.styleable.Toolbar, android.support.v7.appcompat.R.attr.toolbarStyle, 0);
51
+        int titleTextAppearance =
52
+                a.getResourceId(android.support.v7.appcompat.R.styleable.Toolbar_titleTextAppearance, 0);
53
+        a.recycle();
54
+
55
+        dummy = new TextView(context);
56
+        TextViewCompat.setTextAppearance(dummy, titleTextAppearance);
57
+        collapsedTextSize = dummy.getTextSize();
58
+        expendedTextSize = collapsedTextSize * TEXT_SCALE_FACTOR;
59
+        dummy.setTextSize(ViewUtils.convertPixelToSp(expendedTextSize));
60
+        dummy.setVisibility(INVISIBLE);
61
+        addView(dummy, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
62
+    }
63
+
64
+    private void createTextPaint() {
65
+        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
66
+        paint.setColor(Color.GREEN);
67
+        paint.setTextSize(expendedTextSize);
68
+
69
+        ViewUtils.runOnPreDraw(dummy, new Runnable() {
70
+            @Override
71
+            public void run() {
72
+                positionOnScreen = new int[2];
73
+                getLocationInWindow(positionOnScreen);
74
+                currentY = initialY = positionOnScreen[1] + getMeasuredHeight() - dummy.getMeasuredHeight();
75
+                float bottomMargin = ViewUtils.convertDpToPixel(10);
76
+                collapseY = positionOnScreen[1] + bottomMargin;
77
+                invalidate();
78
+            }
79
+        });
80
+    }
81
+
82
+    public void setText(String text) {
83
+        this.text = text;
84
+        dummy.setText(text);
85
+    }
86
+
87
+    public void setTextColor(StyleParams params) {
88
+        if (params.titleBarTitleColor.hasColor()) {
89
+            paint.setColor(params.titleBarTitleColor.getColor());
90
+        }
91
+    }
92
+
93
+    public void collapseBy(float translation) {
94
+        collapseFraction = calculateFraction(translation);
95
+        currentY = linearInterpolation(initialY, collapseY, collapseFraction);
96
+        invalidate();
97
+    }
98
+
99
+    /*
100
+    * A value of {@code 0.0} indicates that the layout is fully expanded.
101
+    * A value of {@code 1.0} indicates that the layout is fully collapsed.
102
+    */
103
+    @FloatRange(from = 0.0, to = 1.0)
104
+    private float calculateFraction(float translation) {
105
+        return Math.abs(translation / (getMeasuredHeight() - collapsedHeight));
106
+    }
107
+
108
+    @Override
109
+    protected void onDraw(Canvas canvas) {
110
+        super.onDraw(canvas);
111
+        paint.setTextSize(linearInterpolation(expendedTextSize, collapsedTextSize, collapseFraction));
112
+        canvas.drawText(text, 0, currentY, paint);
113
+    }
114
+
115
+    private float linearInterpolation(float from, float to, float fraction) {
116
+        fraction = scaleInterpolator.getInterpolation(fraction);
117
+        return from + (fraction * (to - from));
118
+    }
119
+}

+ 52
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTitleBar.java View File

@@ -0,0 +1,52 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.content.Context;
4
+import android.view.MotionEvent;
5
+import android.view.View;
6
+
7
+import com.reactnativenavigation.params.StyleParams;
8
+import com.reactnativenavigation.views.TitleBar;
9
+
10
+public class CollapsingTitleBar extends TitleBar implements View.OnTouchListener {
11
+    private CollapsingTextView title;
12
+    private int collapsedHeight;
13
+    private final ScrollListener scrollListener;
14
+
15
+    public CollapsingTitleBar(Context context, int collapsedHeight, ScrollListener scrollListener) {
16
+        super(context);
17
+        this.collapsedHeight = collapsedHeight;
18
+        this.scrollListener = scrollListener;
19
+        addCollapsingTitle();
20
+        setOnTouchListener(this);
21
+    }
22
+
23
+    private void addCollapsingTitle() {
24
+        title = new CollapsingTextView(getContext(), collapsedHeight);
25
+        addView(title);
26
+    }
27
+
28
+    @Override
29
+    public void setTitle(CharSequence title) {
30
+        this.title.setText((String) title);
31
+    }
32
+
33
+    @Override
34
+    protected void setTitleTextColor(StyleParams params) {
35
+        title.setTextColor(params);
36
+    }
37
+
38
+    @Override
39
+    protected void setSubtitleTextColor(StyleParams params) {
40
+    }
41
+
42
+    public void collapse(float collapse) {
43
+        title.setTranslationY(0);
44
+        setTranslationY(-collapse);
45
+        title.collapseBy(collapse);
46
+    }
47
+
48
+    @Override
49
+    public boolean onTouch(View v, MotionEvent event) {
50
+        return scrollListener.onTouch(event);
51
+    }
52
+}

+ 68
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingToolBar.java View File

@@ -0,0 +1,68 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.content.Context;
4
+import android.content.res.TypedArray;
5
+import android.widget.FrameLayout;
6
+import android.widget.ImageView;
7
+
8
+import com.facebook.drawee.view.SimpleDraweeView;
9
+import com.reactnativenavigation.R;
10
+import com.reactnativenavigation.params.CollapsingTopBarParams;
11
+import com.reactnativenavigation.utils.ViewUtils;
12
+import com.reactnativenavigation.views.Scrim;
13
+
14
+import static android.widget.FrameLayout.LayoutParams.MATCH_PARENT;
15
+
16
+public class CollapsingToolBar extends FrameLayout {
17
+    public static final float MAX_HEIGHT = ViewUtils.convertDpToPixel(256);
18
+    private final CollapsingTopBarParams params;
19
+    private SimpleDraweeView backdrop;
20
+    private Scrim scrim;
21
+    private int topBarHeight = -1;
22
+
23
+    public CollapsingToolBar(Context context, CollapsingTopBarParams params) {
24
+        super(context);
25
+        this.params = params;
26
+        setFitsSystemWindows(true);
27
+        createBackDropImage();
28
+        createScrim();
29
+        setWillNotDraw(false);
30
+    }
31
+
32
+    private void createBackDropImage() {
33
+        backdrop = new SimpleDraweeView(getContext());
34
+        setImageSource();
35
+        backdrop.setScaleType(ImageView.ScaleType.CENTER_CROP);
36
+        backdrop.setFitsSystemWindows(true);
37
+        addView(backdrop, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
38
+    }
39
+
40
+    private void setImageSource() {
41
+        if (params.imageUri != null) {
42
+            backdrop.setImageURI(params.imageUri);
43
+        }
44
+    }
45
+
46
+    private void createScrim() {
47
+        scrim = new Scrim(getContext(), params.scrimColor, MAX_HEIGHT / 2);
48
+        addView(scrim);
49
+    }
50
+
51
+    public int getCollapsedTopBarHeight() {
52
+        if (topBarHeight > -1) {
53
+            return topBarHeight;
54
+        }
55
+        calculateTopBarHeight();
56
+        return topBarHeight;
57
+    }
58
+    private void calculateTopBarHeight() {
59
+        int[] attrs = new int[] {R.attr.actionBarSize};
60
+        TypedArray ta = getContext().obtainStyledAttributes(attrs);
61
+        topBarHeight = ta.getDimensionPixelSize(0, -1);
62
+        ta.recycle();
63
+    }
64
+
65
+    public void collapse(float collapse) {
66
+        scrim.collapse(collapse);
67
+    }
68
+}

+ 69
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingTopBar.java View File

@@ -0,0 +1,69 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.content.Context;
4
+import android.view.View;
5
+import android.widget.ScrollView;
6
+
7
+import com.reactnativenavigation.params.CollapsingTopBarParams;
8
+import com.reactnativenavigation.views.TitleBar;
9
+import com.reactnativenavigation.views.TopBar;
10
+
11
+public class CollapsingTopBar extends TopBar implements CollapsingView {
12
+    private CollapsingToolBar collapsingToolBar;
13
+    private ScrollListener scrollListener;
14
+
15
+    public CollapsingTopBar(Context context, CollapsingTopBarParams params) {
16
+        super(context);
17
+        createCollapsingTopBar(params);
18
+    }
19
+
20
+    public void setScrollListener(ScrollListener scrollListener) {
21
+        this.scrollListener = scrollListener;
22
+    }
23
+
24
+    private void createCollapsingTopBar(CollapsingTopBarParams params) {
25
+        collapsingToolBar = new CollapsingToolBar(getContext(), params);
26
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, (int) CollapsingToolBar.MAX_HEIGHT);
27
+        titleBarAndContextualMenuContainer.addView(collapsingToolBar, lp);
28
+    }
29
+
30
+    @Override
31
+    protected TitleBar createTitleBar() {
32
+        return new CollapsingTitleBar(getContext(),
33
+                collapsingToolBar.getCollapsedTopBarHeight(),
34
+                scrollListener);
35
+    }
36
+
37
+    public int getTitleBarHeight() {
38
+        return titleBar.getMeasuredHeight();
39
+    }
40
+
41
+    public CollapsingToolBar getCollapsingToolBar() {
42
+        return collapsingToolBar;
43
+    }
44
+
45
+    public void collapse(float collapse) {
46
+        setTranslationY(collapse);
47
+        ((CollapsingTitleBar) titleBar).collapse(collapse);
48
+        collapsingToolBar.collapse(collapse);
49
+    }
50
+
51
+    public void onScrollViewAdded(ScrollView scrollView) {
52
+        scrollListener.onScrollViewAdded(scrollView);
53
+    }
54
+
55
+    @Override
56
+    public float getFinalCollapseValue() {
57
+        return getCollapsingToolBar().getCollapsedTopBarHeight() - getHeight();
58
+    }
59
+
60
+    @Override
61
+    public float getCurrentCollapseValue() {
62
+        return getTranslationY();
63
+    }
64
+
65
+    @Override
66
+    public View asView() {
67
+        return this;
68
+    }
69
+}

+ 11
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingView.java View File

@@ -0,0 +1,11 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.view.View;
4
+
5
+public interface CollapsingView {
6
+    float getFinalCollapseValue();
7
+
8
+    float getCurrentCollapseValue();
9
+
10
+    View asView();
11
+}

+ 7
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/OnScrollViewAddedListener.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.widget.ScrollView;
4
+
5
+public interface OnScrollViewAddedListener {
6
+    void onScrollViewAdded(ScrollView scrollView);
7
+}

+ 50
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollDirection.java View File

@@ -0,0 +1,50 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.widget.ScrollView;
4
+
5
+public class ScrollDirection {
6
+
7
+    public enum Direction {
8
+        Up, Down, None
9
+    }
10
+
11
+    private final ScrollView scrollView;
12
+    private int lastScrollY = 0;
13
+
14
+    public ScrollDirection(ScrollView scrollView) {
15
+        this.scrollView = scrollView;
16
+    }
17
+
18
+    public Direction getScrollDirection() {
19
+        Direction direction = Direction.None;
20
+
21
+        final int scrollY = scrollView.getScrollY();
22
+        if (isScrollPositionChanged(scrollY) && !isTopOverscroll(scrollY) && !isBottomOverscroll(scrollY)) {
23
+            direction = getScrollDirection(scrollY);
24
+            lastScrollY = scrollY;
25
+        }
26
+        return direction;
27
+    }
28
+
29
+    public int getScrollDelta() {
30
+        return scrollView.getScrollY() - lastScrollY;
31
+    }
32
+
33
+
34
+    private Direction getScrollDirection(int scrollY) {
35
+        return scrollY > lastScrollY ? Direction.Up : Direction.Down;
36
+    }
37
+
38
+    private boolean isBottomOverscroll(int scrollY) {
39
+        return scrollY >= (scrollView.getChildAt(0).getHeight() - scrollView.getHeight());
40
+    }
41
+
42
+    private boolean isTopOverscroll(int scrollY) {
43
+        return scrollY <= 0;
44
+    }
45
+
46
+    private boolean isScrollPositionChanged(int scrollY) {
47
+        return scrollY != lastScrollY;
48
+    }
49
+
50
+}

+ 37
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollListener.java View File

@@ -0,0 +1,37 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.view.MotionEvent;
4
+import android.widget.ScrollView;
5
+
6
+public class ScrollListener implements ScrollViewDelegate.OnScrollListener {
7
+    private CollapseCalculator collapseCalculator;
8
+    private OnScrollListener scrollListener;
9
+
10
+    public interface  OnScrollListener {
11
+        void onScroll(float amount);
12
+    }
13
+
14
+    public ScrollListener(CollapsingView collapsingView, OnScrollListener scrollListener) {
15
+        this.collapseCalculator = new CollapseCalculator(collapsingView);
16
+        this.scrollListener = scrollListener;
17
+    }
18
+
19
+    @Override
20
+    public void onScrollViewAdded(ScrollView scrollView) {
21
+        collapseCalculator.setScrollView(scrollView);
22
+    }
23
+
24
+    @Override
25
+    public boolean onTouch(MotionEvent event) {
26
+        return handleTouch(event);
27
+    }
28
+
29
+    private boolean handleTouch(MotionEvent event) {
30
+        CollapseAmount amount = collapseCalculator.calculate(event);
31
+        if (amount.canCollapse()) {
32
+            scrollListener.onScroll(amount.get());
33
+            return true;
34
+        }
35
+        return false;
36
+    }
37
+}

+ 39
- 0
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/ScrollViewDelegate.java View File

@@ -0,0 +1,39 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import android.view.MotionEvent;
4
+import android.view.View;
5
+import android.widget.ScrollView;
6
+
7
+public class ScrollViewDelegate implements View.OnTouchListener {
8
+    interface OnScrollListener {
9
+        boolean onTouch(MotionEvent event);
10
+
11
+        void onScrollViewAdded(ScrollView scrollView);
12
+    }
13
+
14
+    private ScrollView scrollView;
15
+    private OnScrollListener listener;
16
+    private Boolean didInterceptLastTouchEvent = null;
17
+
18
+    public ScrollViewDelegate(OnScrollListener scrollListener) {
19
+        listener = scrollListener;
20
+    }
21
+
22
+    public void onScrollViewAdded(ScrollView scrollView) {
23
+        this.scrollView = scrollView;
24
+        this.scrollView.setScrollbarFadingEnabled(false);
25
+        listener.onScrollViewAdded(this.scrollView);
26
+    }
27
+
28
+    public boolean didInterceptTouchEvent(MotionEvent ev) {
29
+            return listener.onTouch(ev);
30
+    }
31
+
32
+    @Override
33
+    public boolean onTouch(View view, MotionEvent event) {
34
+        if (!didInterceptLastTouchEvent) {
35
+            scrollView.onTouchEvent(event);
36
+        }
37
+        return this.listener.onTouch(event);
38
+    }
39
+}

+ 14
- 0
android/app/src/main/java/com/reactnativenavigation/views/utils/ViewMeasurer.java View File

@@ -0,0 +1,14 @@
1
+package com.reactnativenavigation.views.utils;
2
+
3
+import static android.view.View.MeasureSpec;
4
+
5
+public class ViewMeasurer {
6
+
7
+    public int getMeasuredHeight(int heightMeasuerSpec) {
8
+        return MeasureSpec.getSize(heightMeasuerSpec);
9
+    }
10
+
11
+    public int getMeasuredWidth(int widthMeasureSpec) {
12
+        return MeasureSpec.getSize(widthMeasureSpec);
13
+    }
14
+}

BIN
example/img/gyro_header.jpg View File


+ 12
- 6
example/src/app.js View File

@@ -7,6 +7,12 @@ registerScreens();
7 7
 // this will start our app
8 8
 Navigation.startTabBasedApp({
9 9
   tabs: [
10
+    {
11
+      label: 'Collapsing',
12
+      screen: 'example.CollapsingTopBarScreen',
13
+      icon: require('../img/one.png'),
14
+      title: 'Collapsing',
15
+    },
10 16
     {
11 17
       label: 'One',
12 18
       screen: 'example.FirstTabScreen',
@@ -25,12 +31,12 @@ Navigation.startTabBasedApp({
25 31
       }
26 32
     }
27 33
   ],
28
-  drawer: {
29
-    left: {
30
-      screen: 'example.SideMenu'
31
-    }
32
-  },
33
-  portraitOnlyMode: true
34
+  appStyle: {
35
+    tabBarBackgroundColor: '#0f2362',
36
+    tabBarButtonColor: '#ffffff',
37
+    tabBarSelectedButtonColor: '#63d7cc'
38
+
39
+  }
34 40
 });
35 41
 //Navigation.startSingleScreenApp({
36 42
 //  screen: {

+ 133
- 0
example/src/screens/CollapsingTopBarScreen.android.js View File

@@ -0,0 +1,133 @@
1
+import React, {Component} from 'react';
2
+import {
3
+  Text,
4
+  View,
5
+  ScrollView,
6
+  TouchableOpacity,
7
+  StyleSheet,
8
+} from 'react-native';
9
+
10
+// collapsingToolBarImage: require('../../img/gyro_header.jpg'),
11
+// collapsingToolBarImage: "https://static.wixstatic.com/media/ec14061b42d1dc5b809367f7cfda8eff.jpg/v1/fill/w_1002,h_499,q_80/ec14061b42d1dc5b809367f7cfda8eff.webp",
12
+
13
+export default class ThirdTabScreen extends Component {
14
+  static navigatorStyle = {
15
+    drawUnderTabBar: true,
16
+    navBarButtonColor: '#ffffff',
17
+    navBarTextColor: '#ffffff',
18
+    collapsingToolBarImage: require('../../img/gyro_header.jpg'),
19
+    collapsingToolBarCollapsedColor: '#0f2362',
20
+    navBarBackgroundColor: '#eeeeee'
21
+  };
22
+
23
+  static navigatorButtons = {
24
+    rightButtons: [
25
+      {
26
+        title: 'Edit',
27
+        id: 'edit'
28
+      },
29
+      {
30
+        icon: require('../../img/navicon_add.png'),
31
+        id: 'add'
32
+      }
33
+    ]
34
+  };
35
+
36
+  constructor(props) {
37
+    super(props);
38
+    this.state = {
39
+      navBarVisibility: 'shown'
40
+    }
41
+  }
42
+  render() {
43
+    return (
44
+        <ScrollView style={styles.container}>
45
+          <View style={{flex: 1, backgroundColor: '#ffffff'}}>
46
+            <Text style={styles.button}>Row 0</Text>
47
+            <Text style={styles.button}>Row 1</Text>
48
+            <Text style={styles.button}>Row 2</Text>
49
+            <Text style={styles.button}>Row 3</Text>
50
+            <Text style={styles.button}>Row 4</Text>
51
+            <Text style={styles.button}>Row 5</Text>
52
+            <Text style={styles.button}>Row 6</Text>
53
+            <Text style={styles.button}>Row 7</Text>
54
+            <Text style={styles.button}>Row 8</Text>
55
+
56
+            <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
57
+              <Text style={styles.button}>Push Plain Screen</Text>
58
+            </TouchableOpacity>
59
+
60
+            <TouchableOpacity onPress={ this.onPushStyledPress.bind(this) }>
61
+              <Text style={styles.button}>Push Styled Screen</Text>
62
+            </TouchableOpacity>
63
+
64
+            <TouchableOpacity onPress={ this.onPushStyled2Press.bind(this) }>
65
+              <Text style={styles.button}>Push Styled Screen 2</Text>
66
+            </TouchableOpacity>
67
+
68
+            <TouchableOpacity onPress={ this.onModalPress.bind(this) }>
69
+              <Text style={styles.button}>Show Modal Screen</Text>
70
+            </TouchableOpacity>
71
+
72
+            <TouchableOpacity onPress={ this.onToggleNavBarPressed.bind(this) }>
73
+              <Text style={styles.button}>Toggle Navigation Bar</Text>
74
+            </TouchableOpacity>
75
+          </View>
76
+        </ScrollView>
77
+    );
78
+  }
79
+  onPushPress() {
80
+    this.props.navigator.push({
81
+      title: "More",
82
+      screen: "example.PushedScreen"
83
+    });
84
+  }
85
+  onPushStyledPress() {
86
+    this.props.navigator.push({
87
+      title: "Styled",
88
+      screen: "example.StyledScreen"
89
+    });
90
+  }
91
+  onPushStyled2Press () {
92
+    this.props.navigator.push({
93
+      title: "Styled",
94
+      titleImage: require('../../img/two.png'),
95
+      screen: "example.StyledScreen"
96
+    });
97
+  }
98
+  onModalPress() {
99
+    this.props.navigator.showModal({
100
+      title: "Modal",
101
+      screen: "example.ModalScreen"
102
+    });
103
+  }
104
+
105
+  onToggleNavBarPressed() {
106
+    this.state.navBarVisibility = (this.state.navBarVisibility === 'shown') ? 'hidden' : 'shown';
107
+    this.props.navigator.toggleNavBar({
108
+      to: this.state.navBarVisibility,
109
+      animated: true  // true is default
110
+    });
111
+  }
112
+
113
+  componentDidUpdate() {
114
+    console.error('this is an error: ' + Math.random());
115
+    this.state.navBarState = 'shown';
116
+  }
117
+
118
+}
119
+
120
+const styles = StyleSheet.create({
121
+  container: {
122
+    flex: 1,
123
+    padding: 20,
124
+    backgroundColor: '#eeeeee'
125
+  },
126
+  button: {
127
+    textAlign: 'center',
128
+    fontSize: 18,
129
+    marginBottom: 10,
130
+    marginTop:30,
131
+    color: 'blue'
132
+  }
133
+});

+ 0
- 101
example/src/screens/ThirdTabScreen.js View File

@@ -1,101 +0,0 @@
1
-import React, {Component} from 'react';
2
-import {
3
-  Text,
4
-  View,
5
-  ScrollView,
6
-  TouchableOpacity,
7
-  StyleSheet
8
-} from 'react-native';
9
-
10
-export default class ThirdTabScreen extends Component {
11
-  static navigatorStyle = {
12
-    drawUnderTabBar: true
13
-  };
14
-  constructor(props) {
15
-    super(props);
16
-    this.state = {
17
-      navBarVisability: 'shown'
18
-    }
19
-  }
20
-  render() {
21
-    return (
22
-      <View style={styles.container}>
23
-
24
-        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
25
-          <Text style={styles.button}>Push Plain Screen</Text>
26
-        </TouchableOpacity>
27
-
28
-        <TouchableOpacity onPress={ this.onPushStyledPress.bind(this) }>
29
-          <Text style={styles.button}>Push Styled Screen</Text>
30
-        </TouchableOpacity>
31
-
32
-        <TouchableOpacity onPress={ this.onPushStyled2Press.bind(this) }>
33
-          <Text style={styles.button}>Push Styled Screen 2</Text>
34
-        </TouchableOpacity>
35
-
36
-        <TouchableOpacity onPress={ this.onModalPress.bind(this) }>
37
-          <Text style={styles.button}>Show Modal Screen</Text>
38
-        </TouchableOpacity>
39
-
40
-        <TouchableOpacity onPress={ this.onToggleNavBarPressed.bind(this) }>
41
-          <Text style={styles.button}>Toggle Navigation Bar</Text>
42
-        </TouchableOpacity>
43
-
44
-      </View>
45
-    );
46
-  }
47
-  onPushPress() {
48
-    this.props.navigator.push({
49
-      title: "More",
50
-      screen: "example.PushedScreen"
51
-    });
52
-  }
53
-  onPushStyledPress() {
54
-    this.props.navigator.push({
55
-      title: "Styled",
56
-      screen: "example.StyledScreen"
57
-    });
58
-  }
59
-  onPushStyled2Press () {
60
-    this.props.navigator.push({
61
-      title: "Styled",
62
-      titleImage: require('../../img/two.png'),
63
-      screen: "example.StyledScreen"
64
-    });
65
-  }
66
-  onModalPress() {
67
-    this.props.navigator.showModal({
68
-      title: "Modal",
69
-      screen: "example.ModalScreen"
70
-    });
71
-  }
72
-
73
-  onToggleNavBarPressed() {
74
-    this.state.navBarVisability = (this.state.navBarVisability === 'shown') ? 'hidden' : 'shown';
75
-    this.props.navigator.toggleNavBar({
76
-      to: this.state.navBarVisability,
77
-      animated: true  // true is default
78
-    });
79
-  }
80
-
81
-  componentDidUpdate() {
82
-    console.error('this is an error: ' + Math.random());
83
-    this.state.navBarState = 'shown';
84
-  }
85
-
86
-}
87
-
88
-const styles = StyleSheet.create({
89
-  container: {
90
-    flex: 1,
91
-    padding: 20,
92
-    backgroundColor: 'white'
93
-  },
94
-  button: {
95
-    textAlign: 'center',
96
-    fontSize: 18,
97
-    marginBottom: 10,
98
-    marginTop:10,
99
-    color: 'blue'
100
-  }
101
-});

+ 2
- 2
example/src/screens/index.android.js View File

@@ -2,19 +2,19 @@ import {Navigation} from 'react-native-navigation';
2 2
 
3 3
 import FirstTabScreen from './FirstTabScreen';
4 4
 import SecondTabScreen from './SecondTabScreen';
5
-import ThirdTabScreen from './ThirdTabScreen';
6 5
 import PushedScreen from './PushedScreen';
7 6
 import StyledScreen from './StyledScreen';
8 7
 import SideMenu from './SideMenu';
9 8
 import ModalScreen from './ModalScreen';
9
+import CollapsingTopBarScreen from './CollapsingTopBarScreen';
10 10
 
11 11
 // register all screens of the app (including internal ones)
12 12
 export function registerScreens() {
13 13
   Navigation.registerComponent('example.FirstTabScreen', () => FirstTabScreen);
14 14
   Navigation.registerComponent('example.SecondTabScreen', () => SecondTabScreen);
15
-  Navigation.registerComponent('example.ThirdTabScreen', () => ThirdTabScreen);
16 15
   Navigation.registerComponent('example.PushedScreen', () => PushedScreen);
17 16
   Navigation.registerComponent('example.StyledScreen', () => StyledScreen);
18 17
   Navigation.registerComponent('example.ModalScreen', () => ModalScreen);
19 18
   Navigation.registerComponent('example.SideMenu', () => SideMenu);
19
+  Navigation.registerComponent('example.CollapsingTopBarScreen', () => CollapsingTopBarScreen);
20 20
 }

+ 0
- 2
example/src/screens/index.ios.js View File

@@ -2,7 +2,6 @@ import {Navigation} from 'react-native-navigation';
2 2
 
3 3
 import FirstTabScreen from './FirstTabScreen';
4 4
 import SecondTabScreen from './SecondTabScreen';
5
-import ThirdTabScreen from './ThirdTabScreen';
6 5
 import PushedScreen from './PushedScreen';
7 6
 import StyledScreen from './StyledScreen';
8 7
 import SideMenu from './SideMenu';
@@ -14,7 +13,6 @@ import LightBoxScreen from './LightBoxScreen';
14 13
 export function registerScreens() {
15 14
   Navigation.registerComponent('example.FirstTabScreen', () => FirstTabScreen);
16 15
   Navigation.registerComponent('example.SecondTabScreen', () => SecondTabScreen);
17
-  Navigation.registerComponent('example.ThirdTabScreen', () => ThirdTabScreen);
18 16
   Navigation.registerComponent('example.PushedScreen', () => PushedScreen);
19 17
   Navigation.registerComponent('example.StyledScreen', () => StyledScreen);
20 18
   Navigation.registerComponent('example.ModalScreen', () => ModalScreen);

+ 16
- 3
src/deprecated/platformSpecificDeprecated.android.js View File

@@ -113,10 +113,11 @@ function convertStyleParams(originalStyleObject) {
113 113
     return null;
114 114
   }
115 115
 
116
-  return {
116
+  let ret = {
117 117
     statusBarColor: originalStyleObject.statusBarColor,
118 118
     topBarColor: originalStyleObject.navBarBackgroundColor,
119
-    topBarTransparent: originalStyleObject.navBarTransparent,
119
+    collapsingToolBarImage: originalStyleObject.collapsingToolBarImage,
120
+    collapsingToolBarCollapsedColor: originalStyleObject.collapsingToolBarCollapsedColor,
120 121
     titleBarHidden: originalStyleObject.navBarHidden,
121 122
     titleBarTitleColor: originalStyleObject.navBarTextColor,
122 123
     titleBarSubtitleColor: originalStyleObject.navBarTextSubtitleColor,
@@ -148,6 +149,18 @@ function convertStyleParams(originalStyleObject) {
148 149
 
149 150
     navigationBarColor: originalStyleObject.navigationBarColor
150 151
   }
152
+
153
+  if (originalStyleObject.collapsingToolBarImage) {
154
+    if (_.isString(originalStyleObject.collapsingToolBarImage)) {
155
+      ret.collapsingToolBarImage = originalStyleObject.collapsingToolBarImage;
156
+    }
157
+
158
+    const collapsingToolBarImage = resolveAssetSource(originalStyleObject.collapsingToolBarImage)
159
+    if (collapsingToolBarImage) {
160
+      ret.collapsingToolBarImage = collapsingToolBarImage.uri;
161
+    }
162
+  }
163
+  return ret;
151 164
 }
152 165
 
153 166
 function convertDrawerParamsToSideMenuParams(drawerParams) {
@@ -398,7 +411,7 @@ function getFab(screen) {
398 411
       _.forEach(fab.actions, (action) => {
399 412
         action.icon = resolveAssetSource(action.icon).uri;
400 413
         return action;
401
-      })
414
+      });
402 415
     }
403 416
 
404 417
     return fab;