Browse Source

Less intrusive collapse (#590)

* Less intrusive collapsing implementation

In order for a screen to implement collapsingTopBar, it had to define
a specific set of style properties such as drawUnderTabBar or
drawUnderNavBar. This commit aims to make implementing collapsingTopBar
less intrusive by inferring these properties from hideTitleBarOnScroll.

This commit introduces a few fixes and changes related to
the collapsingTopBar mechanism.

1. Screens are now drawn under bottomTabs if titleBarHideOnScroll
   is true.

2. TitleBarHideOnScroll can be passed in AppStyle.

3. ScrollView is detected properly after it gets detached due to large
   diff in render.

4. Take BottomTabs height into account when measuring collapsing
   screens.
Guy Carmeli 7 years ago
parent
commit
cc199a50a9

+ 7
- 7
android/app/src/main/java/com/reactnativenavigation/params/parsers/CollapsingTopBarParamsParser.java View File

@@ -12,13 +12,17 @@ import com.reactnativenavigation.views.collapsingToolbar.behaviours.TitleBarHide
12 12
 
13 13
 class CollapsingTopBarParamsParser extends Parser {
14 14
     private Bundle params;
15
+    private boolean titleBarHideOnScroll;
16
+    private boolean drawBelowTopBar;
15 17
 
16
-    CollapsingTopBarParamsParser(Bundle params) {
18
+    CollapsingTopBarParamsParser(Bundle params, boolean titleBarHideOnScroll, boolean drawBelowTopBar) {
17 19
         this.params = params;
20
+        this.titleBarHideOnScroll = titleBarHideOnScroll;
21
+        this.drawBelowTopBar = drawBelowTopBar;
18 22
     }
19 23
 
20 24
     public CollapsingTopBarParams parse() {
21
-        if (!hasBackgroundImage() && !shouldHideTitleBarOnScroll()) {
25
+        if (!hasBackgroundImage() && !titleBarHideOnScroll) {
22 26
             return null;
23 27
         }
24 28
 
@@ -35,7 +39,7 @@ class CollapsingTopBarParamsParser extends Parser {
35 39
         if (hasBackgroundImage()) {
36 40
             return new CollapseTopBarBehaviour();
37 41
         }
38
-        if (shouldHideTitleBarOnScroll() && params.getBoolean("drawBelowTopBar", false)) {
42
+        if (titleBarHideOnScroll && drawBelowTopBar) {
39 43
             return new CollapseTitleBarBehaviour();
40 44
         }
41 45
         return new TitleBarHideOnScrollBehaviour();
@@ -44,8 +48,4 @@ class CollapsingTopBarParamsParser extends Parser {
44 48
     private boolean hasBackgroundImage() {
45 49
         return params.containsKey("collapsingToolBarImage");
46 50
     }
47
-
48
-    private boolean shouldHideTitleBarOnScroll() {
49
-        return params.getBoolean("titleBarHideOnScroll", false);
50
-    }
51 51
 }

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

@@ -14,26 +14,28 @@ public class StyleParamsParser {
14 14
     }
15 15
 
16 16
     public StyleParams parse() {
17
-        StyleParams result = new StyleParams();
18 17
         if (params == null) {
19
-            result.titleBarDisabledButtonColor = getTitleBarDisabledButtonColor();
20
-            result.topBarElevationShadowEnabled = true;
21
-            return result;
18
+            return createDefaultStyleParams();
22 19
         }
23 20
 
21
+        StyleParams result = new StyleParams();
24 22
         result.statusBarColor = getColor("statusBarColor", getDefaultStatusBarColor());
25 23
         result.contextualMenuStatusBarColor = getColor("contextualMenuStatusBarColor", getDefaultContextualMenuStatusBarColor());
26 24
         result.contextualMenuButtonsColor = getColor("contextualMenuButtonsColor", getDefaultContextualMenuButtonsColor());
27 25
         result.contextualMenuBackgroundColor = getColor("contextualMenuBackgroundColor", getDefaultContextualMenuBackgroundColor());
28 26
 
29 27
         result.topBarColor = getColor("topBarColor", getDefaultTopBarColor());
30
-        result.collapsingTopBarParams = new CollapsingTopBarParamsParser(params).parse();
31
-        result.titleBarHidden = getBoolean("titleBarHidden", getDefaultTopBarHidden());
28
+        result.titleBarHideOnScroll = getBoolean("titleBarHideOnScroll", getDefaultTitleBarHideOnScroll());
32 29
         result.topBarTransparent = getBoolean("topBarTransparent", getDefaultTopBarHidden());
30
+        result.drawScreenBelowTopBar = params.getBoolean("drawBelowTopBar", getDefaultScreenBelowTopBar());
31
+        if (result.topBarTransparent) {
32
+            result.drawScreenBelowTopBar = false;
33
+        }
34
+        result.collapsingTopBarParams = new CollapsingTopBarParamsParser(params, result.titleBarHideOnScroll, result.drawScreenBelowTopBar).parse();
35
+        result.titleBarHidden = getBoolean("titleBarHidden", getDefaultTopBarHidden());
33 36
         result.topBarElevationShadowEnabled = getBoolean("topBarElevationShadowEnabled", getDefaultTopBarElevationShadowEnabled());
34 37
         result.titleBarTitleColor = getColor("titleBarTitleColor", getDefaultTitleBarColor());
35 38
         result.topBarTranslucent = getBoolean("topBarTranslucent", getDefaultTopBarTranslucent());
36
-        result.titleBarHideOnScroll = getBoolean("titleBarHideOnScroll", getDefaultTitleBarHideOnScroll());
37 39
 
38 40
         result.titleBarSubtitleColor = getColor("titleBarSubtitleColor", getDefaultSubtitleBarColor());
39 41
         result.titleBarButtonColor = getColor("titleBarButtonColor", getTitleBarButtonColor());
@@ -46,18 +48,13 @@ public class StyleParamsParser {
46 48
         result.selectedTopTabIndicatorHeight = getInt("selectedTopTabIndicatorHeight", getDefaultSelectedTopTabIndicatorHeight());
47 49
         result.selectedTopTabIndicatorColor = getColor("selectedTopTabIndicatorColor", getDefaultSelectedTopTabIndicatorColor());
48 50
 
49
-        result.drawScreenBelowTopBar = params.getBoolean("drawBelowTopBar", getDefaultScreenBelowTopBar());
50
-        if (result.topBarTransparent) {
51
-            result.drawScreenBelowTopBar = false;
52
-        }
53
-
54 51
         result.screenBackgroundColor = getColor("screenBackgroundColor", getDefaultScreenBackgroundColor());
55 52
 
56 53
         result.bottomTabsHidden = getBoolean("bottomTabsHidden", getDefaultBottomTabsHidden());
57 54
         result.drawScreenAboveBottomTabs = !result.bottomTabsHidden &&
58 55
                                            params.getBoolean("drawScreenAboveBottomTabs", getDefaultDrawScreenAboveBottomTabs());
59
-        result.bottomTabsHiddenOnScroll =
60
-                getBoolean("bottomTabsHiddenOnScroll", getDefaultBottomTabsHiddenOnScroll());
56
+        result.drawScreenAboveBottomTabs = drawScreenUnderBottomTabsIfTitleBarIsHiddenOnScroll(result);
57
+        result.bottomTabsHiddenOnScroll = getBoolean("bottomTabsHiddenOnScroll", getDefaultBottomTabsHiddenOnScroll());
61 58
         result.bottomTabsColor = getColor("bottomTabsColor", getDefaultBottomTabsColor());
62 59
         result.bottomTabsButtonColor = getColor("bottomTabsButtonColor", getDefaultBottomTabsButtonColor());
63 60
         result.selectedBottomTabsButtonColor =
@@ -71,6 +68,18 @@ public class StyleParamsParser {
71 68
         return result;
72 69
     }
73 70
 
71
+    private StyleParams createDefaultStyleParams() {
72
+        StyleParams result = new StyleParams();
73
+        result.titleBarDisabledButtonColor = getTitleBarDisabledButtonColor();
74
+        result.topBarElevationShadowEnabled = true;
75
+        result.titleBarHideOnScroll = false;
76
+        return result;
77
+    }
78
+
79
+    private boolean drawScreenUnderBottomTabsIfTitleBarIsHiddenOnScroll(StyleParams result) {
80
+        return !result.titleBarHideOnScroll;
81
+    }
82
+
74 83
     private StyleParams.Color getDefaultContextualMenuStatusBarColor() {
75 84
         return new StyleParams.Color(Color.parseColor("#7c7c7c"));
76 85
     }

+ 7
- 3
android/app/src/main/java/com/reactnativenavigation/screens/CollapsingSingleScreen.java View File

@@ -38,13 +38,17 @@ public class CollapsingSingleScreen extends SingleScreen {
38 38
     @Override
39 39
     protected void createContent() {
40 40
         contentView = new CollapsingContentView(getContext(), screenParams.screenId, screenParams.navigationParams);
41
-        if (screenParams.styleParams.drawScreenBelowTopBar) {
42
-            contentView.setViewMeasurer(new CollapsingViewMeasurer((CollapsingTopBar) topBar, this));
43
-        }
41
+        setViewMeasurer();
44 42
         setupCollapseDetection((CollapsingTopBar) topBar);
45 43
         addView(contentView, createLayoutParams());
46 44
     }
47 45
 
46
+    private void setViewMeasurer() {
47
+        if (screenParams.styleParams.drawScreenBelowTopBar || screenParams.styleParams.drawScreenAboveBottomTabs) {
48
+            contentView.setViewMeasurer(new CollapsingViewMeasurer((CollapsingTopBar) topBar, this, screenParams.styleParams));
49
+        }
50
+    }
51
+
48 52
     private void setupCollapseDetection(final CollapsingTopBar topBar) {
49 53
         ((CollapsingContentView) contentView).setupCollapseDetection(getScrollListener(topBar), new OnScrollViewAddedListener() {
50 54
             @Override

+ 4
- 2
android/app/src/main/java/com/reactnativenavigation/screens/CollapsingViewPagerScreen.java View File

@@ -1,5 +1,6 @@
1 1
 package com.reactnativenavigation.screens;
2 2
 
3
+import android.annotation.SuppressLint;
3 4
 import android.content.Context;
4 5
 import android.support.v4.view.ViewPager;
5 6
 import android.support.v7.app.AppCompatActivity;
@@ -19,13 +20,14 @@ import com.reactnativenavigation.views.collapsingToolbar.CollapsingTopBar;
19 20
 import com.reactnativenavigation.views.collapsingToolbar.CollapsingView;
20 21
 import com.reactnativenavigation.views.collapsingToolbar.CollapsingViewMeasurer;
21 22
 import com.reactnativenavigation.views.collapsingToolbar.CollapsingViewPager;
23
+import com.reactnativenavigation.views.collapsingToolbar.CollapsingViewPagerContentViewMeasurer;
22 24
 import com.reactnativenavigation.views.collapsingToolbar.OnScrollListener;
23 25
 import com.reactnativenavigation.views.collapsingToolbar.OnScrollViewAddedListener;
24 26
 import com.reactnativenavigation.views.collapsingToolbar.ScrollListener;
25 27
 import com.reactnativenavigation.views.collapsingToolbar.behaviours.CollapseBehaviour;
26 28
 
29
+@SuppressLint("ViewConstructor")
27 30
 public class CollapsingViewPagerScreen extends ViewPagerScreen {
28
-
29 31
     public CollapsingViewPagerScreen(AppCompatActivity activity, ScreenParams screenParams, LeftButtonOnClickListener backButtonListener) {
30 32
         super(activity, screenParams, backButtonListener);
31 33
     }
@@ -49,7 +51,7 @@ public class CollapsingViewPagerScreen extends ViewPagerScreen {
49 51
     protected ContentView createContentView(PageParams tab) {
50 52
         if (tab.hasCollapsingTopBar()) {
51 53
             CollapsingContentView contentView = new CollapsingContentView(getContext(), tab.screenId, tab.navigationParams);
52
-            contentView.setViewMeasurer(new CollapsingViewMeasurer((CollapsingTopBar) topBar, this));
54
+            contentView.setViewMeasurer(new CollapsingViewPagerContentViewMeasurer((CollapsingTopBar) topBar, this, screenParams.styleParams));
53 55
             setupCollapseDetection(contentView);
54 56
             return contentView;
55 57
         } else {

+ 3
- 1
android/app/src/main/java/com/reactnativenavigation/views/CollapsingContentView.java View File

@@ -1,5 +1,6 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3
+import android.annotation.SuppressLint;
3 4
 import android.content.Context;
4 5
 import android.support.annotation.Nullable;
5 6
 import android.view.MotionEvent;
@@ -15,6 +16,7 @@ import com.reactnativenavigation.views.collapsingToolbar.ScrollViewDelegate;
15 16
 import com.reactnativenavigation.views.collapsingToolbar.ViewCollapser;
16 17
 import com.reactnativenavigation.views.utils.ScrollViewDetector;
17 18
 
19
+@SuppressLint("ViewConstructor")
18 20
 public class CollapsingContentView extends ContentView implements CollapsingView {
19 21
 
20 22
     private @Nullable ScrollViewDelegate scrollViewDelegate;
@@ -46,7 +48,7 @@ public class CollapsingContentView extends ContentView implements CollapsingView
46 48
     public void onViewAdded(final View child) {
47 49
         super.onViewAdded(child);
48 50
         if (scrollViewDetector != null) {
49
-            scrollViewDetector.detectScrollViewAdded(child);
51
+            scrollViewDetector.findScrollView(child);
50 52
         }
51 53
     }
52 54
 

+ 16
- 6
android/app/src/main/java/com/reactnativenavigation/views/collapsingToolbar/CollapsingViewMeasurer.java View File

@@ -1,20 +1,30 @@
1 1
 package com.reactnativenavigation.views.collapsingToolbar;
2 2
 
3
+import com.reactnativenavigation.params.StyleParams;
3 4
 import com.reactnativenavigation.screens.Screen;
4 5
 import com.reactnativenavigation.utils.ViewUtils;
5 6
 import com.reactnativenavigation.views.utils.ViewMeasurer;
6 7
 
7 8
 public class CollapsingViewMeasurer extends ViewMeasurer {
8
-    private int collapsedTopBarHeight;
9
-    private float getFinalCollapseValue;
10
-    private int screenHeight;
9
+
10
+    int collapsedTopBarHeight;
11
+    private float finalCollapseValue;
12
+    int screenHeight;
13
+    int bottomTabsHeight = 0;
14
+    boolean bottomTabsHidden;
15
+
16
+    public CollapsingViewMeasurer(CollapsingTopBar topBar, Screen screen, StyleParams styleParams) {
17
+        this(topBar, screen);
18
+        bottomTabsHidden = styleParams.bottomTabsHidden;
19
+        bottomTabsHeight = (int) ViewUtils.convertDpToPixel(56);
20
+    }
11 21
 
12 22
     public CollapsingViewMeasurer(final CollapsingTopBar topBar, final Screen collapsingSingleScreen) {
13 23
         ViewUtils.runOnPreDraw(topBar, new Runnable() {
14 24
             @Override
15 25
             public void run() {
16 26
                 collapsedTopBarHeight = topBar.getCollapsedHeight();
17
-                getFinalCollapseValue = topBar.getFinalCollapseValue();
27
+                finalCollapseValue = topBar.getFinalCollapseValue();
18 28
             }
19 29
         });
20 30
 
@@ -27,11 +37,11 @@ public class CollapsingViewMeasurer extends ViewMeasurer {
27 37
     }
28 38
 
29 39
     public float getFinalCollapseValue() {
30
-        return getFinalCollapseValue;
40
+        return finalCollapseValue;
31 41
     }
32 42
 
33 43
     @Override
34 44
     public int getMeasuredHeight(int heightMeasureSpec) {
35
-        return screenHeight - collapsedTopBarHeight;
45
+        return screenHeight - collapsedTopBarHeight + (bottomTabsHidden ? bottomTabsHeight : 0);
36 46
     }
37 47
 }

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

@@ -0,0 +1,16 @@
1
+package com.reactnativenavigation.views.collapsingToolbar;
2
+
3
+import com.reactnativenavigation.params.StyleParams;
4
+import com.reactnativenavigation.screens.Screen;
5
+
6
+public class CollapsingViewPagerContentViewMeasurer extends CollapsingViewMeasurer {
7
+
8
+    public CollapsingViewPagerContentViewMeasurer(CollapsingTopBar topBar, Screen screen, StyleParams styleParams) {
9
+        super(topBar, screen, styleParams);
10
+    }
11
+
12
+    @Override
13
+    public int getMeasuredHeight(int heightMeasureSpec) {
14
+        return screenHeight - collapsedTopBarHeight - (bottomTabsHidden ? 0 : bottomTabsHeight);
15
+    }
16
+}

+ 35
- 21
android/app/src/main/java/com/reactnativenavigation/views/utils/ScrollViewDetector.java View File

@@ -2,6 +2,7 @@ package com.reactnativenavigation.views.utils;
2 2
 
3 3
 import android.view.View;
4 4
 import android.view.ViewGroup;
5
+import android.view.ViewTreeObserver;
5 6
 import android.widget.ScrollView;
6 7
 
7 8
 import com.reactnativenavigation.utils.ViewUtils;
@@ -10,57 +11,70 @@ import com.reactnativenavigation.views.collapsingToolbar.OnScrollViewAddedListen
10 11
 import com.reactnativenavigation.views.collapsingToolbar.ScrollViewDelegate;
11 12
 
12 13
 public class ScrollViewDetector {
14
+    private static final boolean SCROLLVIEW_FOUND = true;
15
+    private static final boolean SCROLLVIEW_NOT_FOUND = false;
13 16
     private OnScrollViewAddedListener scrollViewAddedListener;
14 17
     private ScrollViewDelegate scrollViewDelegate;
15
-    private View.OnAttachStateChangeListener stateChangeListener;
18
+    private View.OnAttachStateChangeListener scrollViewStateChangeListener;
19
+    private ViewTreeObserver.OnGlobalLayoutListener scrollViewDetectorListener;
16 20
 
17
-    public ScrollViewDetector(final ContentView contentView,
18
-                              OnScrollViewAddedListener onScrollViewAddedListener,
19
-                              final ScrollViewDelegate scrollViewDelegate) {
21
+    public ScrollViewDetector(ContentView contentView, OnScrollViewAddedListener onScrollViewAddedListener,
22
+                              ScrollViewDelegate scrollViewDelegate) {
20 23
         this.scrollViewAddedListener = onScrollViewAddedListener;
21 24
         this.scrollViewDelegate = scrollViewDelegate;
22
-        stateChangeListener = new View.OnAttachStateChangeListener() {
23
-            @Override
24
-            public void onViewAttachedToWindow(View v) {
25
-
26
-            }
25
+        scrollViewStateChangeListener = createScrollViewStateChangeListener(contentView, scrollViewDelegate);
26
+    }
27 27
 
28
+    private StateChangeListenerAdapter createScrollViewStateChangeListener(final ContentView contentView, final ScrollViewDelegate scrollViewDelegate) {
29
+        return new StateChangeListenerAdapter() {
28 30
             @Override
29
-            public void onViewDetachedFromWindow(View v) {
31
+            public void onViewDetachedFromWindow(View scrollView) {
30 32
                 scrollViewDelegate.getScrollView().removeOnAttachStateChangeListener(this);
31 33
                 scrollViewDelegate.onScrollViewRemoved();
32
-                contentView.post(new Runnable() {
33
-                    @Override
34
-                    public void run() {
35
-                        detectScrollViewAdded(contentView);
34
+                detectScrollView(scrollViewDelegate, contentView);
35
+            }
36
+        };
37
+    }
38
+
39
+    private void detectScrollView(final ScrollViewDelegate scrollViewDelegate, final ContentView contentView) {
40
+        scrollViewDetectorListener = new ViewTreeObserver.OnGlobalLayoutListener() {
41
+            @Override
42
+            public void onGlobalLayout() {
43
+                if (!scrollViewDelegate.hasScrollView()) {
44
+                    if (findScrollView(contentView) && contentView.getViewTreeObserver().isAlive()) {
45
+                        contentView.getViewTreeObserver().removeOnGlobalLayoutListener(scrollViewDetectorListener);
36 46
                     }
37
-                });
47
+                }
38 48
             }
39 49
         };
50
+        contentView.getViewTreeObserver().addOnGlobalLayoutListener(scrollViewDetectorListener);
40 51
     }
41 52
 
42
-    public void detectScrollViewAdded(View child) {
53
+    public boolean findScrollView(View child) {
43 54
         if (child instanceof ScrollView) {
44
-            onScrollViewAdded((ScrollView) child);
55
+            onScrollViewFound((ScrollView) child);
56
+            return SCROLLVIEW_FOUND;
45 57
         } else if (child instanceof ViewGroup) {
46 58
             Object maybeScrollView = ViewUtils.findChildByClass((ViewGroup) child, ScrollView.class);
47 59
             if (maybeScrollView instanceof ScrollView) {
48
-                onScrollViewAdded((ScrollView) maybeScrollView);
60
+                onScrollViewFound((ScrollView) maybeScrollView);
61
+                return SCROLLVIEW_FOUND;
49 62
             }
50 63
         }
64
+        return SCROLLVIEW_NOT_FOUND;
51 65
     }
52 66
 
53
-    private void onScrollViewAdded(final ScrollView scrollView) {
67
+    private void onScrollViewFound(final ScrollView scrollView) {
54 68
         if (scrollViewDelegate != null && !scrollViewDelegate.hasScrollView()) {
55 69
             scrollViewDelegate.onScrollViewAdded(scrollView);
56 70
             scrollViewAddedListener.onScrollViewAdded(scrollView);
57
-            scrollView.addOnAttachStateChangeListener(stateChangeListener);
71
+            scrollView.addOnAttachStateChangeListener(scrollViewStateChangeListener);
58 72
         }
59 73
     }
60 74
 
61 75
     public void destroy() {
62 76
         if (scrollViewDelegate.getScrollView() != null) {
63
-            scrollViewDelegate.getScrollView().removeOnAttachStateChangeListener(stateChangeListener);
77
+            scrollViewDelegate.getScrollView().removeOnAttachStateChangeListener(scrollViewStateChangeListener);
64 78
         }
65 79
     }
66 80
 }

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

@@ -0,0 +1,15 @@
1
+package com.reactnativenavigation.views.utils;
2
+
3
+import android.view.View;
4
+
5
+class StateChangeListenerAdapter implements View.OnAttachStateChangeListener {
6
+    @Override
7
+    public void onViewAttachedToWindow(View view) {
8
+
9
+    }
10
+
11
+    @Override
12
+    public void onViewDetachedFromWindow(View view) {
13
+
14
+    }
15
+}