From 1558893b873cd55b2df779f594f1de3c370d3328 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Tue, 26 May 2015 23:03:31 -0700 Subject: Make sure all transition components run on the same thread -> The framework circular reveal transition runs on the render thread which can cause problems when mixed in an AnimatorSet with transitions that don't run on the render thread -> See issue 17556455 issue 21445293 Change-Id: Ie19c184c55060651e817d426ec83049b06af56ba --- src/com/android/launcher3/LauncherAnimUtils.java | 11 +++-- .../LauncherStateTransitionAnimation.java | 23 ++++----- src/com/android/launcher3/Utilities.java | 6 ++- .../launcher3/util/RevealOutlineProvider.java | 49 +++++++++++++++++++ .../launcher3/util/UiThreadCircularReveal.java | 55 ++++++++++++++++++++++ 5 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 src/com/android/launcher3/util/RevealOutlineProvider.java create mode 100644 src/com/android/launcher3/util/UiThreadCircularReveal.java diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index 6ff76665c..6a248a332 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -26,6 +26,9 @@ import android.os.Build; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewTreeObserver; + +import com.android.launcher3.util.UiThreadCircularReveal; + import java.util.HashSet; import java.util.WeakHashMap; @@ -130,13 +133,11 @@ public class LauncherAnimUtils { } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static Animator createCircularReveal(View view, int centerX, + public static ValueAnimator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) { - Animator anim = ViewAnimationUtils.createCircularReveal(view, centerX, + ValueAnimator anim = UiThreadCircularReveal.createCircularReveal(view, centerX, centerY, startRadius, endRadius); - if (anim instanceof ValueAnimator) { - new FirstFrameAnimatorHelper((ValueAnimator) anim, view); - } + new FirstFrameAnimatorHelper(anim, view); return anim; } } diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index f373fde2d..a006d141b 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -26,12 +26,14 @@ import android.annotation.SuppressLint; import android.content.res.Resources; import android.util.Log; import android.view.View; -import android.view.ViewAnimationUtils; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; + import com.android.launcher3.allapps.AllAppsContainerView; +import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; + import java.util.HashMap; /** @@ -320,7 +322,7 @@ public class LauncherStateTransitionAnimation { float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( revealView, allAppsButtonView); - Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2, + Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, height / 2, startRadius, revealRadius); reveal.setDuration(revealDuration); reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); @@ -587,14 +589,14 @@ public class LauncherStateTransitionAnimation { TimeInterpolator decelerateInterpolator = material ? new LogDecelerateInterpolator(100, 0) : new DecelerateInterpolator(1f); - ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY", + ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 0, revealViewToYDrift); panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); panelDriftY.setInterpolator(decelerateInterpolator); mStateAnimation.play(panelDriftY); - ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX", + ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 0, revealViewToXDrift); panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); @@ -605,7 +607,7 @@ public class LauncherStateTransitionAnimation { final float revealViewToAlpha = !material ? 0f : pCb.getMaterialRevealViewFinalAlpha(revealView); if (revealViewToAlpha != 1f) { - ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha", + ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 1f, revealViewToAlpha); panelAlpha.setDuration(material ? revealDuration : 150); panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); @@ -617,7 +619,7 @@ public class LauncherStateTransitionAnimation { layerViews.put(contentView, BUILD_AND_SET_LAYER); // Create the individual animators - ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(contentView, "translationY", + ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 0, revealViewToYDrift); contentView.setTranslationY(0); pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); @@ -626,7 +628,7 @@ public class LauncherStateTransitionAnimation { mStateAnimation.play(pageDrift); contentView.setAlpha(1f); - ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(contentView, "alpha", 1f, 0f); + ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); itemsAlpha.setDuration(100); itemsAlpha.setInterpolator(decelerateInterpolator); mStateAnimation.play(itemsAlpha); @@ -636,9 +638,8 @@ public class LauncherStateTransitionAnimation { float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(revealView, allAppsButtonView); - Animator reveal = - LauncherAnimUtils.createCircularReveal(revealView, width / 2, - height / 2, revealRadius, finalRadius); + Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, + height / 2, revealRadius, finalRadius); reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); reveal.setDuration(revealDuration); reveal.setStartDelay(itemsAlphaStagger); @@ -782,4 +783,4 @@ public class LauncherStateTransitionAnimation { mStateAnimation = null; } } -} \ No newline at end of file +} diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 6c4b7207b..256eba020 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -127,6 +127,11 @@ public final class Utilities { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } + public static boolean isLmpMR1OrAbove() { + // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22; + return Build.VERSION.SDK_INT >= 22; + } + static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { byte[] data = c.getBlob(iconIndex); try { @@ -588,7 +593,6 @@ public final class Utilities { } public static final Comparator RANK_COMPARATOR = new Comparator() { - @Override public int compare(ItemInfo lhs, ItemInfo rhs) { return lhs.rank - rhs.rank; diff --git a/src/com/android/launcher3/util/RevealOutlineProvider.java b/src/com/android/launcher3/util/RevealOutlineProvider.java new file mode 100644 index 000000000..0db3984f8 --- /dev/null +++ b/src/com/android/launcher3/util/RevealOutlineProvider.java @@ -0,0 +1,49 @@ +package com.android.launcher3.util; + +import android.annotation.TargetApi; +import android.graphics.Outline; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.view.ViewOutlineProvider; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class RevealOutlineProvider extends ViewOutlineProvider { + + private int mCenterX; + private int mCenterY; + private float mRadius0; + private float mRadius1; + private int mCurrentRadius; + + private final Rect mOval; + + /** + * @param x reveal center x + * @param y reveal center y + * @param r0 initial radius + * @param r1 final radius + */ + public RevealOutlineProvider(int x, int y, float r0, float r1) { + mCenterX = x; + mCenterY = y; + mRadius0 = r0; + mRadius1 = r1; + + mOval = new Rect(); + } + + public void setProgress(float progress) { + mCurrentRadius = (int) ((1 - progress) * mRadius0 + progress * mRadius1); + + mOval.left = mCenterX - mCurrentRadius; + mOval.top = mCenterY - mCurrentRadius; + mOval.right = mCenterX + mCurrentRadius; + mOval.bottom = mCenterY + mCurrentRadius; + } + + @Override + public void getOutline(View v, Outline outline) { + outline.setRoundRect(mOval, mCurrentRadius); + } +} diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java new file mode 100644 index 000000000..c7324fb1b --- /dev/null +++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java @@ -0,0 +1,55 @@ +package com.android.launcher3.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.os.Build; +import android.view.View; +import android.view.ViewOutlineProvider; + +import com.android.launcher3.Utilities; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class UiThreadCircularReveal { + + public static ValueAnimator createCircularReveal(View v, int x, int y, float r0, float r1) { + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + + final View revealView = v; + final RevealOutlineProvider outlineProvider = new RevealOutlineProvider(x, y, r0, r1); + final ViewOutlineProvider originalProvider = revealView.getOutlineProvider(); + final float elevation = v.getElevation(); + + va.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + revealView.setOutlineProvider(outlineProvider); + revealView.setClipToOutline(true); + revealView.setTranslationZ(-elevation); + } + + public void onAnimationEnd(Animator animation) { + revealView.setOutlineProvider(originalProvider); + revealView.setClipToOutline(false); + revealView.setTranslationZ(0); + } + + }); + + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator arg0) { + float progress = arg0.getAnimatedFraction(); + outlineProvider.setProgress(progress); + if (Utilities.isLmpMR1OrAbove()) { + revealView.invalidateOutline(); + } else { + // On L, a bug requires calling a full view invalidate. + revealView.invalidate(); + } + } + }); + return va; + } +} -- cgit v1.2.3