diff --git a/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java b/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java index 8f788838fa9..adaed42a7da 100644 --- a/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java +++ b/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/BaseTest.java @@ -15,94 +15,100 @@ @RunWith(AndroidJUnit4.class) public abstract class BaseTest { - static final String PACKAGE_NAME = "com.reactnativenavigation.playground"; - private static final long TIMEOUT = 60000; - - @Before - public void beforeEach() throws Exception { - device().wakeUp(); - device().setOrientationNatural(); - launchTheApp(); - assertMainShown(); - } - - @After - public void afterEach() throws Exception { - device().executeShellCommand("am force-stop " + PACKAGE_NAME); - device().executeShellCommand("am kill " + PACKAGE_NAME); - } - - public UiDevice device() { - return UiDevice.getInstance(getInstrumentation()); - } - - public void launchTheApp() throws Exception { - device().executeShellCommand("am start -n " + PACKAGE_NAME + "/.MainActivity"); - device().waitForIdle(); - acceptOverlayPermissionIfNeeded(); - device().wait(Until.gone(By.textContains("Please wait")), 1000 * 60 * 3); - } - - public void assertMainShown() { - assertExists(By.text("React Native Navigation!")); - } - - public void acceptOverlayPermissionIfNeeded() throws Exception { - if (isRequestingOverlayPermission()) { - if (!elementByText("Playground").exists()) { - scrollToText("Playground"); - } - elementByText("Playground").click(); - device().findObject(new UiSelector().checkable(true).checked(false)).click(); - device().pressBack(); - device().pressBack(); - } - } - - private boolean isRequestingOverlayPermission() { - return device().wait(Until.hasObject(By.pkg("com.android.settings").depth(0)), 300); - } - - public UiObject elementByText(String text) { - return device().findObject(new UiSelector().text(text)); - } - - public UiObject elementByTextContains(String text) { - return device().findObject(new UiSelector().textContains(text)); - } - - public void scrollToText(String txt) throws Exception { - new UiScrollable(new UiSelector().scrollable(true)).scrollTextIntoView(txt); - } - - public void assertExists(BySelector selector) { - assertThat(device().wait(Until.hasObject(selector), TIMEOUT)).withFailMessage("expected %1$s to be visible", selector).isTrue(); - assertThat(device().findObject(selector).getVisibleCenter().x).isPositive().isLessThan(device().getDisplayWidth()); - assertThat(device().findObject(selector).getVisibleCenter().y).isPositive().isLessThan(device().getDisplayHeight()); - } - - public Bitmap captureScreenshot() throws Exception { - File file = File.createTempFile("tmpE2E", "png"); - device().takeScreenshot(file); - Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); - file.delete(); - return bitmap; - } - - public void swipeOpenFromLeft() { - int w = device().getDisplayWidth(); - int h = device().getDisplayHeight(); - device().swipe(5, h / 2, w / 2, h / 2, 10); - } - - public void swipeOpenFromRight() { - int w = device().getDisplayWidth(); - int h = device().getDisplayHeight(); - device().swipe(w - 5, h / 2, w / 2, h / 2, 10); - } - - public boolean isDebug() throws Exception { - PackageInfo packageInfo = getInstrumentation().getTargetContext().getPackageManager().getPackageInfo("com.reactnativenavigation.playground", 0); - return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } + static final String PACKAGE_NAME = "com.reactnativenavigation.playground"; + private static final long TIMEOUT = 60000; + + @Before + public void beforeEach() throws Exception { + device().wakeUp(); + device().setOrientationNatural(); + launchTheApp(); + assertMainShown(); + } + + @After + public void afterEach() throws Exception { + device().executeShellCommand("am force-stop " + PACKAGE_NAME); + device().executeShellCommand("am kill " + PACKAGE_NAME); + } + + public UiDevice device() { + return UiDevice.getInstance(getInstrumentation()); + } + + public void launchTheApp() throws Exception { + device().executeShellCommand("am start -n " + PACKAGE_NAME + "/.MainActivity"); + device().waitForIdle(); + acceptOverlayPermissionIfNeeded(); + device().wait(Until.gone(By.textContains("Please wait")), 1000 * 60 * 3); + } + + public void assertMainShown() { + assertExists(By.text("React Native Navigation!")); + } + + public void acceptOverlayPermissionIfNeeded() throws Exception { + if (isRequestingOverlayPermission()) { + if (!elementByText("Playground").exists()) { + scrollToText("Playground"); + } + elementByText("Playground").click(); + device().findObject(new UiSelector().checkable(true).checked(false)).click(); + device().pressBack(); + device().pressBack(); + } + } + + private boolean isRequestingOverlayPermission() { + return device().wait(Until.hasObject(By.pkg("com.android.settings").depth(0)), 300); + } + + public UiObject elementByText(String text) { + return device().findObject(new UiSelector().text(text)); + } + + public UiObject elementByTextContains(String text) { + return device().findObject(new UiSelector().textContains(text)); + } + + public void scrollToText(String txt) throws Exception { + new UiScrollable(new UiSelector().scrollable(true)).scrollTextIntoView(txt); + } + + public void assertExists(BySelector selector) { + assertThat(device().wait(Until.hasObject(selector), TIMEOUT)).withFailMessage("expected %1$s to be visible", selector).isTrue(); + assertThat(device().findObject(selector).getVisibleCenter().x).isPositive().isLessThan(device().getDisplayWidth()); + assertThat(device().findObject(selector).getVisibleCenter().y).isPositive().isLessThan(device().getDisplayHeight()); + } + + public Bitmap captureScreenshot() throws Exception { + File file = File.createTempFile("tmpE2E", "png"); + device().takeScreenshot(file); + Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); + file.delete(); + return bitmap; + } + + public void swipeOpenFromLeft() { + int w = device().getDisplayWidth(); + int h = device().getDisplayHeight(); + device().swipe(5, h / 2, w / 2, h / 2, 10); + } + + public void swipeOpenFromRight() { + int w = device().getDisplayWidth(); + int h = device().getDisplayHeight(); + device().swipe(w - 5, h / 2, w / 2, h / 2, 10); + } + + public void swipeUp() { + int w = device().getDisplayWidth(); + int h = device().getDisplayHeight(); + device().drag(w / 2, h / 2 + 100, w / 2, h / 2 - 100, 10); + } + + public boolean isDebug() throws Exception { + PackageInfo packageInfo = getInstrumentation().getTargetContext().getPackageManager().getPackageInfo("com.reactnativenavigation.playground", 0); + return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } } diff --git a/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java b/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java index 9c0d02c7d78..b1a8968e999 100644 --- a/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java +++ b/AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java @@ -1,6 +1,7 @@ package com.reactnativenavigation.e2e.androide2e; import android.support.test.uiautomator.By; +import android.support.test.uiautomator.Until; import org.junit.Test; @@ -8,30 +9,30 @@ public class NavigationOptionsTest extends BaseTest { - @Test - public void declareNavigationStyleOnContainerComponent() throws Exception { - elementByText("PUSH OPTIONS SCREEN").click(); - assertExists(By.text("Static Title")); - } - - @Test - public void setTitleDynamically() throws Exception { - elementByText("PUSH OPTIONS SCREEN").click(); - assertExists(By.text("Static Title")); - elementByText("DYNAMIC OPTIONS").click(); - assertExists(By.text("Dynamic Title")); - } - -// @Test -// public void testTopBarHidden() throws Exception { -// elementByText("PUSH OPTIONS SCREEN").click(); -// int topWithNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top; -// elementByText("HIDE TOP BAR").click(); -// int topWithoutNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top; -// assertThat(topWithoutNavigation).isLessThan(topWithNavigation); -// elementByText("SHOW TOP BAR").click(); -// assertExists(By.text("Static Title")); -// } + @Test + public void declareNavigationStyleOnContainerComponent() throws Exception { + elementByText("PUSH OPTIONS SCREEN").click(); + assertExists(By.text("Static Title")); + } + + @Test + public void setTitleDynamically() throws Exception { + elementByText("PUSH OPTIONS SCREEN").click(); + assertExists(By.text("Static Title")); + elementByText("DYNAMIC OPTIONS").click(); + assertExists(By.text("Dynamic Title")); + } + + @Test + public void testTopBarHidden() throws Exception { + elementByText("PUSH OPTIONS SCREEN").click(); + int topWithNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top; + elementByText("HIDE TOP BAR").click(); + int topWithoutNavigation = elementByText("HIDE TOP BAR").getVisibleBounds().top; + assertThat(topWithoutNavigation).isLessThan(topWithNavigation); + elementByText("SHOW TOP BAR").click(); + assertExists(By.text("Static Title")); + } @Test public void testRightButtons() throws Exception { @@ -39,4 +40,14 @@ public void testRightButtons() throws Exception { assertExists(By.text("ONE")); elementByText("ONE").click(); } + + @Test + public void testTopBarCollapse() throws Exception { + elementByText("PUSH OPTIONS SCREEN").click(); + elementByText("SCROLLVIEW SCREEN").click(); + assertExists(By.text("Collapse")); + elementByText("TOGGLE TOP BAR HIDE ON SCROLL").click(); + swipeUp(); + assertThat(device().hasObject(By.text("Collapse"))).isFalse(); + } } diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index 8252b66967b..431a446e482 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -70,5 +70,5 @@ dependencies { testImplementation 'org.robolectric:robolectric:3.5.1' testImplementation 'org.assertj:assertj-core:3.8.0' testImplementation 'com.squareup.assertj:assertj-android:1.1.1' - testImplementation 'org.mockito:mockito-core:2.12.0' + testImplementation 'org.mockito:mockito-core:2.13.0' } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java b/lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java new file mode 100644 index 00000000000..6a13d79807a --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java @@ -0,0 +1,83 @@ +package com.reactnativenavigation.anim; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import com.reactnativenavigation.utils.UiUtils; +import com.reactnativenavigation.views.TopBar; + +@SuppressWarnings("ResourceType") +public class NavigationAnimator { + + public interface NavigationAnimationListener { + void onAnimationEnd(); + } + + private static final int DURATION = 300; + private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); + private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); + private float translationY; + + public NavigationAnimator(Context context) { + translationY = UiUtils.getWindowHeight(context); + } + + public void animatePush(final View view, @Nullable final NavigationAnimationListener animationListener) { + view.setVisibility(View.INVISIBLE); + ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); + alpha.setInterpolator(DECELERATE_INTERPOLATOR); + + AnimatorSet set = new AnimatorSet(); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + view.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (animationListener != null) { + animationListener.onAnimationEnd(); + } + } + }); + ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0); + translationY.setInterpolator(DECELERATE_INTERPOLATOR); + translationY.setDuration(DURATION); + alpha.setDuration(DURATION); + set.playTogether(translationY, alpha); + set.start(); + } + + public void animatePop(View view, @Nullable final NavigationAnimationListener animationListener) { + ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); + alpha.setInterpolator(ACCELERATE_INTERPOLATOR); + + AnimatorSet set = new AnimatorSet(); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animationListener != null) { + animationListener.onAnimationEnd(); + } + } + }); + ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY); + translationY.setInterpolator(ACCELERATE_INTERPOLATOR); + translationY.setDuration(DURATION); + alpha.setDuration(DURATION); + set.playTogether(translationY, alpha); + set.start(); + } +} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java b/lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java deleted file mode 100644 index 6083fb12d3b..00000000000 --- a/lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.reactnativenavigation.anim; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.LinearLayout; - -import com.reactnativenavigation.views.TopBar; - -@SuppressWarnings("ResourceType") -public class StackAnimator { - - public interface StackAnimationListener { - void onAnimationEnd(); - } - - private static final int DURATION = 300; - private static final int DURATION_TOPBAR = 300; - private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); - private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); - private float translationY; - - public StackAnimator(Context context) { - translationY = getWindowHeight(context); - } - - public void animatePush(final View view, @Nullable final StackAnimationListener animationListener) { - view.setVisibility(View.INVISIBLE); - ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); - alpha.setInterpolator(DECELERATE_INTERPOLATOR); - - AnimatorSet set = new AnimatorSet(); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - view.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - if (animationListener != null) { - animationListener.onAnimationEnd(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0); - translationY.setInterpolator(DECELERATE_INTERPOLATOR); - translationY.setDuration(DURATION); - alpha.setDuration(DURATION); - set.playTogether(translationY, alpha); - set.start(); - } - - public void animatePop(View view, @Nullable final StackAnimationListener animationListener) { - ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); - alpha.setInterpolator(ACCELERATE_INTERPOLATOR); - - AnimatorSet set = new AnimatorSet(); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - if (animationListener != null) { - animationListener.onAnimationEnd(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY); - translationY.setInterpolator(ACCELERATE_INTERPOLATOR); - translationY.setDuration(DURATION); - alpha.setDuration(DURATION); - set.playTogether(translationY, alpha); - set.start(); - } - - private float getWindowHeight(Context context) { - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getMetrics(metrics); - return metrics.heightPixels; - } - - public void animateShowTopBar(final TopBar topBar, final View container) { - ValueAnimator containerHeightAnim = ValueAnimator.ofInt(container.getMeasuredHeight(), container.getMeasuredHeight() - topBar.getMeasuredHeight()); - containerHeightAnim.setInterpolator(DECELERATE_INTERPOLATOR); - containerHeightAnim.setDuration(DURATION_TOPBAR); - containerHeightAnim.addUpdateListener(valueAnimator -> { - int val = (Integer) valueAnimator.getAnimatedValue(); - ViewGroup.LayoutParams layoutParams = container.getLayoutParams(); - layoutParams.height = val; - container.setLayoutParams(layoutParams); - }); - ObjectAnimator containerTransitionAnim = ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, -1 * topBar.getMeasuredHeight(), 0); - containerTransitionAnim.setInterpolator(DECELERATE_INTERPOLATOR); - containerTransitionAnim.setDuration(DURATION_TOPBAR); - - ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, -1 * topBar.getHeight(), 0); - topbarAnim.setInterpolator(DECELERATE_INTERPOLATOR); - topbarAnim.setDuration(DURATION_TOPBAR); - - AnimatorSet set = new AnimatorSet(); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - topBar.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - ViewGroup.LayoutParams layoutParams = container.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - container.setLayoutParams(layoutParams); - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - set.playTogether(containerHeightAnim, containerTransitionAnim, topbarAnim); - set.start(); - } - - public void animateHideTopBar(final TopBar topBar, final View container) { - ValueAnimator containerHeightAnim = ValueAnimator.ofInt(container.getMeasuredHeight(), container.getMeasuredHeight() + topBar.getMeasuredHeight()); - containerHeightAnim.setInterpolator(ACCELERATE_INTERPOLATOR); - containerHeightAnim.setDuration(DURATION_TOPBAR); - containerHeightAnim.addUpdateListener(valueAnimator -> { - int val = (Integer) valueAnimator.getAnimatedValue(); - ViewGroup.LayoutParams layoutParams = container.getLayoutParams(); - layoutParams.height = val; - container.setLayoutParams(layoutParams); - }); - ObjectAnimator containerTransitionAnim = ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, 0, -1 * topBar.getMeasuredHeight()); - containerTransitionAnim.setInterpolator(ACCELERATE_INTERPOLATOR); - containerTransitionAnim.setDuration(DURATION_TOPBAR); - - ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, 0, -1 * topBar.getMeasuredHeight()); - topbarAnim.setInterpolator(ACCELERATE_INTERPOLATOR); - topbarAnim.setDuration(DURATION_TOPBAR); - - AnimatorSet set = new AnimatorSet(); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - ViewGroup.LayoutParams layoutParams = container.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - container.setLayoutParams(layoutParams); - container.setTranslationY(0); - - topBar.setVisibility(View.GONE); - topBar.setTranslationY(0); - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - set.playTogether(containerHeightAnim, containerTransitionAnim, topbarAnim); - set.start(); - } -} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java b/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java new file mode 100644 index 00000000000..78c4570f3c7 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java @@ -0,0 +1,82 @@ +package com.reactnativenavigation.anim; + + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import com.reactnativenavigation.views.TopBar; + +public class TopBarAnimator { + + private static final int DURATION_TOPBAR = 300; + private DecelerateInterpolator decelerateInterpolator; + private AccelerateInterpolator accelerateInterpolator; + + private TopBar topBar; + private View contentView; + + public TopBarAnimator(TopBar topBar, View contentView) { + decelerateInterpolator = new DecelerateInterpolator(); + accelerateInterpolator = new AccelerateInterpolator(); + this.topBar = topBar; + this.contentView = contentView; + } + + public void show() { + show(-1 * topBar.getMeasuredHeight(), decelerateInterpolator, DURATION_TOPBAR); + } + + public void show(float startTranslation, TimeInterpolator interpolator, int duration) { + ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, 0); + topbarAnim.setInterpolator(interpolator); + topbarAnim.setDuration(duration); + + topbarAnim.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationStart(Animator animation) { + topBar.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (contentView != null) { + ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + contentView.setLayoutParams(layoutParams); + } + } + }); + topbarAnim.start(); + } + + public void hide() { + hide(0, accelerateInterpolator, DURATION_TOPBAR); + } + + public void hide(float startTranslation, TimeInterpolator interpolator, int duration) { + ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight()); + topbarAnim.setInterpolator(interpolator); + topbarAnim.setDuration(duration); + + topbarAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (contentView != null) { + ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + contentView.setLayoutParams(layoutParams); + } + + topBar.setVisibility(View.GONE); + } + }); + topbarAnim.start(); + } +} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java b/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java new file mode 100644 index 00000000000..db028e00a94 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java @@ -0,0 +1,93 @@ +package com.reactnativenavigation.anim; + + +import android.view.View; + +import com.facebook.react.uimanager.events.EventDispatcher; +import com.reactnativenavigation.interfaces.ScrollEventListener; +import com.reactnativenavigation.utils.UiThread; +import com.reactnativenavigation.views.TopBar; + +public class TopBarCollapseBehavior { + private TopBar topBar; + + private EventDispatcher eventDispatcher; + private ScrollEventListener scrollEventListener; + private boolean dragStarted; + private TopBarAnimator animator; + + public TopBarCollapseBehavior(EventDispatcher eventDispatcher, TopBar topBar) { + this.eventDispatcher = eventDispatcher; + this.topBar = topBar; + this.animator = new TopBarAnimator(topBar, null); + } + + public void enableCollapse() { + scrollEventListener = (new ScrollEventListener(new ScrollEventListener.OnVerticalScrollListener() { + @Override + public void onVerticalScroll(int scrollY, int oldScrollY) { + if (scrollY < 0) return; + if (!dragStarted) return; + + final int scrollDiff = calcScrollDiff(scrollY, oldScrollY, topBar.getMeasuredHeight()); + final float nextTranslation = topBar.getTranslationY() - scrollDiff; + if (scrollDiff < 0) { + down(topBar.getMeasuredHeight(), nextTranslation); + } else { + up(topBar.getMeasuredHeight(), nextTranslation); + } + } + + @Override + public void onDrag(boolean started, double velocity) { + dragStarted = started; + UiThread.post(() -> { + if (!dragStarted) { + if (velocity > 0) { + animator.show(topBar.getTranslationY(), null, 100); + } else { + animator.hide(topBar.getTranslationY(), null, 100); + } + } + }); + } + })); + if (eventDispatcher != null) { + eventDispatcher.addListener(scrollEventListener); + } + } + + private void up(int measuredHeight, float nextTranslation) { + if (nextTranslation < -measuredHeight && topBar.getVisibility() == View.VISIBLE) { + topBar.setVisibility(View.GONE); + topBar.setTranslationY(-measuredHeight); + } else if (nextTranslation > -measuredHeight && nextTranslation <= 0) { + topBar.setTranslationY(nextTranslation); + } + } + + private void down(int measuredHeight, float nextTranslation) { + if (topBar.getVisibility() == View.GONE && nextTranslation > -measuredHeight) { + topBar.setVisibility(View.VISIBLE); + topBar.setTranslationY(nextTranslation); + } else if (nextTranslation <= 0 && nextTranslation >= -measuredHeight) { + topBar.setTranslationY(nextTranslation); + } + } + + private int calcScrollDiff(int scrollY, int oldScrollY, int measuredHeight) { + int diff = scrollY - oldScrollY; + if (Math.abs(diff) > measuredHeight) { + diff = (Math.abs(diff) / diff) * measuredHeight; + } + return diff; + } + + public void disableCollapse() { + if (eventDispatcher != null) { + eventDispatcher.removeListener(scrollEventListener); + } + topBar.setVisibility(View.VISIBLE); + topBar.setTranslationY(0); + } +} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java b/lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java new file mode 100644 index 00000000000..2bf3f8b4ce5 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java @@ -0,0 +1,49 @@ +package com.reactnativenavigation.interfaces; + +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.EventDispatcherListener; +import com.facebook.react.views.scroll.ScrollEvent; +import com.reactnativenavigation.utils.ReflectionUtils; + +public class ScrollEventListener implements EventDispatcherListener { + + private OnVerticalScrollListener verticalScrollListener; + private int prevScrollY = -1; + + public interface OnVerticalScrollListener { + void onVerticalScroll(int scrollY, int oldScrollY); + + void onDrag(boolean started, double velocity); + } + + public ScrollEventListener(OnVerticalScrollListener verticalScrollListener) { + this.verticalScrollListener = verticalScrollListener; + } + + @Override + public void onEventDispatch(Event event) { + if (event instanceof ScrollEvent) { + handleScrollEvent((ScrollEvent) event); + } + } + + private void handleScrollEvent(ScrollEvent event) { + try { + if ("topScroll".equals(event.getEventName())) { + int scrollY = (int) ReflectionUtils.getDeclaredField(event, "mScrollY"); + verticalScrollListener.onVerticalScroll(scrollY, prevScrollY); + if (scrollY != prevScrollY) { + prevScrollY = scrollY; + } + } else if ("topScrollBeginDrag".equals(event.getEventName())) { + double velocity = (double) ReflectionUtils.getDeclaredField(event, "mYVelocity"); + verticalScrollListener.onDrag(true, velocity); + } else if ("topScrollEndDrag".equals(event.getEventName())) { + double velocity = (double) ReflectionUtils.getDeclaredField(event, "mYVelocity"); + verticalScrollListener.onDrag(false, velocity); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java b/lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java index 928a5ea0310..7df6f289fd8 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java @@ -13,52 +13,64 @@ public class TopBarOptions implements DEFAULT_VALUES { - public static TopBarOptions parse(TypefaceLoader typefaceManager, JSONObject json) { - TopBarOptions options = new TopBarOptions(); - if (json == null) return options; + public static TopBarOptions parse(TypefaceLoader typefaceManager, JSONObject json) { + TopBarOptions options = new TopBarOptions(); + if (json == null) return options; - options.title = json.optString("title", NO_VALUE); - options.backgroundColor = json.optInt("backgroundColor", NO_COLOR_VALUE); - options.textColor = json.optInt("textColor", NO_COLOR_VALUE); - options.textFontSize = (float) json.optDouble("textFontSize", NO_FLOAT_VALUE); - options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", NO_VALUE)); - options.hidden = NavigationOptions.BooleanOptions.parse(json.optString("hidden")); - options.animateHide = NavigationOptions.BooleanOptions.parse(json.optString("animateHide")); - options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons")); - options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons")); + options.title = json.optString("title", NO_VALUE); + options.backgroundColor = json.optInt("backgroundColor", NO_COLOR_VALUE); + options.textColor = json.optInt("textColor", NO_COLOR_VALUE); + options.textFontSize = (float) json.optDouble("textFontSize", NO_FLOAT_VALUE); + options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", NO_VALUE)); + options.hidden = NavigationOptions.BooleanOptions.parse(json.optString("hidden")); + options.animateHide = NavigationOptions.BooleanOptions.parse(json.optString("animateHide")); + options.hideOnScroll = NavigationOptions.BooleanOptions.parse(json.optString("hideOnScroll")); + options.drawBehind = NavigationOptions.BooleanOptions.parse(json.optString("drawBehind")); + options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons")); + options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons")); - return options; - } + return options; + } - public String title = NO_VALUE; - @ColorInt public int backgroundColor = NO_COLOR_VALUE; - @ColorInt public int textColor = NO_COLOR_VALUE; - public float textFontSize = NO_FLOAT_VALUE; - @Nullable public Typeface textFontFamily; - public NavigationOptions.BooleanOptions hidden = NavigationOptions.BooleanOptions.False; - public NavigationOptions.BooleanOptions animateHide = NavigationOptions.BooleanOptions.False; - public ArrayList