From 2ca8076b0a055e348f338870c9cbcae4560ff196 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Wed, 30 Nov 2016 05:03:05 +0000 Subject: [PATCH] Merge \"Fix FloatingActionButton hide() and show()\" into nyc-support-24.1-dev am: dfc69f69f1 Change-Id: I1d26458467f4fcd154c179c3ce5e2810d0ae69e5 GitOrigin-RevId=49886f03824b46ae35cd649fddc85d26d1da7372 PiperOrigin-RevId: 140564038 --- .../widget/FloatingActionButtonIcs.java | 139 +++++++++++------- .../FloatingActionButtonActions.java | 50 +++++++ .../widget/FloatingActionButtonTest.java | 21 +++ 3 files changed, 158 insertions(+), 52 deletions(-) diff --git a/ics/android/support/design/widget/FloatingActionButtonIcs.java b/ics/android/support/design/widget/FloatingActionButtonIcs.java index 57ad15ca7..2ead4c41e 100644 --- a/ics/android/support/design/widget/FloatingActionButtonIcs.java +++ b/ics/android/support/design/widget/FloatingActionButtonIcs.java @@ -25,7 +25,11 @@ import android.view.View; class FloatingActionButtonIcs extends FloatingActionButtonGingerbread { - private boolean mIsHiding; + private static final int ANIM_STATE_NONE = 0; + private static final int ANIM_STATE_HIDING = 1; + private static final int ANIM_STATE_SHOWING = 2; + + private int mAnimState = ANIM_STATE_NONE; private float mRotation; FloatingActionButtonIcs(VisibilityAwareImageButton view, @@ -50,22 +54,16 @@ class FloatingActionButtonIcs extends FloatingActionButtonGingerbread { @Override void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { - if (mIsHiding || mView.getVisibility() != View.VISIBLE) { - // A hide animation is in progress, or we're already hidden. Skip the call - if (listener != null) { - listener.onHidden(); - } + if (isOrWillBeHidden()) { + // We either are or will soon be hidden, skip the call return; } - if (!ViewCompat.isLaidOut(mView) || mView.isInEditMode()) { - // If the view isn't laid out, or we're in the editor, don't run the animation - mView.internalSetVisibility(View.GONE, fromUser); - if (listener != null) { - listener.onHidden(); - } - } else { - mView.animate().cancel(); + mView.animate().cancel(); + + if (shouldAnimateVisibilityChange()) { + mAnimState = ANIM_STATE_HIDING; + mView.animate() .scaleX(0f) .scaleY(0f) @@ -77,20 +75,19 @@ class FloatingActionButtonIcs extends FloatingActionButtonGingerbread { @Override public void onAnimationStart(Animator animation) { - mIsHiding = true; - mCancelled = false; mView.internalSetVisibility(View.VISIBLE, fromUser); + mCancelled = false; } @Override public void onAnimationCancel(Animator animation) { - mIsHiding = false; mCancelled = true; } @Override public void onAnimationEnd(Animator animation) { - mIsHiding = false; + mAnimState = ANIM_STATE_NONE; + if (!mCancelled) { mView.internalSetVisibility(View.GONE, fromUser); if (listener != null) { @@ -99,51 +96,89 @@ class FloatingActionButtonIcs extends FloatingActionButtonGingerbread { } } }); + } else { + // If the view isn't laid out, or we're in the editor, don't run the animation + mView.internalSetVisibility(View.GONE, fromUser); + if (listener != null) { + listener.onHidden(); + } } } @Override void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { - if (mIsHiding || mView.getVisibility() != View.VISIBLE) { - if (ViewCompat.isLaidOut(mView) && !mView.isInEditMode()) { - mView.animate().cancel(); - if (mView.getVisibility() != View.VISIBLE) { - // If the view isn't visible currently, we'll animate it from a single pixel - mView.setAlpha(0f); - mView.setScaleY(0f); - mView.setScaleX(0f); - } - mView.animate() - .scaleX(1f) - .scaleY(1f) - .alpha(1f) - .setDuration(SHOW_HIDE_ANIM_DURATION) - .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mView.internalSetVisibility(View.VISIBLE, fromUser); - } + if (isOrWillBeShown()) { + // We either are or will soon be visible, skip the call + return; + } - @Override - public void onAnimationEnd(Animator animation) { - if (listener != null) { - listener.onShown(); - } + mView.animate().cancel(); + + if (shouldAnimateVisibilityChange()) { + mAnimState = ANIM_STATE_SHOWING; + + if (mView.getVisibility() != View.VISIBLE) { + // If the view isn't visible currently, we'll animate it from a single pixel + mView.setAlpha(0f); + mView.setScaleY(0f); + mView.setScaleX(0f); + } + + mView.animate() + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .setDuration(SHOW_HIDE_ANIM_DURATION) + .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mView.internalSetVisibility(View.VISIBLE, fromUser); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimState = ANIM_STATE_NONE; + if (listener != null) { + listener.onShown(); } - }); - } else { - mView.internalSetVisibility(View.VISIBLE, fromUser); - mView.setAlpha(1f); - mView.setScaleY(1f); - mView.setScaleX(1f); - if (listener != null) { - listener.onShown(); - } + } + }); + } else { + mView.internalSetVisibility(View.VISIBLE, fromUser); + mView.setAlpha(1f); + mView.setScaleY(1f); + mView.setScaleX(1f); + if (listener != null) { + listener.onShown(); } } } + private boolean isOrWillBeShown() { + if (mView.getVisibility() != View.VISIBLE) { + // If we not currently visible, return true if we're animating to be shown + return mAnimState == ANIM_STATE_SHOWING; + } else { + // Otherwise if we're visible, return true if we're not animating to be hidden + return mAnimState != ANIM_STATE_HIDING; + } + } + + private boolean isOrWillBeHidden() { + if (mView.getVisibility() == View.VISIBLE) { + // If we currently visible, return true if we're animating to be hidden + return mAnimState == ANIM_STATE_HIDING; + } else { + // Otherwise if we're not visible, return true if we're not animating to be shown + return mAnimState != ANIM_STATE_SHOWING; + } + } + + private boolean shouldAnimateVisibilityChange() { + return ViewCompat.isLaidOut(mView) && !mView.isInEditMode(); + } + private void updateFromViewRotation() { if (Build.VERSION.SDK_INT == 19) { // KitKat seems to have an issue with views which are rotated with angles which are diff --git a/tests/src/android/support/design/testutils/FloatingActionButtonActions.java b/tests/src/android/support/design/testutils/FloatingActionButtonActions.java index ad8068b4c..59b0d48f5 100644 --- a/tests/src/android/support/design/testutils/FloatingActionButtonActions.java +++ b/tests/src/android/support/design/testutils/FloatingActionButtonActions.java @@ -129,4 +129,54 @@ public class FloatingActionButtonActions { }; } + public static ViewAction hideThenShow(final int animDuration) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isAssignableFrom(FloatingActionButton.class); + } + + @Override + public String getDescription() { + return "Calls hide() then show()"; + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + + FloatingActionButton fab = (FloatingActionButton) view; + fab.hide(); + fab.show(); + + uiController.loopMainThreadForAtLeast(animDuration + 100); + } + }; + } + + public static ViewAction showThenHide(final int animDuration) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isAssignableFrom(FloatingActionButton.class); + } + + @Override + public String getDescription() { + return "Calls show() then hide()"; + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + + FloatingActionButton fab = (FloatingActionButton) view; + fab.show(); + fab.hide(); + + uiController.loopMainThreadForAtLeast(animDuration + 50); + } + }; + } + } diff --git a/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/tests/src/android/support/design/widget/FloatingActionButtonTest.java index 69277320e..417fa35f5 100644 --- a/tests/src/android/support/design/widget/FloatingActionButtonTest.java +++ b/tests/src/android/support/design/widget/FloatingActionButtonTest.java @@ -16,15 +16,19 @@ package android.support.design.widget; +import static android.support.design.testutils.FloatingActionButtonActions.hideThenShow; import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintColor; import static android.support.design.testutils.FloatingActionButtonActions.setImageResource; import static android.support.design.testutils.FloatingActionButtonActions.setLayoutGravity; import static android.support.design.testutils.FloatingActionButtonActions.setSize; +import static android.support.design.testutils.FloatingActionButtonActions.showThenHide; import static android.support.design.testutils.TestUtilsMatchers.withFabBackgroundFill; import static android.support.design.testutils.TestUtilsMatchers.withFabContentAreaOnMargins; import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight; +import static android.support.design.widget.DesignViewActions.setVisibility; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import android.graphics.Color; @@ -32,6 +36,7 @@ import android.support.design.test.R; import android.support.design.testutils.TestUtils; import android.test.suitebuilder.annotation.SmallTest; import android.view.Gravity; +import android.view.View; import org.junit.Test; @@ -114,4 +119,20 @@ public class FloatingActionButtonTest .check(matches(withFabContentAreaOnMargins(Gravity.RIGHT | Gravity.BOTTOM))); } + @Test + public void testHideShow() { + onView(withId(R.id.fab_standard)) + .perform(setVisibility(View.VISIBLE)) + .perform(hideThenShow(FloatingActionButtonImpl.SHOW_HIDE_ANIM_DURATION)) + .check(matches(isDisplayed())); + } + + @Test + public void testShowHide() { + onView(withId(R.id.fab_standard)) + .perform(setVisibility(View.GONE)) + .perform(showThenHide(FloatingActionButtonImpl.SHOW_HIDE_ANIM_DURATION)) + .check(matches(not(isDisplayed()))); + } + }