Support design - fix FloatingActionButton elevation
This bug has been fixed in the framework for api 25+ but
a between 21-24 still triggers a NPE when an animation has
a delay set (via "setDelay" or "after"). The current
workaround is introducing a no-op animation which mimics
the delay on the following one by having a duration.
Bug: 32136707
Test: ./gradlew support-design:connectedCheck --info \
--daemon -Pandroid.testInstrumentationRunnerArg\
uments.class=android.support.design.widget.Float\
ingActionButtonTest
Change-Id: I75722d6e142c0d75170ef912b261c1d7a9b55a1e
diff --git a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
index d5fe912..506f459 100644
--- a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
+++ b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
@@ -16,7 +16,6 @@
package android.support.design.widget;
-import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.StateListAnimator;
@@ -31,8 +30,6 @@
import android.os.Build;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class FloatingActionButtonLollipop extends FloatingActionButtonIcs {
@@ -102,14 +99,17 @@
// Animate translationZ to 0 if not pressed
set = new AnimatorSet();
- // Use an AnimatorSet to set a start delay since there is a bug with ValueAnimator that
- // prevents it from being cancelled properly when used with a StateListAnimator.
- AnimatorSet anim = new AnimatorSet();
- anim.play(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f)
- .setDuration(PRESSED_ANIM_DURATION))
- .after(PRESSED_ANIM_DURATION);
- set.play(ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0))
- .with(anim);
+ set.playSequentially(
+ ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0),
+ // This is a no-op animation which exists here only for introducing the duration
+ // because setting the delay (on the next animation) via "setDelay" or "after" can
+ // trigger a NPE between android versions 21 and 24 (due to a framework bug). The
+ // issue has been fixed in version 25.
+ ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, mView.getTranslationZ())
+ .setDuration(PRESSED_ANIM_DELAY),
+ ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f)
+ .setDuration(PRESSED_ANIM_DURATION));
+
set.setInterpolator(ANIM_INTERPOLATOR);
stateListAnimator.addState(ENABLED_STATE_SET, set);
diff --git a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
index 59b0d48..2716165 100644
--- a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
+++ b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
@@ -103,6 +103,30 @@
};
}
+ public static ViewAction setCompatElevation(final float size) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(FloatingActionButton.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets FloatingActionButton elevation";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+ fab.setCompatElevation(size);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
public static ViewAction setLayoutGravity(final int gravity) {
return new ViewAction() {
@Override
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
index 857121e..248b9df 100644
--- a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
@@ -18,10 +18,12 @@
import static android.support.design.testutils.FloatingActionButtonActions.hideThenShow;
import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintColor;
+import static android.support.design.testutils.FloatingActionButtonActions.setCompatElevation;
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.TestUtilsActions.setEnabled;
import static android.support.design.testutils.TestUtilsMatchers.withFabBackgroundFill;
import static android.support.design.testutils.TestUtilsMatchers.withFabContentAreaOnMargins;
import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight;
@@ -137,4 +139,14 @@
.check(matches(not(isDisplayed())));
}
+ @Test
+ public void testSetCompatElevation() {
+ onView(withId(R.id.fab_standard))
+ .perform(setEnabled(false))
+ .perform(setCompatElevation(0));
+
+ onView(withId(R.id.fab_standard))
+ .perform(setEnabled(true))
+ .perform(setCompatElevation(8));
+ }
}