Browse Source

Topbar v2 (#2413)

* readme

* doc

* topbar fix

* rm doc

* options presenter fix

* rm userless stuff

* WIP

* WIP

* WIP

* translation

* WIP

* todo

* draw under

* fix merge

* merge fix

* more merge fix

* improve calc

* animation improvement

* fix tests

* refactor

* refctor

* tests

* fix test

* anim improve

* rm outdated

* refactoring

* more refactoring

* tests

* refactor

* hotfix

* more refactoring

* refactor

* some more refactoring

* refactor

* Collapse topbar
Roman Kozlov 7 years ago
parent
commit
a9e2068508
32 changed files with 1164 additions and 838 deletions
  1. 96
    90
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java
  2. 35
    24
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java
  3. 1
    1
      lib/android/app/build.gradle
  4. 83
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java
  5. 0
    210
      lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java
  6. 82
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java
  7. 93
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java
  8. 49
    0
      lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java
  9. 62
    46
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  10. 21
    33
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  11. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/UiUtils.java
  12. 67
    61
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ContainerViewController.java
  13. 6
    6
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  14. 8
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/Container.java
  15. 73
    45
      lib/android/app/src/main/java/com/reactnativenavigation/views/ContainerLayout.java
  16. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/ContainerViewCreator.java
  17. 140
    92
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  18. 33
    6
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java
  19. 44
    19
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerLayout.java
  20. 37
    22
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerView.java
  21. 24
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestNavigationAnimator.java
  22. 0
    25
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestStackAnimator.java
  23. 32
    26
      lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java
  24. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  25. 134
    114
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  26. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  27. 4
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java
  28. 1
    2
      lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java
  29. 2
    2
      lib/android/gradle/wrapper/gradle-wrapper.properties
  30. 2
    2
      lib/src/params/options/TopBar.js
  31. 2
    1
      package.json
  32. 14
    1
      playground/src/containers/ScrollViewScreen.js

+ 96
- 90
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java View File

@@ -15,94 +15,100 @@ import static org.assertj.core.api.Java6Assertions.*;
15 15
 
16 16
 @RunWith(AndroidJUnit4.class)
17 17
 public abstract class BaseTest {
18
-	static final String PACKAGE_NAME = "com.reactnativenavigation.playground";
19
-	private static final long TIMEOUT = 60000;
20
-
21
-	@Before
22
-	public void beforeEach() throws Exception {
23
-		device().wakeUp();
24
-		device().setOrientationNatural();
25
-		launchTheApp();
26
-		assertMainShown();
27
-	}
28
-
29
-	@After
30
-	public void afterEach() throws Exception {
31
-		device().executeShellCommand("am force-stop " + PACKAGE_NAME);
32
-		device().executeShellCommand("am kill " + PACKAGE_NAME);
33
-	}
34
-
35
-	public UiDevice device() {
36
-		return UiDevice.getInstance(getInstrumentation());
37
-	}
38
-
39
-	public void launchTheApp() throws Exception {
40
-		device().executeShellCommand("am start -n " + PACKAGE_NAME + "/.MainActivity");
41
-		device().waitForIdle();
42
-		acceptOverlayPermissionIfNeeded();
43
-		device().wait(Until.gone(By.textContains("Please wait")), 1000 * 60 * 3);
44
-	}
45
-
46
-	public void assertMainShown() {
47
-		assertExists(By.text("React Native Navigation!"));
48
-	}
49
-
50
-	public void acceptOverlayPermissionIfNeeded() throws Exception {
51
-		if (isRequestingOverlayPermission()) {
52
-			if (!elementByText("Playground").exists()) {
53
-				scrollToText("Playground");
54
-			}
55
-			elementByText("Playground").click();
56
-			device().findObject(new UiSelector().checkable(true).checked(false)).click();
57
-			device().pressBack();
58
-			device().pressBack();
59
-		}
60
-	}
61
-
62
-	private boolean isRequestingOverlayPermission() {
63
-		return device().wait(Until.hasObject(By.pkg("com.android.settings").depth(0)), 300);
64
-	}
65
-
66
-	public UiObject elementByText(String text) {
67
-		return device().findObject(new UiSelector().text(text));
68
-	}
69
-
70
-	public UiObject elementByTextContains(String text) {
71
-		return device().findObject(new UiSelector().textContains(text));
72
-	}
73
-
74
-	public void scrollToText(String txt) throws Exception {
75
-		new UiScrollable(new UiSelector().scrollable(true)).scrollTextIntoView(txt);
76
-	}
77
-
78
-	public void assertExists(BySelector selector) {
79
-		assertThat(device().wait(Until.hasObject(selector), TIMEOUT)).withFailMessage("expected %1$s to be visible", selector).isTrue();
80
-		assertThat(device().findObject(selector).getVisibleCenter().x).isPositive().isLessThan(device().getDisplayWidth());
81
-		assertThat(device().findObject(selector).getVisibleCenter().y).isPositive().isLessThan(device().getDisplayHeight());
82
-	}
83
-
84
-	public Bitmap captureScreenshot() throws Exception {
85
-		File file = File.createTempFile("tmpE2E", "png");
86
-		device().takeScreenshot(file);
87
-		Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
88
-		file.delete();
89
-		return bitmap;
90
-	}
91
-
92
-	public void swipeOpenFromLeft() {
93
-		int w = device().getDisplayWidth();
94
-		int h = device().getDisplayHeight();
95
-		device().swipe(5, h / 2, w / 2, h / 2, 10);
96
-	}
97
-
98
-	public void swipeOpenFromRight() {
99
-		int w = device().getDisplayWidth();
100
-		int h = device().getDisplayHeight();
101
-		device().swipe(w - 5, h / 2, w / 2, h / 2, 10);
102
-	}
103
-
104
-	public boolean isDebug() throws Exception {
105
-		PackageInfo packageInfo = getInstrumentation().getTargetContext().getPackageManager().getPackageInfo("com.reactnativenavigation.playground", 0);
106
-		return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
107
-	}
18
+    static final String PACKAGE_NAME = "com.reactnativenavigation.playground";
19
+    private static final long TIMEOUT = 60000;
20
+
21
+    @Before
22
+    public void beforeEach() throws Exception {
23
+        device().wakeUp();
24
+        device().setOrientationNatural();
25
+        launchTheApp();
26
+        assertMainShown();
27
+    }
28
+
29
+    @After
30
+    public void afterEach() throws Exception {
31
+        device().executeShellCommand("am force-stop " + PACKAGE_NAME);
32
+        device().executeShellCommand("am kill " + PACKAGE_NAME);
33
+    }
34
+
35
+    public UiDevice device() {
36
+        return UiDevice.getInstance(getInstrumentation());
37
+    }
38
+
39
+    public void launchTheApp() throws Exception {
40
+        device().executeShellCommand("am start -n " + PACKAGE_NAME + "/.MainActivity");
41
+        device().waitForIdle();
42
+        acceptOverlayPermissionIfNeeded();
43
+        device().wait(Until.gone(By.textContains("Please wait")), 1000 * 60 * 3);
44
+    }
45
+
46
+    public void assertMainShown() {
47
+        assertExists(By.text("React Native Navigation!"));
48
+    }
49
+
50
+    public void acceptOverlayPermissionIfNeeded() throws Exception {
51
+        if (isRequestingOverlayPermission()) {
52
+            if (!elementByText("Playground").exists()) {
53
+                scrollToText("Playground");
54
+            }
55
+            elementByText("Playground").click();
56
+            device().findObject(new UiSelector().checkable(true).checked(false)).click();
57
+            device().pressBack();
58
+            device().pressBack();
59
+        }
60
+    }
61
+
62
+    private boolean isRequestingOverlayPermission() {
63
+        return device().wait(Until.hasObject(By.pkg("com.android.settings").depth(0)), 300);
64
+    }
65
+
66
+    public UiObject elementByText(String text) {
67
+        return device().findObject(new UiSelector().text(text));
68
+    }
69
+
70
+    public UiObject elementByTextContains(String text) {
71
+        return device().findObject(new UiSelector().textContains(text));
72
+    }
73
+
74
+    public void scrollToText(String txt) throws Exception {
75
+        new UiScrollable(new UiSelector().scrollable(true)).scrollTextIntoView(txt);
76
+    }
77
+
78
+    public void assertExists(BySelector selector) {
79
+        assertThat(device().wait(Until.hasObject(selector), TIMEOUT)).withFailMessage("expected %1$s to be visible", selector).isTrue();
80
+        assertThat(device().findObject(selector).getVisibleCenter().x).isPositive().isLessThan(device().getDisplayWidth());
81
+        assertThat(device().findObject(selector).getVisibleCenter().y).isPositive().isLessThan(device().getDisplayHeight());
82
+    }
83
+
84
+    public Bitmap captureScreenshot() throws Exception {
85
+        File file = File.createTempFile("tmpE2E", "png");
86
+        device().takeScreenshot(file);
87
+        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
88
+        file.delete();
89
+        return bitmap;
90
+    }
91
+
92
+    public void swipeOpenFromLeft() {
93
+        int w = device().getDisplayWidth();
94
+        int h = device().getDisplayHeight();
95
+        device().swipe(5, h / 2, w / 2, h / 2, 10);
96
+    }
97
+
98
+    public void swipeOpenFromRight() {
99
+        int w = device().getDisplayWidth();
100
+        int h = device().getDisplayHeight();
101
+        device().swipe(w - 5, h / 2, w / 2, h / 2, 10);
102
+    }
103
+
104
+    public void swipeUp() {
105
+        int w = device().getDisplayWidth();
106
+        int h = device().getDisplayHeight();
107
+        device().drag(w / 2, h / 2 + 100, w / 2, h / 2 - 100, 10);
108
+    }
109
+
110
+    public boolean isDebug() throws Exception {
111
+        PackageInfo packageInfo = getInstrumentation().getTargetContext().getPackageManager().getPackageInfo("com.reactnativenavigation.playground", 0);
112
+        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
113
+    }
108 114
 }

+ 35
- 24
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java View File

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.e2e.androide2e;
2 2
 
3 3
 import android.support.test.uiautomator.By;
4
+import android.support.test.uiautomator.Until;
4 5
 
5 6
 import org.junit.Test;
6 7
 
@@ -8,30 +9,30 @@ import static org.assertj.core.api.Java6Assertions.assertThat;
8 9
 
9 10
 public class NavigationOptionsTest extends BaseTest {
10 11
 
11
-	@Test
12
-	public void declareNavigationStyleOnContainerComponent() throws Exception {
13
-		elementByText("PUSH OPTIONS SCREEN").click();
14
-		assertExists(By.text("Static Title"));
15
-	}
16
-
17
-	@Test
18
-	public void setTitleDynamically() throws Exception {
19
-		elementByText("PUSH OPTIONS SCREEN").click();
20
-		assertExists(By.text("Static Title"));
21
-		elementByText("DYNAMIC OPTIONS").click();
22
-		assertExists(By.text("Dynamic Title"));
23
-	}
24
-
25
-//	@Test
26
-//	public void testTopBarHidden() throws Exception {
27
-//		elementByText("PUSH OPTIONS SCREEN").click();
28
-//		int topWithNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top;
29
-//		elementByText("HIDE TOP BAR").click();
30
-//		int topWithoutNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top;
31
-//		assertThat(topWithoutNavigation).isLessThan(topWithNavigation);
32
-//		elementByText("SHOW TOP BAR").click();
33
-//		assertExists(By.text("Static Title"));
34
-//	}
12
+    @Test
13
+    public void declareNavigationStyleOnContainerComponent() throws Exception {
14
+        elementByText("PUSH OPTIONS SCREEN").click();
15
+        assertExists(By.text("Static Title"));
16
+    }
17
+
18
+    @Test
19
+    public void setTitleDynamically() throws Exception {
20
+        elementByText("PUSH OPTIONS SCREEN").click();
21
+        assertExists(By.text("Static Title"));
22
+        elementByText("DYNAMIC OPTIONS").click();
23
+        assertExists(By.text("Dynamic Title"));
24
+    }
25
+
26
+    @Test
27
+    public void testTopBarHidden() throws Exception {
28
+        elementByText("PUSH OPTIONS SCREEN").click();
29
+        int topWithNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top;
30
+        elementByText("HIDE TOP BAR").click();
31
+        int topWithoutNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top;
32
+        assertThat(topWithoutNavigation).isLessThan(topWithNavigation);
33
+        elementByText("SHOW TOP BAR").click();
34
+        assertExists(By.text("Static Title"));
35
+    }
35 36
 
36 37
     @Test
37 38
     public void testRightButtons() throws Exception {
@@ -39,4 +40,14 @@ public class NavigationOptionsTest extends BaseTest {
39 40
         assertExists(By.text("ONE"));
40 41
         elementByText("ONE").click();
41 42
     }
43
+
44
+    @Test
45
+    public void testTopBarCollapse() throws Exception {
46
+        elementByText("PUSH OPTIONS SCREEN").click();
47
+        elementByText("SCROLLVIEW SCREEN").click();
48
+        assertExists(By.text("Collapse"));
49
+        elementByText("TOGGLE TOP BAR HIDE ON SCROLL").click();
50
+        swipeUp();
51
+        assertThat(device().hasObject(By.text("Collapse"))).isFalse();
52
+    }
42 53
 }

+ 1
- 1
lib/android/app/build.gradle View File

@@ -70,5 +70,5 @@ dependencies {
70 70
     testImplementation 'org.robolectric:robolectric:3.5.1'
71 71
     testImplementation 'org.assertj:assertj-core:3.8.0'
72 72
     testImplementation 'com.squareup.assertj:assertj-android:1.1.1'
73
-    testImplementation 'org.mockito:mockito-core:2.12.0'
73
+    testImplementation 'org.mockito:mockito-core:2.13.0'
74 74
 }

+ 83
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java View File

@@ -0,0 +1,83 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+import android.animation.Animator;
4
+import android.animation.AnimatorListenerAdapter;
5
+import android.animation.AnimatorSet;
6
+import android.animation.ObjectAnimator;
7
+import android.animation.TimeInterpolator;
8
+import android.content.Context;
9
+import android.support.annotation.Nullable;
10
+import android.util.DisplayMetrics;
11
+import android.view.View;
12
+import android.view.ViewGroup;
13
+import android.view.WindowManager;
14
+import android.view.animation.AccelerateInterpolator;
15
+import android.view.animation.DecelerateInterpolator;
16
+
17
+import com.reactnativenavigation.utils.UiUtils;
18
+import com.reactnativenavigation.views.TopBar;
19
+
20
+@SuppressWarnings("ResourceType")
21
+public class NavigationAnimator {
22
+
23
+    public interface NavigationAnimationListener {
24
+        void onAnimationEnd();
25
+    }
26
+
27
+    private static final int DURATION = 300;
28
+    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
29
+    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
30
+    private float translationY;
31
+
32
+    public NavigationAnimator(Context context) {
33
+        translationY = UiUtils.getWindowHeight(context);
34
+    }
35
+
36
+    public void animatePush(final View view, @Nullable final NavigationAnimationListener animationListener) {
37
+        view.setVisibility(View.INVISIBLE);
38
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
39
+        alpha.setInterpolator(DECELERATE_INTERPOLATOR);
40
+
41
+        AnimatorSet set = new AnimatorSet();
42
+        set.addListener(new AnimatorListenerAdapter() {
43
+            @Override
44
+            public void onAnimationStart(Animator animation) {
45
+                view.setVisibility(View.VISIBLE);
46
+            }
47
+
48
+            @Override
49
+            public void onAnimationEnd(Animator animation) {
50
+                if (animationListener != null) {
51
+                    animationListener.onAnimationEnd();
52
+                }
53
+            }
54
+        });
55
+        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0);
56
+        translationY.setInterpolator(DECELERATE_INTERPOLATOR);
57
+        translationY.setDuration(DURATION);
58
+        alpha.setDuration(DURATION);
59
+        set.playTogether(translationY, alpha);
60
+        set.start();
61
+    }
62
+
63
+    public void animatePop(View view, @Nullable final NavigationAnimationListener animationListener) {
64
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
65
+        alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
66
+
67
+        AnimatorSet set = new AnimatorSet();
68
+        set.addListener(new AnimatorListenerAdapter() {
69
+            @Override
70
+            public void onAnimationEnd(Animator animation) {
71
+                if (animationListener != null) {
72
+                    animationListener.onAnimationEnd();
73
+                }
74
+            }
75
+        });
76
+        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY);
77
+        translationY.setInterpolator(ACCELERATE_INTERPOLATOR);
78
+        translationY.setDuration(DURATION);
79
+        alpha.setDuration(DURATION);
80
+        set.playTogether(translationY, alpha);
81
+        set.start();
82
+    }
83
+}

+ 0
- 210
lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java View File

@@ -1,210 +0,0 @@
1
-package com.reactnativenavigation.anim;
2
-
3
-import android.animation.Animator;
4
-import android.animation.AnimatorSet;
5
-import android.animation.ObjectAnimator;
6
-import android.animation.ValueAnimator;
7
-import android.content.Context;
8
-import android.support.annotation.Nullable;
9
-import android.util.DisplayMetrics;
10
-import android.view.View;
11
-import android.view.ViewGroup;
12
-import android.view.WindowManager;
13
-import android.view.animation.AccelerateInterpolator;
14
-import android.view.animation.DecelerateInterpolator;
15
-import android.widget.LinearLayout;
16
-
17
-import com.reactnativenavigation.views.TopBar;
18
-
19
-@SuppressWarnings("ResourceType")
20
-public class StackAnimator {
21
-
22
-	public interface StackAnimationListener {
23
-		void onAnimationEnd();
24
-	}
25
-
26
-	private static final int DURATION = 300;
27
-	private static final int DURATION_TOPBAR = 300;
28
-	private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
29
-	private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
30
-	private float translationY;
31
-
32
-	public StackAnimator(Context context) {
33
-		translationY = getWindowHeight(context);
34
-	}
35
-
36
-	public void animatePush(final View view, @Nullable final StackAnimationListener animationListener) {
37
-		view.setVisibility(View.INVISIBLE);
38
-		ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
39
-		alpha.setInterpolator(DECELERATE_INTERPOLATOR);
40
-
41
-		AnimatorSet set = new AnimatorSet();
42
-		set.addListener(new Animator.AnimatorListener() {
43
-			@Override
44
-			public void onAnimationStart(Animator animation) {
45
-				view.setVisibility(View.VISIBLE);
46
-			}
47
-
48
-			@Override
49
-			public void onAnimationEnd(Animator animation) {
50
-				if (animationListener != null) {
51
-					animationListener.onAnimationEnd();
52
-				}
53
-			}
54
-
55
-			@Override
56
-			public void onAnimationCancel(Animator animation) {
57
-
58
-			}
59
-
60
-			@Override
61
-			public void onAnimationRepeat(Animator animation) {
62
-
63
-			}
64
-		});
65
-		ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0);
66
-		translationY.setInterpolator(DECELERATE_INTERPOLATOR);
67
-		translationY.setDuration(DURATION);
68
-		alpha.setDuration(DURATION);
69
-		set.playTogether(translationY, alpha);
70
-		set.start();
71
-	}
72
-
73
-	public void animatePop(View view, @Nullable final StackAnimationListener animationListener) {
74
-		ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
75
-		alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
76
-
77
-		AnimatorSet set = new AnimatorSet();
78
-		set.addListener(new Animator.AnimatorListener() {
79
-			@Override
80
-			public void onAnimationStart(Animator animation) {
81
-
82
-			}
83
-
84
-			@Override
85
-			public void onAnimationEnd(Animator animation) {
86
-				if (animationListener != null) {
87
-					animationListener.onAnimationEnd();
88
-				}
89
-			}
90
-
91
-			@Override
92
-			public void onAnimationCancel(Animator animation) {
93
-
94
-			}
95
-
96
-			@Override
97
-			public void onAnimationRepeat(Animator animation) {
98
-
99
-			}
100
-		});
101
-		ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY);
102
-		translationY.setInterpolator(ACCELERATE_INTERPOLATOR);
103
-		translationY.setDuration(DURATION);
104
-		alpha.setDuration(DURATION);
105
-		set.playTogether(translationY, alpha);
106
-		set.start();
107
-	}
108
-
109
-	private float getWindowHeight(Context context) {
110
-		DisplayMetrics metrics = new DisplayMetrics();
111
-		WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
112
-		windowManager.getDefaultDisplay().getMetrics(metrics);
113
-		return metrics.heightPixels;
114
-	}
115
-
116
-	public void animateShowTopBar(final TopBar topBar, final View container) {
117
-		ValueAnimator containerHeightAnim = ValueAnimator.ofInt(container.getMeasuredHeight(), container.getMeasuredHeight() - topBar.getMeasuredHeight());
118
-		containerHeightAnim.setInterpolator(DECELERATE_INTERPOLATOR);
119
-		containerHeightAnim.setDuration(DURATION_TOPBAR);
120
-		containerHeightAnim.addUpdateListener(valueAnimator -> {
121
-            int val = (Integer) valueAnimator.getAnimatedValue();
122
-			ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
123
-            layoutParams.height = val;
124
-            container.setLayoutParams(layoutParams);
125
-        });
126
-		ObjectAnimator containerTransitionAnim = ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, -1 * topBar.getMeasuredHeight(), 0);
127
-		containerTransitionAnim.setInterpolator(DECELERATE_INTERPOLATOR);
128
-		containerTransitionAnim.setDuration(DURATION_TOPBAR);
129
-
130
-		ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, -1 * topBar.getHeight(), 0);
131
-		topbarAnim.setInterpolator(DECELERATE_INTERPOLATOR);
132
-		topbarAnim.setDuration(DURATION_TOPBAR);
133
-
134
-		AnimatorSet set = new AnimatorSet();
135
-		set.addListener(new Animator.AnimatorListener() {
136
-			@Override
137
-			public void onAnimationStart(Animator animation) {
138
-				topBar.setVisibility(View.VISIBLE);
139
-			}
140
-
141
-			@Override
142
-			public void onAnimationEnd(Animator animation) {
143
-                ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
144
-				layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
145
-				container.setLayoutParams(layoutParams);
146
-			}
147
-
148
-			@Override
149
-			public void onAnimationCancel(Animator animation) {
150
-
151
-			}
152
-
153
-			@Override
154
-			public void onAnimationRepeat(Animator animation) {
155
-
156
-			}
157
-		});
158
-		set.playTogether(containerHeightAnim, containerTransitionAnim, topbarAnim);
159
-		set.start();
160
-	}
161
-
162
-	public void animateHideTopBar(final TopBar topBar, final View container) {
163
-		ValueAnimator containerHeightAnim = ValueAnimator.ofInt(container.getMeasuredHeight(), container.getMeasuredHeight() + topBar.getMeasuredHeight());
164
-		containerHeightAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
165
-		containerHeightAnim.setDuration(DURATION_TOPBAR);
166
-		containerHeightAnim.addUpdateListener(valueAnimator -> {
167
-            int val = (Integer) valueAnimator.getAnimatedValue();
168
-			ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
169
-            layoutParams.height = val;
170
-            container.setLayoutParams(layoutParams);
171
-        });
172
-		ObjectAnimator containerTransitionAnim = ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, 0, -1 * topBar.getMeasuredHeight());
173
-		containerTransitionAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
174
-		containerTransitionAnim.setDuration(DURATION_TOPBAR);
175
-
176
-		ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, 0, -1 * topBar.getMeasuredHeight());
177
-		topbarAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
178
-		topbarAnim.setDuration(DURATION_TOPBAR);
179
-
180
-		AnimatorSet set = new AnimatorSet();
181
-		set.addListener(new Animator.AnimatorListener() {
182
-			@Override
183
-			public void onAnimationStart(Animator animation) {
184
-			}
185
-
186
-			@Override
187
-			public void onAnimationEnd(Animator animation) {
188
-                ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
189
-				layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
190
-				container.setLayoutParams(layoutParams);
191
-				container.setTranslationY(0);
192
-
193
-				topBar.setVisibility(View.GONE);
194
-				topBar.setTranslationY(0);
195
-			}
196
-
197
-			@Override
198
-			public void onAnimationCancel(Animator animation) {
199
-
200
-			}
201
-
202
-			@Override
203
-			public void onAnimationRepeat(Animator animation) {
204
-
205
-			}
206
-		});
207
-		set.playTogether(containerHeightAnim, containerTransitionAnim, topbarAnim);
208
-		set.start();
209
-	}
210
-}

+ 82
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java View File

@@ -0,0 +1,82 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.animation.Animator;
5
+import android.animation.AnimatorListenerAdapter;
6
+import android.animation.ObjectAnimator;
7
+import android.animation.TimeInterpolator;
8
+import android.view.View;
9
+import android.view.ViewGroup;
10
+import android.view.animation.AccelerateInterpolator;
11
+import android.view.animation.DecelerateInterpolator;
12
+
13
+import com.reactnativenavigation.views.TopBar;
14
+
15
+public class TopBarAnimator {
16
+
17
+    private static final int DURATION_TOPBAR = 300;
18
+    private DecelerateInterpolator decelerateInterpolator;
19
+    private AccelerateInterpolator accelerateInterpolator;
20
+
21
+    private TopBar topBar;
22
+    private View contentView;
23
+
24
+    public TopBarAnimator(TopBar topBar, View contentView) {
25
+        decelerateInterpolator = new DecelerateInterpolator();
26
+        accelerateInterpolator = new AccelerateInterpolator();
27
+        this.topBar = topBar;
28
+        this.contentView = contentView;
29
+    }
30
+
31
+    public void show() {
32
+        show(-1 * topBar.getMeasuredHeight(), decelerateInterpolator, DURATION_TOPBAR);
33
+    }
34
+
35
+    public void show(float startTranslation, TimeInterpolator interpolator, int duration) {
36
+        ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, 0);
37
+        topbarAnim.setInterpolator(interpolator);
38
+        topbarAnim.setDuration(duration);
39
+
40
+        topbarAnim.addListener(new AnimatorListenerAdapter() {
41
+
42
+            @Override
43
+            public void onAnimationStart(Animator animation) {
44
+                topBar.setVisibility(View.VISIBLE);
45
+            }
46
+
47
+            @Override
48
+            public void onAnimationEnd(Animator animation) {
49
+                if (contentView != null) {
50
+                    ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams();
51
+                    layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
52
+                    contentView.setLayoutParams(layoutParams);
53
+                }
54
+            }
55
+        });
56
+        topbarAnim.start();
57
+    }
58
+
59
+    public void hide() {
60
+        hide(0, accelerateInterpolator, DURATION_TOPBAR);
61
+    }
62
+
63
+    public void hide(float startTranslation, TimeInterpolator interpolator, int duration) {
64
+        ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
65
+        topbarAnim.setInterpolator(interpolator);
66
+        topbarAnim.setDuration(duration);
67
+
68
+        topbarAnim.addListener(new AnimatorListenerAdapter() {
69
+            @Override
70
+            public void onAnimationEnd(Animator animation) {
71
+                if (contentView != null) {
72
+                    ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams();
73
+                    layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
74
+                    contentView.setLayoutParams(layoutParams);
75
+                }
76
+
77
+                topBar.setVisibility(View.GONE);
78
+            }
79
+        });
80
+        topbarAnim.start();
81
+    }
82
+}

+ 93
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java View File

@@ -0,0 +1,93 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.view.View;
5
+
6
+import com.facebook.react.uimanager.events.EventDispatcher;
7
+import com.reactnativenavigation.interfaces.ScrollEventListener;
8
+import com.reactnativenavigation.utils.UiThread;
9
+import com.reactnativenavigation.views.TopBar;
10
+
11
+public class TopBarCollapseBehavior {
12
+    private TopBar topBar;
13
+
14
+    private EventDispatcher eventDispatcher;
15
+    private ScrollEventListener scrollEventListener;
16
+    private boolean dragStarted;
17
+    private TopBarAnimator animator;
18
+
19
+    public TopBarCollapseBehavior(EventDispatcher eventDispatcher, TopBar topBar) {
20
+        this.eventDispatcher = eventDispatcher;
21
+        this.topBar = topBar;
22
+        this.animator = new TopBarAnimator(topBar, null);
23
+    }
24
+
25
+    public void enableCollapse() {
26
+        scrollEventListener = (new ScrollEventListener(new ScrollEventListener.OnVerticalScrollListener() {
27
+            @Override
28
+            public void onVerticalScroll(int scrollY, int oldScrollY) {
29
+                if (scrollY < 0) return;
30
+                if (!dragStarted) return;
31
+
32
+                final int scrollDiff = calcScrollDiff(scrollY, oldScrollY, topBar.getMeasuredHeight());
33
+                final float nextTranslation = topBar.getTranslationY() - scrollDiff;
34
+                if (scrollDiff < 0) {
35
+                    down(topBar.getMeasuredHeight(), nextTranslation);
36
+                } else {
37
+                    up(topBar.getMeasuredHeight(), nextTranslation);
38
+                }
39
+            }
40
+
41
+            @Override
42
+            public void onDrag(boolean started, double velocity) {
43
+                dragStarted = started;
44
+                UiThread.post(() -> {
45
+                    if (!dragStarted) {
46
+                        if (velocity > 0) {
47
+                            animator.show(topBar.getTranslationY(), null, 100);
48
+                        } else {
49
+                            animator.hide(topBar.getTranslationY(), null, 100);
50
+                        }
51
+                    }
52
+                });
53
+            }
54
+        }));
55
+        if (eventDispatcher != null) {
56
+            eventDispatcher.addListener(scrollEventListener);
57
+        }
58
+    }
59
+
60
+    private void up(int measuredHeight, float nextTranslation) {
61
+        if (nextTranslation < -measuredHeight && topBar.getVisibility() == View.VISIBLE) {
62
+            topBar.setVisibility(View.GONE);
63
+            topBar.setTranslationY(-measuredHeight);
64
+        } else if (nextTranslation > -measuredHeight && nextTranslation <= 0) {
65
+            topBar.setTranslationY(nextTranslation);
66
+        }
67
+    }
68
+
69
+    private void down(int measuredHeight, float nextTranslation) {
70
+        if (topBar.getVisibility() == View.GONE && nextTranslation > -measuredHeight) {
71
+            topBar.setVisibility(View.VISIBLE);
72
+            topBar.setTranslationY(nextTranslation);
73
+        } else if (nextTranslation <= 0 && nextTranslation >= -measuredHeight) {
74
+            topBar.setTranslationY(nextTranslation);
75
+        }
76
+    }
77
+
78
+    private int calcScrollDiff(int scrollY, int oldScrollY, int measuredHeight) {
79
+        int diff = scrollY - oldScrollY;
80
+        if (Math.abs(diff) > measuredHeight) {
81
+            diff = (Math.abs(diff) / diff) * measuredHeight;
82
+        }
83
+        return diff;
84
+    }
85
+
86
+    public void disableCollapse() {
87
+        if (eventDispatcher != null) {
88
+            eventDispatcher.removeListener(scrollEventListener);
89
+        }
90
+        topBar.setVisibility(View.VISIBLE);
91
+        topBar.setTranslationY(0);
92
+    }
93
+}

+ 49
- 0
lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java View File

@@ -0,0 +1,49 @@
1
+package com.reactnativenavigation.interfaces;
2
+
3
+import com.facebook.react.uimanager.events.Event;
4
+import com.facebook.react.uimanager.events.EventDispatcherListener;
5
+import com.facebook.react.views.scroll.ScrollEvent;
6
+import com.reactnativenavigation.utils.ReflectionUtils;
7
+
8
+public class ScrollEventListener implements EventDispatcherListener {
9
+
10
+    private OnVerticalScrollListener verticalScrollListener;
11
+    private int prevScrollY = -1;
12
+
13
+    public interface OnVerticalScrollListener {
14
+        void onVerticalScroll(int scrollY, int oldScrollY);
15
+
16
+        void onDrag(boolean started, double velocity);
17
+    }
18
+
19
+    public ScrollEventListener(OnVerticalScrollListener verticalScrollListener) {
20
+        this.verticalScrollListener = verticalScrollListener;
21
+    }
22
+
23
+    @Override
24
+    public void onEventDispatch(Event event) {
25
+        if (event instanceof ScrollEvent) {
26
+            handleScrollEvent((ScrollEvent) event);
27
+        }
28
+    }
29
+
30
+    private void handleScrollEvent(ScrollEvent event) {
31
+        try {
32
+            if ("topScroll".equals(event.getEventName())) {
33
+                int scrollY = (int) ReflectionUtils.getDeclaredField(event, "mScrollY");
34
+                verticalScrollListener.onVerticalScroll(scrollY, prevScrollY);
35
+                if (scrollY != prevScrollY) {
36
+                    prevScrollY = scrollY;
37
+                }
38
+            } else if ("topScrollBeginDrag".equals(event.getEventName())) {
39
+                double velocity = (double) ReflectionUtils.getDeclaredField(event, "mYVelocity");
40
+                verticalScrollListener.onDrag(true, velocity);
41
+            } else if ("topScrollEndDrag".equals(event.getEventName())) {
42
+                double velocity = (double) ReflectionUtils.getDeclaredField(event, "mYVelocity");
43
+                verticalScrollListener.onDrag(false, velocity);
44
+            }
45
+        } catch (Exception e) {
46
+            e.printStackTrace();
47
+        }
48
+    }
49
+}

+ 62
- 46
lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java View File

@@ -13,52 +13,64 @@ import java.util.ArrayList;
13 13
 
14 14
 public class TopBarOptions implements DEFAULT_VALUES {
15 15
 
16
-	public static TopBarOptions parse(TypefaceLoader typefaceManager, JSONObject json) {
17
-		TopBarOptions options = new TopBarOptions();
18
-		if (json == null) return options;
16
+    public static TopBarOptions parse(TypefaceLoader typefaceManager, JSONObject json) {
17
+        TopBarOptions options = new TopBarOptions();
18
+        if (json == null) return options;
19 19
 
20
-		options.title = json.optString("title", NO_VALUE);
21
-		options.backgroundColor = json.optInt("backgroundColor", NO_COLOR_VALUE);
22
-		options.textColor = json.optInt("textColor", NO_COLOR_VALUE);
23
-		options.textFontSize = (float) json.optDouble("textFontSize", NO_FLOAT_VALUE);
24
-		options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", NO_VALUE));
25
-		options.hidden = NavigationOptions.BooleanOptions.parse(json.optString("hidden"));
26
-		options.animateHide = NavigationOptions.BooleanOptions.parse(json.optString("animateHide"));
27
-		options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
28
-		options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
20
+        options.title = json.optString("title", NO_VALUE);
21
+        options.backgroundColor = json.optInt("backgroundColor", NO_COLOR_VALUE);
22
+        options.textColor = json.optInt("textColor", NO_COLOR_VALUE);
23
+        options.textFontSize = (float) json.optDouble("textFontSize", NO_FLOAT_VALUE);
24
+        options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", NO_VALUE));
25
+        options.hidden = NavigationOptions.BooleanOptions.parse(json.optString("hidden"));
26
+        options.animateHide = NavigationOptions.BooleanOptions.parse(json.optString("animateHide"));
27
+        options.hideOnScroll = NavigationOptions.BooleanOptions.parse(json.optString("hideOnScroll"));
28
+        options.drawBehind = NavigationOptions.BooleanOptions.parse(json.optString("drawBehind"));
29
+        options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
30
+        options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
29 31
 
30
-		return options;
31
-	}
32
+        return options;
33
+    }
32 34
 
33
-	public String title = NO_VALUE;
34
-	@ColorInt public int backgroundColor = NO_COLOR_VALUE;
35
-	@ColorInt public int textColor = NO_COLOR_VALUE;
36
-	public float textFontSize = NO_FLOAT_VALUE;
37
-	@Nullable public Typeface textFontFamily;
38
-	public NavigationOptions.BooleanOptions hidden = NavigationOptions.BooleanOptions.False;
39
-	public NavigationOptions.BooleanOptions animateHide = NavigationOptions.BooleanOptions.False;
40
-	public ArrayList<Button> leftButtons;
41
-	public ArrayList<Button> rightButtons;
35
+    public String title = NO_VALUE;
36
+    @ColorInt public int backgroundColor = NO_COLOR_VALUE;
37
+    @ColorInt public int textColor = NO_COLOR_VALUE;
38
+    public float textFontSize = NO_FLOAT_VALUE;
39
+    @Nullable public Typeface textFontFamily;
40
+    public NavigationOptions.BooleanOptions hidden = NavigationOptions.BooleanOptions.NoValue;
41
+    public NavigationOptions.BooleanOptions animateHide = NavigationOptions.BooleanOptions.NoValue;
42
+    public NavigationOptions.BooleanOptions hideOnScroll = NavigationOptions.BooleanOptions.NoValue;
43
+    public NavigationOptions.BooleanOptions drawBehind = NavigationOptions.BooleanOptions.NoValue;
44
+    public ArrayList<Button> leftButtons;
45
+    public ArrayList<Button> rightButtons;
42 46
 
43
-	void mergeWith(final TopBarOptions other) {
44
-		if (!NO_VALUE.equals(other.title)) title = other.title;
45
-		if (other.backgroundColor != NO_COLOR_VALUE)
46
-			backgroundColor = other.backgroundColor;
47
-		if (other.textColor != NO_COLOR_VALUE)
48
-			textColor = other.textColor;
49
-		if (other.textFontSize != NO_FLOAT_VALUE)
50
-			textFontSize = other.textFontSize;
51
-		if (other.textFontFamily != null)
52
-			textFontFamily = other.textFontFamily;
53
-		if (other.hidden != NavigationOptions.BooleanOptions.NoValue)
54
-			hidden = other.hidden;
55
-		if (other.animateHide != NavigationOptions.BooleanOptions.NoValue)
56
-			animateHide = other.animateHide;
57
-		if(other.leftButtons != null)
58
-			leftButtons = other.leftButtons;
59
-		if(other.rightButtons != null)
60
-			rightButtons = other.rightButtons;
61
-	}
47
+    void mergeWith(final TopBarOptions other) {
48
+        if (!NO_VALUE.equals(other.title)) title = other.title;
49
+        if (other.backgroundColor != NO_COLOR_VALUE)
50
+            backgroundColor = other.backgroundColor;
51
+        if (other.textColor != NO_COLOR_VALUE)
52
+            textColor = other.textColor;
53
+        if (other.textFontSize != NO_FLOAT_VALUE)
54
+            textFontSize = other.textFontSize;
55
+        if (other.textFontFamily != null)
56
+            textFontFamily = other.textFontFamily;
57
+        if (other.hidden != NavigationOptions.BooleanOptions.NoValue) {
58
+            hidden = other.hidden;
59
+        }
60
+        if (other.animateHide != NavigationOptions.BooleanOptions.NoValue) {
61
+            animateHide = other.animateHide;
62
+        }
63
+        if (other.hideOnScroll != NavigationOptions.BooleanOptions.NoValue) {
64
+            hideOnScroll = other.hideOnScroll;
65
+        }
66
+        if (other.drawBehind != NavigationOptions.BooleanOptions.NoValue) {
67
+            drawBehind = other.drawBehind;
68
+        }
69
+        if (other.leftButtons != null)
70
+            leftButtons = other.leftButtons;
71
+        if (other.rightButtons != null)
72
+            rightButtons = other.rightButtons;
73
+    }
62 74
 
63 75
     void mergeWithDefault(TopBarOptions defaultOptions) {
64 76
         if (NO_VALUE.equals(title))
@@ -75,9 +87,13 @@ public class TopBarOptions implements DEFAULT_VALUES {
75 87
             hidden = defaultOptions.hidden;
76 88
         if (animateHide == NavigationOptions.BooleanOptions.NoValue)
77 89
             animateHide = defaultOptions.animateHide;
78
-		if(leftButtons == null)
79
-			leftButtons = defaultOptions.leftButtons;
80
-		if(rightButtons == null)
81
-			rightButtons = defaultOptions.rightButtons;
90
+        if (hideOnScroll == NavigationOptions.BooleanOptions.NoValue)
91
+            hideOnScroll = defaultOptions.hideOnScroll;
92
+        if (drawBehind == NavigationOptions.BooleanOptions.NoValue)
93
+            drawBehind = defaultOptions.drawBehind;
94
+        if (leftButtons == null)
95
+            leftButtons = defaultOptions.leftButtons;
96
+        if (rightButtons == null)
97
+            rightButtons = defaultOptions.rightButtons;
82 98
     }
83 99
 }

+ 21
- 33
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java View File

@@ -1,27 +1,26 @@
1 1
 package com.reactnativenavigation.presentation;
2 2
 
3
-import android.view.View;
4
-
5
-import com.reactnativenavigation.anim.StackAnimator;
6 3
 import com.reactnativenavigation.parse.Button;
7 4
 import com.reactnativenavigation.parse.NavigationOptions;
8 5
 import com.reactnativenavigation.parse.TopBarOptions;
9 6
 import com.reactnativenavigation.parse.TopTabOptions;
10 7
 import com.reactnativenavigation.parse.TopTabsOptions;
8
+import com.reactnativenavigation.views.Container;
11 9
 import com.reactnativenavigation.views.TopBar;
12 10
 
13 11
 import java.util.ArrayList;
14 12
 
13
+import static com.reactnativenavigation.parse.NavigationOptions.BooleanOptions.False;
14
+import static com.reactnativenavigation.parse.NavigationOptions.BooleanOptions.True;
15
+
15 16
 public class OptionsPresenter {
16 17
 
17
-	private final StackAnimator animator;
18
-    private View contentView;
18
+    private Container reactContainer;
19 19
     private TopBar topBar;
20 20
 
21
-    public OptionsPresenter(TopBar topBar, View contentView) {
22
-        this.topBar = topBar;
23
-        this.contentView = contentView;
24
-        animator = new StackAnimator(topBar.getContext());
21
+    public OptionsPresenter(Container reactContainer) {
22
+        this.reactContainer = reactContainer;
23
+        this.topBar = reactContainer.getTopBar();
25 24
     }
26 25
 
27 26
     public void applyOptions(NavigationOptions options) {
@@ -39,34 +38,23 @@ public class OptionsPresenter {
39 38
 
40 39
         topBar.setTitleTypeface(options.textFontFamily);
41 40
         if (options.hidden == NavigationOptions.BooleanOptions.True) {
42
-            hideTopBar(options.animateHide);
41
+            topBar.hide(options.animateHide);
43 42
         }
44 43
         if (options.hidden == NavigationOptions.BooleanOptions.False) {
45
-            showTopBar(options.animateHide);
44
+            topBar.show(options.animateHide);
45
+        }
46
+        if (options.drawBehind == True) {
47
+            reactContainer.drawBehindTopBar();
48
+        } else if (options.drawBehind == False) {
49
+            reactContainer.drawBelowTopBar();
46 50
         }
47
-    }
48
-
49
-	private void showTopBar(NavigationOptions.BooleanOptions animated) {
50
-		if (topBar.getVisibility() == View.VISIBLE) {
51
-			return;
52
-		}
53
-		if (animated == NavigationOptions.BooleanOptions.True) {
54
-			animator.animateShowTopBar(topBar, contentView);
55
-		} else {
56
-			topBar.setVisibility(View.VISIBLE);
57
-		}
58
-	}
59 51
 
60
-	private void hideTopBar(NavigationOptions.BooleanOptions animated) {
61
-		if (topBar.getVisibility() == View.GONE) {
62
-			return;
63
-		}
64
-		if (animated == NavigationOptions.BooleanOptions.True) {
65
-			animator.animateHideTopBar(topBar, contentView);
66
-		} else {
67
-			topBar.setVisibility(View.GONE);
68
-		}
69
-	}
52
+        if (options.hideOnScroll == True) {
53
+            topBar.enableCollapse();
54
+        } else if (options.hideOnScroll == False) {
55
+            topBar.disableCollapse();
56
+        }
57
+    }
70 58
 
71 59
     private void applyButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
72 60
         topBar.setButtons(leftButtons, rightButtons);

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/UiUtils.java View File

@@ -1,12 +1,15 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3
+import android.content.Context;
3 4
 import android.graphics.PorterDuff;
4 5
 import android.graphics.PorterDuffColorFilter;
5 6
 import android.graphics.drawable.Drawable;
6 7
 import android.os.Handler;
7 8
 import android.os.Looper;
9
+import android.util.DisplayMetrics;
8 10
 import android.view.View;
9 11
 import android.view.ViewTreeObserver;
12
+import android.view.WindowManager;
10 13
 
11 14
 public class UiUtils {
12 15
 	public static void runOnPreDrawOnce(final View view, final Runnable task) {
@@ -27,4 +30,13 @@ public class UiUtils {
27 30
 	public static void runOnMainThread(Runnable runnable) {
28 31
 		new Handler(Looper.getMainLooper()).post(runnable);
29 32
 	}
33
+
34
+	public static float getWindowHeight(Context context) {
35
+		DisplayMetrics metrics = new DisplayMetrics();
36
+		WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
37
+		if (windowManager != null) {
38
+			windowManager.getDefaultDisplay().getMetrics(metrics);
39
+		}
40
+		return metrics.heightPixels;
41
+	}
30 42
 }

+ 67
- 61
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ContainerViewController.java View File

@@ -3,6 +3,7 @@ package com.reactnativenavigation.viewcontrollers;
3 3
 import android.app.Activity;
4 4
 import android.support.annotation.NonNull;
5 5
 import android.support.annotation.RestrictTo;
6
+import android.util.Log;
6 7
 import android.view.View;
7 8
 
8 9
 import com.reactnativenavigation.parse.NavigationOptions;
@@ -15,85 +16,90 @@ public class ContainerViewController extends ViewController implements Navigatio
15 16
     public interface ReactViewCreator {
16 17
 
17 18
         IReactView create(Activity activity, String containerId, String containerName);
18
-	}
19
+    }
19 20
 
20
-	public interface IReactView {
21
+    public interface IReactView {
21 22
 
22
-		boolean isReady();
23
+        boolean isReady();
23 24
 
24
-		View asView();
25
+        View asView();
25 26
 
26
-		void destroy();
27
+        void destroy();
27 28
 
28
-		void sendContainerStart();
29
+        void sendContainerStart();
29 30
 
30
-		void sendContainerStop();
31
+        void sendContainerStop();
31 32
 
32 33
         void sendOnNavigationButtonPressed(String buttonId);
33 34
     }
34 35
 
35
-	private final String containerName;
36
-
37
-	private final ReactViewCreator viewCreator;
38
-	private NavigationOptions navigationOptions;
39
-	private ReactContainer container;
40
-
41
-	public ContainerViewController(final Activity activity,
42
-								   final String id,
43
-								   final String containerName,
44
-								   final ReactViewCreator viewCreator,
45
-								   final NavigationOptions initialNavigationOptions) {
46
-		super(activity, id);
47
-		this.containerName = containerName;
48
-		this.viewCreator = viewCreator;
49
-		this.navigationOptions = initialNavigationOptions;
50
-	}
36
+    private final String containerName;
37
+
38
+    private final ReactViewCreator viewCreator;
39
+    private NavigationOptions navigationOptions;
40
+    private ReactContainer container;
41
+
42
+    public ContainerViewController(final Activity activity,
43
+                                   final String id,
44
+                                   final String containerName,
45
+                                   final ReactViewCreator viewCreator,
46
+                                   final NavigationOptions initialNavigationOptions) {
47
+        super(activity, id);
48
+        this.containerName = containerName;
49
+        this.viewCreator = viewCreator;
50
+        this.navigationOptions = initialNavigationOptions;
51
+    }
51 52
 
52 53
     @RestrictTo(RestrictTo.Scope.TESTS)
53 54
     TopBar getTopBar() {
54 55
         return container.getTopBar();
55 56
     }
56 57
 
57
-	@Override
58
-	public void destroy() {
59
-		super.destroy();
60
-		if (container != null) container.destroy();
61
-		container = null;
62
-	}
63
-
64
-	@Override
65
-	public void onViewAppeared() {
66
-		super.onViewAppeared();
67
-		ensureViewIsCreated();
68
-		container.applyOptions(navigationOptions);
69
-		container.sendContainerStart();
70
-	}
71
-
72
-	@Override
73
-	public void onViewDisappear() {
74
-		super.onViewDisappear();
75
-		container.sendContainerStop();
76
-	}
77
-
78
-	@Override
79
-	protected boolean isViewShown() {
80
-		return super.isViewShown() && container.isReady();
81
-	}
82
-
83
-	@NonNull
84
-	@Override
85
-	protected View createView() {
86
-		container = (ReactContainer) viewCreator.create(getActivity(), getId(), containerName);
58
+    @RestrictTo(RestrictTo.Scope.TESTS)
59
+    ReactContainer getContainer() {
60
+        return container;
61
+    }
62
+
63
+    @Override
64
+    public void destroy() {
65
+        super.destroy();
66
+        if (container != null) container.destroy();
67
+        container = null;
68
+    }
69
+
70
+    @Override
71
+    public void onViewAppeared() {
72
+        super.onViewAppeared();
73
+        ensureViewIsCreated();
74
+        container.applyOptions(navigationOptions);
75
+        container.sendContainerStart();
76
+    }
77
+
78
+    @Override
79
+    public void onViewDisappear() {
80
+        super.onViewDisappear();
81
+        container.sendContainerStop();
82
+    }
83
+
84
+    @Override
85
+    protected boolean isViewShown() {
86
+        return super.isViewShown() && container.isReady();
87
+    }
88
+
89
+    @NonNull
90
+    @Override
91
+    protected View createView() {
92
+        container = (ReactContainer) viewCreator.create(getActivity(), getId(), containerName);
87 93
         return container.asView();
88
-	}
94
+    }
89 95
 
90
-	@Override
91
-	public void mergeNavigationOptions(NavigationOptions options) {
92
-		navigationOptions.mergeWith(options);
96
+    @Override
97
+    public void mergeNavigationOptions(NavigationOptions options) {
98
+        navigationOptions.mergeWith(options);
93 99
         container.applyOptions(navigationOptions);
94
-	}
100
+    }
95 101
 
96
-	NavigationOptions getNavigationOptions() {
97
-		return navigationOptions;
98
-	}
102
+    NavigationOptions getNavigationOptions() {
103
+        return navigationOptions;
104
+    }
99 105
 }

+ 6
- 6
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java View File

@@ -7,7 +7,7 @@ import android.view.ViewGroup;
7 7
 import android.widget.FrameLayout;
8 8
 
9 9
 import com.facebook.react.bridge.Promise;
10
-import com.reactnativenavigation.anim.StackAnimator;
10
+import com.reactnativenavigation.anim.NavigationAnimator;
11 11
 
12 12
 import java.util.Collection;
13 13
 import java.util.Iterator;
@@ -15,13 +15,13 @@ import java.util.Iterator;
15 15
 public class StackController extends ParentController {
16 16
 
17 17
 	private final IdStack<ViewController> stack = new IdStack<>();
18
-	private final StackAnimator animator;
18
+	private final NavigationAnimator animator;
19 19
 
20 20
 	public StackController(final Activity activity, String id) {
21
-		this(activity, id, new StackAnimator(activity));
21
+		this(activity, id, new NavigationAnimator(activity));
22 22
 	}
23 23
 
24
-	public StackController(final Activity activity, String id, StackAnimator animator) {
24
+	public StackController(final Activity activity, String id, NavigationAnimator animator) {
25 25
 		super(activity, id);
26 26
 		this.animator = animator;
27 27
 	}
@@ -40,7 +40,7 @@ public class StackController extends ParentController {
40 40
 
41 41
 		//TODO animatePush only when needed
42 42
 		if (previousTop != null) {
43
-			animator.animatePush(enteringView, new StackAnimator.StackAnimationListener() {
43
+			animator.animatePush(enteringView, new NavigationAnimator.NavigationAnimationListener() {
44 44
 				@Override
45 45
 				public void onAnimationEnd() {
46 46
 					getView().removeView(previousTop.getView());
@@ -80,7 +80,7 @@ public class StackController extends ParentController {
80 80
 		getView().addView(enteringView, getView().getChildCount() - 1);
81 81
 
82 82
 		if (animate) {
83
-			animator.animatePop(exitingView, new StackAnimator.StackAnimationListener() {
83
+			animator.animatePop(exitingView, new NavigationAnimator.NavigationAnimationListener() {
84 84
 				@Override
85 85
 				public void onAnimationEnd() {
86 86
 					finishPopping(exitingView, poppedTop, promise);

+ 8
- 1
lib/android/app/src/main/java/com/reactnativenavigation/views/Container.java View File

@@ -1,14 +1,21 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3 3
 import android.support.annotation.RestrictTo;
4
+import android.view.View;
4 5
 
5 6
 import com.reactnativenavigation.parse.NavigationOptions;
7
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
6 8
 
7 9
 public interface Container {
8 10
     void applyOptions(NavigationOptions options);
9 11
 
10 12
     void sendOnNavigationButtonPressed(String id);
11 13
 
12
-    @RestrictTo(RestrictTo.Scope.TESTS)
13 14
     TopBar getTopBar();
15
+
16
+    View getContentView();
17
+
18
+    void drawBehindTopBar();
19
+
20
+    void drawBelowTopBar();
14 21
 }

+ 73
- 45
lib/android/app/src/main/java/com/reactnativenavigation/views/ContainerLayout.java View File

@@ -2,73 +2,101 @@ package com.reactnativenavigation.views;
2 2
 
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5
+import android.os.Build;
5 6
 import android.support.annotation.RestrictTo;
6 7
 import android.view.View;
7
-import android.widget.LinearLayout;
8
+import android.view.ViewGroup;
9
+import android.widget.RelativeLayout;
8 10
 
11
+import com.facebook.react.uimanager.events.EventDispatcher;
9 12
 import com.reactnativenavigation.parse.NavigationOptions;
10 13
 import com.reactnativenavigation.presentation.OptionsPresenter;
14
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
11 15
 import com.reactnativenavigation.viewcontrollers.ContainerViewController.IReactView;
12 16
 
17
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
18
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
19
+
13 20
 @SuppressLint("ViewConstructor")
14
-public class ContainerLayout extends LinearLayout implements ReactContainer {
21
+public class ContainerLayout extends RelativeLayout implements ReactContainer {
22
+
23
+    private TopBar topBar;
24
+    private IReactView reactView;
25
+    private final OptionsPresenter optionsPresenter;
15 26
 
16
-	private TopBar topBar;
17
-	private IReactView reactView;
18
-	private final OptionsPresenter optionsPresenter;
27
+    public ContainerLayout(Context context, IReactView reactView, EventDispatcher eventDispatcher) {
28
+        super(context);
29
+        this.reactView = reactView;
30
+        this.topBar = new TopBar(context, this, eventDispatcher);
31
+        topBar.setId(View.generateViewId());
19 32
 
20
-	public ContainerLayout(Context context, IReactView reactView) {
21
-		super(context);
22
-		this.topBar = new TopBar(context, this);
23
-		this.reactView = reactView;
24
-        optionsPresenter = new OptionsPresenter(topBar, reactView.asView());
33
+        optionsPresenter = new OptionsPresenter(this);
25 34
         initViews();
26
-	}
27
-
28
-	private void initViews() {
29
-	    setOrientation(VERTICAL);
30
-		addView(topBar);
31
-		addView(reactView.asView());
32
-	}
33
-
34
-	@Override
35
-	public boolean isReady() {
36
-		return reactView.isReady();
37
-	}
38
-
39
-	@Override
40
-	public View asView() {
41
-		return this;
42
-	}
43
-
44
-	@Override
45
-	public void destroy() {
46
-		reactView.destroy();
47
-	}
48
-
49
-	@Override
50
-	public void sendContainerStart() {
51
-		reactView.sendContainerStart();
52
-	}
53
-
54
-	@Override
55
-	public void sendContainerStop() {
56
-		reactView.sendContainerStop();
57
-	}
35
+    }
36
+
37
+    private void initViews() {
38
+        LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
39
+        layoutParams.addRule(BELOW, topBar.getId());
40
+        addView(reactView.asView(), layoutParams);
41
+        addView(topBar, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
42
+    }
43
+
44
+    @Override
45
+    public boolean isReady() {
46
+        return reactView.isReady();
47
+    }
48
+
49
+    @Override
50
+    public View asView() {
51
+        return this;
52
+    }
53
+
54
+    @Override
55
+    public void destroy() {
56
+        reactView.destroy();
57
+    }
58
+
59
+    @Override
60
+    public void sendContainerStart() {
61
+        reactView.sendContainerStart();
62
+    }
63
+
64
+    @Override
65
+    public void sendContainerStop() {
66
+        reactView.sendContainerStop();
67
+    }
58 68
 
59 69
     @Override
60 70
     public void applyOptions(NavigationOptions options) {
61 71
         optionsPresenter.applyOptions(options);
62 72
     }
63 73
 
64
-	@Override
74
+    @Override
65 75
     public void sendOnNavigationButtonPressed(String buttonId) {
66 76
         reactView.sendOnNavigationButtonPressed(buttonId);
67 77
     }
68 78
 
69 79
     @Override
70
-    @RestrictTo(RestrictTo.Scope.TESTS)
71 80
     public TopBar getTopBar() {
72 81
         return topBar;
73 82
     }
74
-}
83
+
84
+    @Override
85
+    public View getContentView() {
86
+        return reactView.asView();
87
+    }
88
+
89
+    @Override
90
+    public void drawBehindTopBar() {
91
+        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) reactView.asView().getLayoutParams();
92
+        layoutParams.removeRule(BELOW);
93
+        reactView.asView().setLayoutParams(layoutParams);
94
+    }
95
+
96
+    @Override
97
+    public void drawBelowTopBar() {
98
+        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) reactView.asView().getLayoutParams();
99
+        layoutParams.addRule(BELOW, topBar.getId());
100
+        reactView.asView().setLayoutParams(layoutParams);
101
+    }
102
+}

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

@@ -3,6 +3,8 @@ package com.reactnativenavigation.views;
3 3
 import android.app.Activity;
4 4
 
5 5
 import com.facebook.react.ReactInstanceManager;
6
+import com.facebook.react.uimanager.UIManagerModule;
7
+import com.reactnativenavigation.react.NavigationModule;
6 8
 import com.reactnativenavigation.react.ReactContainerViewCreator;
7 9
 import com.reactnativenavigation.viewcontrollers.ContainerViewController;
8 10
 
@@ -17,6 +19,6 @@ public class ContainerViewCreator implements ContainerViewController.ReactViewCr
17 19
 	@Override
18 20
 	public ContainerViewController.IReactView create(Activity activity, String containerId, String containerName) {
19 21
         ContainerViewController.IReactView reactView = new ReactContainerViewCreator(instanceManager).create(activity, containerId, containerName);
20
-        return new ContainerLayout(activity, reactView);
22
+        return new ContainerLayout(activity, reactView, instanceManager.getCurrentReactContext().getNativeModule(UIManagerModule.class).getEventDispatcher());
21 23
 	}
22 24
 }

+ 140
- 92
lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java View File

@@ -13,52 +13,69 @@ import android.view.View;
13 13
 import android.view.ViewGroup;
14 14
 import android.widget.TextView;
15 15
 
16
+import com.facebook.react.uimanager.events.EventDispatcher;
17
+import com.reactnativenavigation.anim.TopBarAnimator;
18
+import com.reactnativenavigation.anim.TopBarCollapseBehavior;
16 19
 import com.reactnativenavigation.parse.Button;
20
+import com.reactnativenavigation.parse.Color;
21
+import com.reactnativenavigation.parse.NavigationOptions;
17 22
 import com.reactnativenavigation.parse.Number;
18 23
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
19
-import com.reactnativenavigation.parse.Color;
20 24
 
21 25
 import java.util.ArrayList;
22 26
 
23 27
 @SuppressLint("ViewConstructor")
24 28
 public class TopBar extends AppBarLayout {
25
-	private final Toolbar titleBar;
29
+    private final Toolbar titleBar;
26 30
     private Container container;
27 31
     private TopTabs topTabs;
28 32
 
29
-    public TopBar(final Context context, Container container) {
33
+    private TopBarAnimator animator;
34
+    private TopBarCollapseBehavior collapsingBehavior;
35
+
36
+    public TopBar(Context context) {
37
+        this(context, null, null);
38
+    }
39
+
40
+    public TopBar(Context context, Container container) {
41
+        this(context, container, null);
42
+    }
43
+
44
+    public TopBar(Context context, Container container, EventDispatcher eventDispatcher) {
30 45
         super(context);
46
+        collapsingBehavior = new TopBarCollapseBehavior(eventDispatcher, this);
31 47
         this.container = container;
32 48
         titleBar = new Toolbar(context);
33 49
         topTabs = new TopTabs(getContext());
50
+        animator = new TopBarAnimator(this, container != null ? container.getContentView() : null);
34 51
         addView(titleBar);
35 52
     }
36 53
 
37 54
     public void setTitle(String title) {
38
-		titleBar.setTitle(title);
39
-	}
40
-
41
-	public String getTitle() {
42
-		return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
43
-	}
44
-
45
-	public void setTitleTextColor(@ColorInt int color) {
46
-		titleBar.setTitleTextColor(color);
47
-	}
48
-
49
-	public void setTitleFontSize(float size) {
50
-		TextView titleTextView = getTitleTextView();
51
-		if (titleTextView != null) {
52
-			titleTextView.setTextSize(size);
53
-		}
54
-	}
55
-
56
-	public void setTitleTypeface(Typeface typeface) {
57
-		TextView titleTextView = getTitleTextView();
58
-		if (titleTextView != null) {
59
-			titleTextView.setTypeface(typeface);
60
-		}
61
-	}
55
+        titleBar.setTitle(title);
56
+    }
57
+
58
+    public String getTitle() {
59
+        return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
60
+    }
61
+
62
+    public void setTitleTextColor(@ColorInt int color) {
63
+        titleBar.setTitleTextColor(color);
64
+    }
65
+
66
+    public void setTitleFontSize(float size) {
67
+        TextView titleTextView = getTitleTextView();
68
+        if (titleTextView != null) {
69
+            titleTextView.setTextSize(size);
70
+        }
71
+    }
72
+
73
+    public void setTitleTypeface(Typeface typeface) {
74
+        TextView titleTextView = getTitleTextView();
75
+        if (titleTextView != null) {
76
+            titleTextView.setTypeface(typeface);
77
+        }
78
+    }
62 79
 
63 80
     public void setTopTabFontFamily(int tabIndex, Typeface fontFamily) {
64 81
         topTabs.setFontFamily(tabIndex, fontFamily);
@@ -72,71 +89,71 @@ public class TopBar extends AppBarLayout {
72 89
         topTabs.applyTopTabsFontSize(fontSize);
73 90
     }
74 91
 
75
-	public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
76
-		setLeftButtons(leftButtons);
77
-		setRightButtons(rightButtons);
78
-    }
79
-
80
-	public TextView getTitleTextView() {
81
-		return findTextView(titleBar);
82
-	}
83
-
84
-	@Override
85
-	public void setBackgroundColor(@ColorInt int color) {
86
-		titleBar.setBackgroundColor(color);
87
-	}
88
-
89
-	@Nullable
90
-	private TextView findTextView(ViewGroup root) {
91
-		for (int i = 0; i < root.getChildCount(); i++) {
92
-			View view = root.getChildAt(i);
93
-			if (view instanceof TextView) {
94
-				return (TextView) view;
95
-			}
96
-			if (view instanceof ViewGroup) {
97
-				return findTextView((ViewGroup) view);
98
-			}
99
-		}
100
-		return null;
101
-	}
102
-
103
-	private void setLeftButtons(ArrayList<Button> leftButtons) {
104
-		if(leftButtons == null || leftButtons.isEmpty()) {
105
-			titleBar.setNavigationIcon(null);
106
-			return;
107
-		}
108
-
109
-		if(leftButtons.size() > 1) {
110
-			Log.w("RNN", "Use a custom TopBar to have more than one left button");
111
-		}
112
-
113
-		Button leftButton = leftButtons.get(0);
114
-		setLeftButton(leftButton);
115
-	}
116
-
117
-	private void setLeftButton(final Button button) {
118
-		TitleBarButton leftBarButton = new TitleBarButton(container, this.titleBar, button);
119
-		leftBarButton.applyNavigationIcon(getContext());
120
-	}
121
-
122
-	private void setRightButtons(ArrayList<Button> rightButtons) {
123
-		if(rightButtons == null || rightButtons.size() == 0) {
124
-			return;
125
-		}
126
-
127
-		Menu menu = getTitleBar().getMenu();
128
-		menu.clear();
129
-
130
-		for (int i = 0; i < rightButtons.size(); i++){
131
-	   		Button button = rightButtons.get(i);
132
-			TitleBarButton titleBarButton = new TitleBarButton(container, this.titleBar, button);
133
-			titleBarButton.addToMenu(getContext(), menu);
134
-       }
135
-    }
136
-
137
-	public Toolbar getTitleBar() {
138
-		return titleBar;
139
-	}
92
+    public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
93
+        setLeftButtons(leftButtons);
94
+        setRightButtons(rightButtons);
95
+    }
96
+
97
+    public TextView getTitleTextView() {
98
+        return findTextView(titleBar);
99
+    }
100
+
101
+    @Override
102
+    public void setBackgroundColor(@ColorInt int color) {
103
+        titleBar.setBackgroundColor(color);
104
+    }
105
+
106
+    @Nullable
107
+    private TextView findTextView(ViewGroup root) {
108
+        for (int i = 0; i < root.getChildCount(); i++) {
109
+            View view = root.getChildAt(i);
110
+            if (view instanceof TextView) {
111
+                return (TextView) view;
112
+            }
113
+            if (view instanceof ViewGroup) {
114
+                return findTextView((ViewGroup) view);
115
+            }
116
+        }
117
+        return null;
118
+    }
119
+
120
+    private void setLeftButtons(ArrayList<Button> leftButtons) {
121
+        if (leftButtons == null || leftButtons.isEmpty()) {
122
+            titleBar.setNavigationIcon(null);
123
+            return;
124
+        }
125
+
126
+        if (leftButtons.size() > 1) {
127
+            Log.w("RNN", "Use a custom TopBar to have more than one left button");
128
+        }
129
+
130
+        Button leftButton = leftButtons.get(0);
131
+        setLeftButton(leftButton);
132
+    }
133
+
134
+    private void setLeftButton(final Button button) {
135
+        TitleBarButton leftBarButton = new TitleBarButton(container, this.titleBar, button);
136
+        leftBarButton.applyNavigationIcon(getContext());
137
+    }
138
+
139
+    private void setRightButtons(ArrayList<Button> rightButtons) {
140
+        if (rightButtons == null || rightButtons.size() == 0) {
141
+            return;
142
+        }
143
+
144
+        Menu menu = getTitleBar().getMenu();
145
+        menu.clear();
146
+
147
+        for (int i = 0; i < rightButtons.size(); i++) {
148
+            Button button = rightButtons.get(i);
149
+            TitleBarButton titleBarButton = new TitleBarButton(container, this.titleBar, button);
150
+            titleBarButton.addToMenu(getContext(), menu);
151
+        }
152
+    }
153
+
154
+    public Toolbar getTitleBar() {
155
+        return titleBar;
156
+    }
140 157
 
141 158
     public void setupTopTabsWithViewPager(TopTabsViewPager viewPager) {
142 159
         initTopTabs();
@@ -144,6 +161,37 @@ public class TopBar extends AppBarLayout {
144 161
     }
145 162
 
146 163
     private void initTopTabs() {
164
+        topTabs = new TopTabs(getContext());
147 165
         addView(topTabs);
148 166
     }
167
+
168
+    public void enableCollapse() {
169
+        collapsingBehavior.enableCollapse();
170
+    }
171
+
172
+    public void disableCollapse() {
173
+        collapsingBehavior.disableCollapse();
174
+    }
175
+
176
+    public void show(NavigationOptions.BooleanOptions animated) {
177
+        if (getVisibility() == View.VISIBLE) {
178
+            return;
179
+        }
180
+        if (animated == NavigationOptions.BooleanOptions.True) {
181
+            animator.show();
182
+        } else {
183
+            setVisibility(View.VISIBLE);
184
+        }
185
+    }
186
+
187
+    public void hide(NavigationOptions.BooleanOptions animated) {
188
+        if (getVisibility() == View.GONE) {
189
+            return;
190
+        }
191
+        if (animated == NavigationOptions.BooleanOptions.True) {
192
+            animator.hide();
193
+        } else {
194
+            setVisibility(View.GONE);
195
+        }
196
+    }
149 197
 }

+ 33
- 6
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java View File

@@ -4,10 +4,14 @@ import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.support.annotation.RestrictTo;
6 6
 import android.support.v4.view.ViewPager;
7
+import android.view.View;
8
+import android.view.ViewGroup;
7 9
 import android.widget.LinearLayout;
10
+import android.widget.RelativeLayout;
8 11
 
9 12
 import com.reactnativenavigation.parse.NavigationOptions;
10 13
 import com.reactnativenavigation.presentation.OptionsPresenter;
14
+import com.reactnativenavigation.utils.Task;
11 15
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabController;
12 16
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsAdapter;
13 17
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
@@ -15,24 +19,28 @@ import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
15 19
 import java.util.List;
16 20
 
17 21
 @SuppressLint("ViewConstructor")
18
-public class TopTabsLayout extends LinearLayout implements Container {
22
+public class TopTabsLayout extends RelativeLayout implements Container {
19 23
 
20 24
     private TopBar topBar;
25
+    private List<TopTabController> tabs;
21 26
     private TopTabsViewPager viewPager;
22 27
     private final OptionsPresenter optionsPresenter;
23 28
 
24 29
     public TopTabsLayout(Context context, List<TopTabController> tabs, TopTabsAdapter adapter) {
25 30
         super(context);
31
+        this.tabs = tabs;
26 32
         topBar = new TopBar(context, this);
33
+        topBar.setId(View.generateViewId());
27 34
         viewPager = new TopTabsViewPager(context, tabs, adapter);
28
-        optionsPresenter = new OptionsPresenter(topBar, viewPager);
35
+        optionsPresenter = new OptionsPresenter(this);
29 36
         initViews();
30 37
     }
31 38
 
32 39
     private void initViews() {
33
-        setOrientation(VERTICAL);
34
-        addView(topBar);
35
-        addView(viewPager);
40
+        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
41
+        layoutParams.addRule(BELOW, topBar.getId());
42
+        addView(topBar, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
43
+        addView(viewPager, layoutParams);
36 44
         topBar.setupTopTabsWithViewPager(viewPager);
37 45
     }
38 46
 
@@ -47,16 +55,35 @@ public class TopTabsLayout extends LinearLayout implements Container {
47 55
     }
48 56
 
49 57
     @Override
50
-    @RestrictTo(RestrictTo.Scope.TESTS)
51 58
     public TopBar getTopBar() {
52 59
         return topBar;
53 60
     }
54 61
 
62
+    @Override
63
+    public View getContentView() {
64
+        return viewPager;
65
+    }
66
+
67
+    @Override
68
+    public void drawBehindTopBar() {
69
+
70
+    }
71
+
72
+    @Override
73
+    public void drawBelowTopBar() {
74
+
75
+    }
76
+
55 77
     @RestrictTo(RestrictTo.Scope.TESTS)
56 78
     public ViewPager getViewPager() {
57 79
         return viewPager;
58 80
     }
59 81
 
82
+
83
+    public void performOnCurrentTab(Task<TopTabController> task) {
84
+        task.run(tabs.get(viewPager.getCurrentItem()));
85
+    }
86
+
60 87
     public void switchToTab(int index) {
61 88
         viewPager.setCurrentItem(index);
62 89
     }

+ 44
- 19
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerLayout.java View File

@@ -2,21 +2,27 @@ package com.reactnativenavigation.mocks;
2 2
 
3 3
 import android.content.Context;
4 4
 import android.view.View;
5
+import android.view.ViewGroup;
6
+import android.widget.RelativeLayout;
5 7
 
6 8
 import com.reactnativenavigation.parse.NavigationOptions;
7 9
 import com.reactnativenavigation.presentation.OptionsPresenter;
8 10
 import com.reactnativenavigation.views.ReactContainer;
9 11
 import com.reactnativenavigation.views.TopBar;
10 12
 
11
-public class TestContainerLayout extends View implements ReactContainer {
13
+public class TestContainerLayout extends RelativeLayout implements ReactContainer {
12 14
 
13 15
     private final TopBar topBar;
16
+    private final View contentView;
14 17
     private final OptionsPresenter optionsPresenter;
15 18
 
16 19
     public TestContainerLayout(final Context context) {
17
-		super(context);
20
+        super(context);
18 21
         topBar = new TopBar(context, this);
19
-        optionsPresenter = new OptionsPresenter(topBar, new View(context));
22
+        contentView = new View(context);
23
+        addView(topBar);
24
+        addView(contentView);
25
+        optionsPresenter = new OptionsPresenter(this);
20 26
     }
21 27
 
22 28
     public TopBar getTopBar() {
@@ -24,26 +30,45 @@ public class TestContainerLayout extends View implements ReactContainer {
24 30
     }
25 31
 
26 32
     @Override
27
-	public boolean isReady() {
28
-		return false;
29
-	}
33
+    public View getContentView() {
34
+        return contentView;
35
+    }
36
+
37
+    @Override
38
+    public void drawBehindTopBar() {
39
+        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
40
+        layoutParams.removeRule(BELOW);
41
+        contentView.setLayoutParams(layoutParams);
42
+    }
43
+
44
+    @Override
45
+    public void drawBelowTopBar() {
46
+        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
47
+        layoutParams.addRule(BELOW, topBar.getId());
48
+        contentView.setLayoutParams(layoutParams);
49
+    }
50
+
51
+    @Override
52
+    public boolean isReady() {
53
+        return false;
54
+    }
30 55
 
31
-	@Override
32
-	public View asView() {
33
-		return this;
34
-	}
56
+    @Override
57
+    public View asView() {
58
+        return this;
59
+    }
35 60
 
36
-	@Override
37
-	public void destroy() {
38
-	}
61
+    @Override
62
+    public void destroy() {
63
+    }
39 64
 
40
-	@Override
41
-	public void sendContainerStart() {
42
-	}
65
+    @Override
66
+    public void sendContainerStart() {
67
+    }
43 68
 
44
-	@Override
45
-	public void sendContainerStop() {
46
-	}
69
+    @Override
70
+    public void sendContainerStop() {
71
+    }
47 72
 
48 73
     @Override
49 74
     public void applyOptions(NavigationOptions options) {

+ 37
- 22
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerView.java View File

@@ -9,35 +9,35 @@ import com.reactnativenavigation.views.TopBar;
9 9
 
10 10
 public class TestContainerView extends View implements ReactContainer {
11 11
 
12
-	private TopBar topBar;
12
+    private TopBar topBar;
13 13
 
14
-	public TestContainerView(final Context context) {
15
-		super(context);
16
-		topBar = new TopBar(context, this);
14
+    public TestContainerView(final Context context) {
15
+        super(context);
16
+        topBar = new TopBar(context, this);
17 17
 
18
-	}
18
+    }
19 19
 
20
-	@Override
21
-	public boolean isReady() {
22
-		return false;
23
-	}
20
+    @Override
21
+    public boolean isReady() {
22
+        return false;
23
+    }
24 24
 
25
-	@Override
26
-	public View asView() {
27
-		return this;
28
-	}
25
+    @Override
26
+    public View asView() {
27
+        return this;
28
+    }
29 29
 
30
-	@Override
31
-	public void destroy() {
32
-	}
30
+    @Override
31
+    public void destroy() {
32
+    }
33 33
 
34
-	@Override
35
-	public void sendContainerStart() {
36
-	}
34
+    @Override
35
+    public void sendContainerStart() {
36
+    }
37 37
 
38
-	@Override
39
-	public void sendContainerStop() {
40
-	}
38
+    @Override
39
+    public void sendContainerStop() {
40
+    }
41 41
 
42 42
     @Override
43 43
     public void applyOptions(NavigationOptions options) {
@@ -53,4 +53,19 @@ public class TestContainerView extends View implements ReactContainer {
53 53
     public TopBar getTopBar() {
54 54
         return topBar;
55 55
     }
56
+
57
+    @Override
58
+    public View getContentView() {
59
+        return null;
60
+    }
61
+
62
+    @Override
63
+    public void drawBehindTopBar() {
64
+
65
+    }
66
+
67
+    @Override
68
+    public void drawBelowTopBar() {
69
+
70
+    }
56 71
 }

+ 24
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestNavigationAnimator.java View File

@@ -0,0 +1,24 @@
1
+package com.reactnativenavigation.mocks;
2
+
3
+import android.view.View;
4
+
5
+import com.reactnativenavigation.anim.NavigationAnimator;
6
+
7
+import org.robolectric.RuntimeEnvironment;
8
+
9
+public class TestNavigationAnimator extends NavigationAnimator {
10
+
11
+	public TestNavigationAnimator() {
12
+		super(RuntimeEnvironment.application);
13
+	}
14
+
15
+	@Override
16
+	public void animatePush(final View enteringView, NavigationAnimationListener animationListener) {
17
+		if (animationListener != null) animationListener.onAnimationEnd();
18
+	}
19
+
20
+	@Override
21
+	public void animatePop(final View enteringView, NavigationAnimationListener animationListener) {
22
+		if (animationListener != null) animationListener.onAnimationEnd();
23
+	}
24
+}

+ 0
- 25
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestStackAnimator.java View File

@@ -1,25 +0,0 @@
1
-package com.reactnativenavigation.mocks;
2
-
3
-import android.support.annotation.Nullable;
4
-import android.view.View;
5
-
6
-import com.reactnativenavigation.anim.StackAnimator;
7
-
8
-import org.robolectric.RuntimeEnvironment;
9
-
10
-public class TestStackAnimator extends StackAnimator {
11
-
12
-	public TestStackAnimator() {
13
-		super(RuntimeEnvironment.application);
14
-	}
15
-
16
-	@Override
17
-	public void animatePush(final View enteringView, StackAnimationListener animationListener) {
18
-		if (animationListener != null) animationListener.onAnimationEnd();
19
-	}
20
-
21
-	@Override
22
-	public void animatePop(final View enteringView, StackAnimationListener animationListener) {
23
-		if (animationListener != null) animationListener.onAnimationEnd();
24
-	}
25
-}

+ 32
- 26
lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java View File

@@ -24,6 +24,8 @@ public class NavigationOptionsTest extends BaseTest {
24 24
     private static final String TOP_BAR_FONT_FAMILY = "HelveticaNeue-CondensedBold";
25 25
     private static final Typeface TOP_BAR_TYPEFACE = Typeface.create("HelveticaNeue-CondensedBold", Typeface.BOLD);
26 26
     private static final NavigationOptions.BooleanOptions TOP_BAR_HIDDEN = True;
27
+    private static final NavigationOptions.BooleanOptions TOP_BAR_DRAW_BEHIND = True;
28
+    private static final NavigationOptions.BooleanOptions TOP_BAR_HIDE_ON_SCROLL = True;
27 29
     private static final NavigationOptions.BooleanOptions BOTTOM_TABS_ANIMATE_HIDE = True;
28 30
     private static final NavigationOptions.BooleanOptions BOTTOM_TABS_HIDDEN = True;
29 31
     private static final int BOTTOM_TABS_BADGE = 3;
@@ -38,18 +40,18 @@ public class NavigationOptionsTest extends BaseTest {
38 40
     }
39 41
 
40 42
     @Test
41
-	public void parsesNullAsDefaultEmptyOptions() throws Exception {
42
-		assertThat(NavigationOptions.parse(mockLoader, null)).isNotNull();
43
-	}
43
+    public void parsesNullAsDefaultEmptyOptions() throws Exception {
44
+        assertThat(NavigationOptions.parse(mockLoader, null)).isNotNull();
45
+    }
44 46
 
45
-	@Test
46
-	public void parsesJson() throws Exception {
47
-		JSONObject json = new JSONObject()
47
+    @Test
48
+    public void parsesJson() throws Exception {
49
+        JSONObject json = new JSONObject()
48 50
                 .put("topBar", createTopBar())
49 51
                 .put("bottomTabs", createTabBar());
50
-		NavigationOptions result = NavigationOptions.parse(mockLoader, json);
52
+        NavigationOptions result = NavigationOptions.parse(mockLoader, json);
51 53
         assertResult(result);
52
-	}
54
+    }
53 55
 
54 56
     private void assertResult(NavigationOptions result) {
55 57
         assertThat(result.topBarOptions.title).isEqualTo(TITLE);
@@ -58,6 +60,8 @@ public class NavigationOptionsTest extends BaseTest {
58 60
         assertThat(result.topBarOptions.textFontSize).isEqualTo(TOP_BAR_FONT_SIZE);
59 61
         assertThat(result.topBarOptions.textFontFamily).isEqualTo(TOP_BAR_TYPEFACE);
60 62
         assertThat(result.topBarOptions.hidden).isEqualTo(TOP_BAR_HIDDEN);
63
+        assertThat(result.topBarOptions.drawBehind).isEqualTo(TOP_BAR_DRAW_BEHIND);
64
+        assertThat(result.topBarOptions.hideOnScroll).isEqualTo(TOP_BAR_HIDE_ON_SCROLL);
61 65
         assertThat(result.bottomTabsOptions.animateHide).isEqualTo(BOTTOM_TABS_ANIMATE_HIDE);
62 66
         assertThat(result.bottomTabsOptions.hidden).isEqualTo(BOTTOM_TABS_HIDDEN);
63 67
         assertThat(result.bottomTabsOptions.tabBadge).isEqualTo(BOTTOM_TABS_BADGE);
@@ -68,22 +72,24 @@ public class NavigationOptionsTest extends BaseTest {
68 72
     @NonNull
69 73
     private JSONObject createTabBar() throws JSONException {
70 74
         return new JSONObject()
71
-            .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
72
-            .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
73
-            .put("hidden", BOTTOM_TABS_HIDDEN)
74
-            .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
75
-            .put("tabBadge", BOTTOM_TABS_BADGE);
75
+                .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
76
+                .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
77
+                .put("hidden", BOTTOM_TABS_HIDDEN)
78
+                .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
79
+                .put("tabBadge", BOTTOM_TABS_BADGE);
76 80
     }
77 81
 
78 82
     @NonNull
79 83
     private JSONObject createTopBar() throws JSONException {
80 84
         return new JSONObject()
81
-            .put("title", TITLE)
82
-            .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
83
-            .put("textColor", TOP_BAR_TEXT_COLOR)
84
-            .put("textFontSize", TOP_BAR_FONT_SIZE)
85
-            .put("textFontFamily", TOP_BAR_FONT_FAMILY)
86
-            .put("hidden", TOP_BAR_HIDDEN);
85
+                .put("title", TITLE)
86
+                .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
87
+                .put("textColor", TOP_BAR_TEXT_COLOR)
88
+                .put("textFontSize", TOP_BAR_FONT_SIZE)
89
+                .put("textFontFamily", TOP_BAR_FONT_FAMILY)
90
+                .put("hidden", TOP_BAR_HIDDEN)
91
+                .put("drawBehind", TOP_BAR_DRAW_BEHIND)
92
+                .put("hideOnScroll", TOP_BAR_HIDE_ON_SCROLL);
87 93
     }
88 94
 
89 95
     @NonNull
@@ -122,8 +128,8 @@ public class NavigationOptionsTest extends BaseTest {
122 128
     @Test
123 129
     public void mergedDefaultOptionsDontOverrideGivenOptions() throws Exception {
124 130
         JSONObject defaultJson = new JSONObject()
125
-            .put("topBar", createOtherTopBar())
126
-            .put("bottomTabs", createOtherTabBar());
131
+                .put("topBar", createOtherTopBar())
132
+                .put("bottomTabs", createOtherTabBar());
127 133
         NavigationOptions defaultOptions = NavigationOptions.parse(mockLoader, defaultJson);
128 134
 
129 135
         JSONObject json = new JSONObject()
@@ -134,9 +140,9 @@ public class NavigationOptionsTest extends BaseTest {
134 140
         assertResult(options);
135 141
     }
136 142
 
137
-	@Test
138
-	public void defaultEmptyOptions() throws Exception {
139
-		NavigationOptions uut = new NavigationOptions();
140
-		assertThat(uut.topBarOptions.title).isEmpty();
141
-	}
143
+    @Test
144
+    public void defaultEmptyOptions() throws Exception {
145
+        NavigationOptions uut = new NavigationOptions();
146
+        assertThat(uut.topBarOptions.title).isEmpty();
147
+    }
142 148
 }

+ 2
- 2
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java View File

@@ -7,7 +7,7 @@ import com.reactnativenavigation.BaseTest;
7 7
 import com.reactnativenavigation.mocks.MockPromise;
8 8
 import com.reactnativenavigation.mocks.SimpleContainerViewController;
9 9
 import com.reactnativenavigation.mocks.SimpleViewController;
10
-import com.reactnativenavigation.mocks.TestStackAnimator;
10
+import com.reactnativenavigation.mocks.TestNavigationAnimator;
11 11
 import com.reactnativenavigation.parse.NavigationOptions;
12 12
 import com.reactnativenavigation.utils.CompatUtils;
13 13
 
@@ -218,7 +218,7 @@ public class NavigatorTest extends BaseTest {
218 218
 
219 219
 	@NonNull
220 220
 	private StackController newStack() {
221
-		return new StackController(activity, "stack" + CompatUtils.generateViewId(), new TestStackAnimator());
221
+		return new StackController(activity, "stack" + CompatUtils.generateViewId(), new TestNavigationAnimator());
222 222
 	}
223 223
 
224 224
 	@Test

+ 134
- 114
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java View File

@@ -4,6 +4,8 @@ import android.app.Activity;
4 4
 import android.graphics.Color;
5 5
 import android.graphics.drawable.ColorDrawable;
6 6
 import android.view.View;
7
+import android.view.ViewGroup;
8
+import android.widget.RelativeLayout;
7 9
 
8 10
 import com.reactnativenavigation.BaseTest;
9 11
 import com.reactnativenavigation.mocks.TestContainerLayout;
@@ -11,127 +13,145 @@ import com.reactnativenavigation.parse.NavigationOptions;
11 13
 
12 14
 import org.junit.Test;
13 15
 
16
+import static android.widget.RelativeLayout.BELOW;
14 17
 import static org.assertj.core.api.Java6Assertions.assertThat;
15 18
 import static org.mockito.Mockito.spy;
16 19
 
17 20
 public class OptionsApplyingTest extends BaseTest {
18
-	private Activity activity;
19
-	private ContainerViewController uut;
20
-	private ContainerViewController.IReactView view;
21
-	private NavigationOptions initialNavigationOptions;
22
-
23
-	@Override
24
-	public void beforeEach() {
25
-		super.beforeEach();
26
-		activity = newActivity();
27
-		initialNavigationOptions = new NavigationOptions();
28
-		view = spy(new TestContainerLayout(activity));
29
-		uut = new ContainerViewController(activity,
21
+    private Activity activity;
22
+    private ContainerViewController uut;
23
+    private ContainerViewController.IReactView view;
24
+    private NavigationOptions initialNavigationOptions;
25
+
26
+    @Override
27
+    public void beforeEach() {
28
+        super.beforeEach();
29
+        activity = newActivity();
30
+        initialNavigationOptions = new NavigationOptions();
31
+        view = spy(new TestContainerLayout(activity));
32
+        uut = new ContainerViewController(activity,
30 33
                 "containerId1",
31 34
                 "containerName",
32 35
                 (activity1, containerId, containerName) -> view,
33 36
                 initialNavigationOptions
34 37
         );
35
-		uut.ensureViewIsCreated();
36
-	}
37
-
38
-	@Test
39
-	public void applyNavigationOptionsHandlesNoParentStack() throws Exception {
40
-		assertThat(uut.getParentStackController()).isNull();
41
-		uut.onViewAppeared();
42
-		assertThat(uut.getParentStackController()).isNull();
43
-	}
44
-
45
-	@Test
46
-	public void initialOptionsAppliedOnAppear() throws Exception {
47
-		assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
48
-		initialNavigationOptions.topBarOptions.title = "the title";
49
-		StackController stackController = new StackController(activity, "stackId");
50
-		stackController.push(uut);
51
-		assertThat(uut.getTopBar().getTitle()).isEmpty();
52
-
53
-		uut.onViewAppeared();
54
-		assertThat(uut.getTopBar().getTitle()).isEqualTo("the title");
55
-	}
56
-
57
-	@Test
58
-	public void mergeNavigationOptionsUpdatesCurrentOptions() throws Exception {
59
-		assertThat(uut.getNavigationOptions().topBarOptions.title).isEmpty();
60
-		NavigationOptions options = new NavigationOptions();
61
-		options.topBarOptions.title = "new title";
62
-		uut.mergeNavigationOptions(options);
63
-		assertThat(uut.getNavigationOptions().topBarOptions.title).isEqualTo("new title");
64
-		assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
65
-	}
66
-
67
-	@Test
68
-	public void reappliesOptionsOnMerge() throws Exception {
69
-		uut.onViewAppeared();
70
-		assertThat(uut.getTopBar().getTitle()).isEmpty();
71
-
72
-		NavigationOptions opts = new NavigationOptions();
73
-		opts.topBarOptions.title = "the new title";
74
-		uut.mergeNavigationOptions(opts);
75
-
76
-		assertThat(uut.getTopBar().getTitle()).isEqualTo("the new title");
77
-	}
78
-
79
-	@Test
80
-	public void appliesTopBackBackgroundColor() throws Exception {
81
-		uut.onViewAppeared();
82
-		//TODO: FIX TEST
83
-		assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isNotEqualTo(Color.RED);
84
-
85
-		NavigationOptions opts = new NavigationOptions();
86
-		opts.topBarOptions.backgroundColor = Color.RED;
87
-		uut.mergeNavigationOptions(opts);
88
-
89
-		assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isEqualTo(Color.RED);
90
-	}
91
-
92
-	@Test
93
-	public void appliesTopBarTextColor() throws Exception {
94
-		assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
95
-		initialNavigationOptions.topBarOptions.title = "the title";
96
-		uut.onViewAppeared();
97
-		assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isNotEqualTo(Color.RED);
98
-
99
-		NavigationOptions opts = new NavigationOptions();
100
-		opts.topBarOptions.title = "the title";
101
-		opts.topBarOptions.textColor = Color.RED;
102
-		uut.mergeNavigationOptions(opts);
103
-
104
-		assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
105
-		assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isEqualTo(Color.RED);
106
-	}
107
-
108
-	@Test
109
-	public void appliesTopBarTextSize() throws Exception {
110
-		assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
111
-		initialNavigationOptions.topBarOptions.title = "the title";
112
-		uut.onViewAppeared();
113
-		assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isNotEqualTo(18);
114
-
115
-		NavigationOptions opts = new NavigationOptions();
116
-		opts.topBarOptions.title = "the title";
117
-		opts.topBarOptions.textFontSize = 18;
118
-		uut.mergeNavigationOptions(opts);
119
-
120
-		assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
121
-		assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isEqualTo(18);
122
-	}
123
-
124
-	@Test
125
-	public void appliesTopBarHidden() throws Exception {
126
-		assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
127
-		initialNavigationOptions.topBarOptions.title = "the title";
128
-		uut.onViewAppeared();
129
-		assertThat(uut.getTopBar().getVisibility()).isNotEqualTo(View.GONE);
130
-
131
-		NavigationOptions opts = new NavigationOptions();
132
-		opts.topBarOptions.hidden = NavigationOptions.BooleanOptions.True;
133
-		uut.mergeNavigationOptions(opts);
134
-
135
-		assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE);
136
-	}
38
+        uut.ensureViewIsCreated();
39
+    }
40
+
41
+    @Test
42
+    public void applyNavigationOptionsHandlesNoParentStack() throws Exception {
43
+        assertThat(uut.getParentStackController()).isNull();
44
+        uut.onViewAppeared();
45
+        assertThat(uut.getParentStackController()).isNull();
46
+    }
47
+
48
+    @Test
49
+    public void initialOptionsAppliedOnAppear() throws Exception {
50
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
51
+        initialNavigationOptions.topBarOptions.title = "the title";
52
+        StackController stackController = new StackController(activity, "stackId");
53
+        stackController.push(uut);
54
+        assertThat(uut.getTopBar().getTitle()).isEmpty();
55
+
56
+        uut.onViewAppeared();
57
+        assertThat(uut.getTopBar().getTitle()).isEqualTo("the title");
58
+    }
59
+
60
+    @Test
61
+    public void mergeNavigationOptionsUpdatesCurrentOptions() throws Exception {
62
+        assertThat(uut.getNavigationOptions().topBarOptions.title).isEmpty();
63
+        NavigationOptions options = new NavigationOptions();
64
+        options.topBarOptions.title = "new title";
65
+        uut.mergeNavigationOptions(options);
66
+        assertThat(uut.getNavigationOptions().topBarOptions.title).isEqualTo("new title");
67
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
68
+    }
69
+
70
+    @Test
71
+    public void reappliesOptionsOnMerge() throws Exception {
72
+        uut.onViewAppeared();
73
+        assertThat(uut.getTopBar().getTitle()).isEmpty();
74
+
75
+        NavigationOptions opts = new NavigationOptions();
76
+        opts.topBarOptions.title = "the new title";
77
+        uut.mergeNavigationOptions(opts);
78
+
79
+        assertThat(uut.getTopBar().getTitle()).isEqualTo("the new title");
80
+    }
81
+
82
+    @Test
83
+    public void appliesTopBackBackgroundColor() throws Exception {
84
+        uut.onViewAppeared();
85
+        //TODO: FIX TEST
86
+        assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isNotEqualTo(Color.RED);
87
+
88
+        NavigationOptions opts = new NavigationOptions();
89
+        opts.topBarOptions.backgroundColor = Color.RED;
90
+        uut.mergeNavigationOptions(opts);
91
+
92
+        assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isEqualTo(Color.RED);
93
+    }
94
+
95
+    @Test
96
+    public void appliesTopBarTextColor() throws Exception {
97
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
98
+        initialNavigationOptions.topBarOptions.title = "the title";
99
+        uut.onViewAppeared();
100
+        assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isNotEqualTo(Color.RED);
101
+
102
+        NavigationOptions opts = new NavigationOptions();
103
+        opts.topBarOptions.title = "the title";
104
+        opts.topBarOptions.textColor = Color.RED;
105
+        uut.mergeNavigationOptions(opts);
106
+
107
+        assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
108
+        assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isEqualTo(Color.RED);
109
+    }
110
+
111
+    @Test
112
+    public void appliesTopBarTextSize() throws Exception {
113
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
114
+        initialNavigationOptions.topBarOptions.title = "the title";
115
+        uut.onViewAppeared();
116
+        assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isNotEqualTo(18);
117
+
118
+        NavigationOptions opts = new NavigationOptions();
119
+        opts.topBarOptions.title = "the title";
120
+        opts.topBarOptions.textFontSize = 18;
121
+        uut.mergeNavigationOptions(opts);
122
+
123
+        assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
124
+        assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isEqualTo(18);
125
+    }
126
+
127
+    @Test
128
+    public void appliesTopBarHidden() throws Exception {
129
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
130
+        initialNavigationOptions.topBarOptions.title = "the title";
131
+        uut.onViewAppeared();
132
+        assertThat(uut.getTopBar().getVisibility()).isNotEqualTo(View.GONE);
133
+
134
+        NavigationOptions opts = new NavigationOptions();
135
+        opts.topBarOptions.hidden = NavigationOptions.BooleanOptions.True;
136
+        uut.mergeNavigationOptions(opts);
137
+
138
+        assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE);
139
+    }
140
+
141
+    @Test
142
+    public void appliesDrawUnder() throws Exception {
143
+        assertThat(uut.getNavigationOptions()).isSameAs(initialNavigationOptions);
144
+        initialNavigationOptions.topBarOptions.title = "the title";
145
+        initialNavigationOptions.topBarOptions.drawBehind = NavigationOptions.BooleanOptions.False;
146
+        uut.onViewAppeared();
147
+        RelativeLayout.LayoutParams uutLayoutParams = (RelativeLayout.LayoutParams) ((ViewGroup) uut.getContainer().asView()).getChildAt(1).getLayoutParams();
148
+        assertThat(uutLayoutParams.getRule(BELOW)).isNotEqualTo(0);
149
+
150
+        NavigationOptions opts = new NavigationOptions();
151
+        opts.topBarOptions.drawBehind = NavigationOptions.BooleanOptions.True;
152
+        uut.mergeNavigationOptions(opts);
153
+
154
+        uutLayoutParams = (RelativeLayout.LayoutParams) ((ViewGroup) uut.getContainer().asView()).getChildAt(1).getLayoutParams();
155
+        assertThat(uutLayoutParams.getRule(BELOW)).isEqualTo(0);
156
+    }
137 157
 }

+ 2
- 2
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java View File

@@ -7,7 +7,7 @@ import android.widget.FrameLayout;
7 7
 
8 8
 import com.reactnativenavigation.BaseTest;
9 9
 import com.reactnativenavigation.mocks.SimpleViewController;
10
-import com.reactnativenavigation.mocks.TestStackAnimator;
10
+import com.reactnativenavigation.mocks.TestNavigationAnimator;
11 11
 
12 12
 import org.junit.Test;
13 13
 
@@ -70,7 +70,7 @@ public class ParentControllerTest extends BaseTest {
70 70
 
71 71
 	@Test
72 72
 	public void findControllerById_Recursive() throws Exception {
73
-		StackController stackController = new StackController(activity, "stack", new TestStackAnimator());
73
+		StackController stackController = new StackController(activity, "stack", new TestNavigationAnimator());
74 74
 		SimpleViewController child1 = new SimpleViewController(activity, "child1");
75 75
 		SimpleViewController child2 = new SimpleViewController(activity, "child2");
76 76
 		stackController.push(child1);

+ 4
- 4
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java View File

@@ -4,7 +4,7 @@ import android.app.Activity;
4 4
 
5 5
 import com.reactnativenavigation.BaseTest;
6 6
 import com.reactnativenavigation.mocks.SimpleViewController;
7
-import com.reactnativenavigation.mocks.TestStackAnimator;
7
+import com.reactnativenavigation.mocks.TestNavigationAnimator;
8 8
 
9 9
 import org.assertj.core.api.iterable.Extractor;
10 10
 import org.junit.Test;
@@ -26,7 +26,7 @@ public class StackControllerTest extends BaseTest {
26 26
 	public void beforeEach() {
27 27
 		super.beforeEach();
28 28
 		activity = newActivity();
29
-		uut = new StackController(activity, "uut", new TestStackAnimator());
29
+		uut = new StackController(activity, "uut", new TestNavigationAnimator());
30 30
 		child1 = new SimpleViewController(activity, "child1");
31 31
 		child2 = new SimpleViewController(activity, "child2");
32 32
 		child3 = new SimpleViewController(activity, "child3");
@@ -80,7 +80,7 @@ public class StackControllerTest extends BaseTest {
80 80
 		uut.push(child1);
81 81
 		assertThat(child1.getParentStackController()).isEqualTo(uut);
82 82
 
83
-		StackController anotherNavController = new StackController(activity, "another", new TestStackAnimator());
83
+		StackController anotherNavController = new StackController(activity, "another", new TestNavigationAnimator());
84 84
 		anotherNavController.push(child2);
85 85
 		assertThat(child2.getParentStackController()).isEqualTo(anotherNavController);
86 86
 	}
@@ -227,7 +227,7 @@ public class StackControllerTest extends BaseTest {
227 227
 
228 228
 	@Test
229 229
 	public void findControllerById_Deeply() throws Exception {
230
-		StackController stack = new StackController(activity, "stack2", new TestStackAnimator());
230
+		StackController stack = new StackController(activity, "stack2", new TestNavigationAnimator());
231 231
 		stack.push(child2);
232 232
 		uut.push(stack);
233 233
 		assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);

+ 1
- 2
lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java View File

@@ -9,8 +9,7 @@ import static org.assertj.core.api.Java6Assertions.assertThat;
9 9
 public class TopBarTest extends BaseTest {
10 10
 	@Test
11 11
 	public void title() throws Exception {
12
-		//TODO:  fix null
13
-		TopBar topBar = new TopBar(newActivity(), null);
12
+		TopBar topBar = new TopBar(newActivity());
14 13
 		assertThat(topBar.getTitle()).isEmpty();
15 14
 
16 15
 		topBar.setTitle("new title");

+ 2
- 2
lib/android/gradle/wrapper/gradle-wrapper.properties View File

@@ -1,6 +1,6 @@
1
-#Mon Dec 28 10:00:20 PST 2015
1
+#Thu Jan 04 11:01:32 EET 2018
2 2
 distributionBase=GRADLE_USER_HOME
3 3
 distributionPath=wrapper/dists
4 4
 zipStoreBase=GRADLE_USER_HOME
5 5
 zipStorePath=wrapper/dists
6
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
6
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

+ 2
- 2
lib/src/params/options/TopBar.js View File

@@ -17,7 +17,7 @@ class TopBar {
17 17
    * @property {boolean} [blur]
18 18
    * @property {boolean} [noBorder]
19 19
    * @property {boolean} [largeTitle]
20
-   * @property {boolean} [drawUnder]
20
+   * @property {boolean} [drawBehind]
21 21
    * @property {options:Button[]} [rightButtons]
22 22
    * @property {options:Button[]} [leftButtons]
23 23
    */
@@ -37,7 +37,7 @@ class TopBar {
37 37
     this.noBorder = options.noBorder;
38 38
     this.largeTitle = options.largeTitle;
39 39
     this.testID = options.testID;
40
-    this.drawUnder = options.drawUnder;
40
+    this.drawBehind = options.drawBehind;
41 41
     this.rightButtons = options.rightButtons && options.rightButtons.map((button) => new Button(button));
42 42
     this.leftButtons = options.leftButtons && options.leftButtons.map((button) => new Button(button));
43 43
   }

+ 2
- 1
package.json View File

@@ -176,6 +176,7 @@
176 176
       "react/jsx-no-bind": 0,
177 177
       "react/jsx-handler-names": 0,
178 178
       "react/forbid-component-props": 0,
179
+      "react/jsx-curly-brace-presence": 0,
179 180
       "capitalized-comments": 0,
180 181
       "no-use-before-define": [
181 182
         2,
@@ -196,4 +197,4 @@
196 197
       ]
197 198
     }
198 199
   }
199
-}
200
+}

+ 14
- 1
playground/src/containers/ScrollViewScreen.js View File

@@ -10,6 +10,10 @@ class ScrollViewScreen extends Component {
10 10
   static get navigationOptions() {
11 11
     return {
12 12
       topBar: {
13
+        title: 'Collapse',
14
+        textColor: 'black',
15
+        textFontSize: 16,
16
+        drawUnder: true,
13 17
         translucent: false
14 18
       }
15 19
     };
@@ -36,9 +40,16 @@ class ScrollViewScreen extends Component {
36 40
   }
37 41
 
38 42
   onClickToggleTopBarHideOnScroll() {
43
+    this.setState({
44
+      topBarHideOnScroll: !this.state.topBarHideOnScroll
45
+    });
46
+  }
47
+
48
+  componentDidUpdate() {
39 49
     Navigation.setOptions(this.props.containerId, {
40 50
       topBar: {
41
-        hideOnScroll: !this.state.topBarHideOnScroll
51
+        drawUnder: true,
52
+        hideOnScroll: this.state.topBarHideOnScroll
42 53
       }
43 54
     });
44 55
   }
@@ -49,6 +60,8 @@ module.exports = ScrollViewScreen;
49 60
 const styles = StyleSheet.create({
50 61
   contentContainer: {
51 62
     alignItems: 'center',
63
+    backgroundColor: 'green',
64
+    paddingTop: 200,
52 65
     height: 1200
53 66
   }
54 67
 });