1 package com.android.launcher3.anim; 2 3 import android.animation.Animator; 4 import android.animation.AnimatorListenerAdapter; 5 import android.animation.ValueAnimator; 6 import android.graphics.Outline; 7 import android.graphics.Rect; 8 import android.view.View; 9 import android.view.ViewOutlineProvider; 10 11 /** 12 * A {@link ViewOutlineProvider} that has helper functions to create reveal animations. 13 * This class should be extended so that subclasses can define the reveal shape as the 14 * animation progresses from 0 to 1. 15 */ 16 public abstract class RevealOutlineAnimation extends ViewOutlineProvider { 17 protected Rect mOutline; 18 protected float mOutlineRadius; 19 RevealOutlineAnimation()20 public RevealOutlineAnimation() { 21 mOutline = new Rect(); 22 } 23 24 /** Returns whether elevation should be removed for the duration of the reveal animation. */ shouldRemoveElevationDuringAnimation()25 abstract boolean shouldRemoveElevationDuringAnimation(); 26 /** Sets the progress, from 0 to 1, of the reveal animation. */ setProgress(float progress)27 abstract void setProgress(float progress); 28 29 /** 30 * @see #createRevealAnimator(View, boolean, float) where startProgress is set to 0. 31 */ createRevealAnimator(final View revealView, boolean isReversed)32 public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) { 33 return createRevealAnimator(revealView, isReversed, 0f /* startProgress */); 34 } 35 36 /** 37 * Animates the given View's ViewOutline according to {@link #setProgress(float)}. 38 * @param revealView The View whose outline we are animating. 39 * @param isReversed Whether we are hiding rather than revealing the View. 40 * @param startProgress The progress at which to start the newly created animation. Useful if 41 * the previous reveal animation was cancelled and we want to create a new animation where it 42 * left off. Note that if isReversed=true, we start at 1 - startProgress (and go to 0). 43 * @return The Animator, which the caller must start. 44 */ createRevealAnimator(final View revealView, boolean isReversed, float startProgress)45 public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed, 46 float startProgress) { 47 ValueAnimator va = isReversed 48 ? ValueAnimator.ofFloat(1f - startProgress, 0f) 49 : ValueAnimator.ofFloat(startProgress, 1f); 50 final float elevation = revealView.getElevation(); 51 52 va.addListener(new AnimatorListenerAdapter() { 53 private boolean mIsClippedToOutline; 54 private ViewOutlineProvider mOldOutlineProvider; 55 56 public void onAnimationStart(Animator animation) { 57 mIsClippedToOutline = revealView.getClipToOutline(); 58 mOldOutlineProvider = revealView.getOutlineProvider(); 59 60 revealView.setOutlineProvider(RevealOutlineAnimation.this); 61 revealView.setClipToOutline(true); 62 if (shouldRemoveElevationDuringAnimation()) { 63 revealView.setTranslationZ(-elevation); 64 } 65 } 66 67 public void onAnimationEnd(Animator animation) { 68 revealView.setOutlineProvider(mOldOutlineProvider); 69 revealView.setClipToOutline(mIsClippedToOutline); 70 if (shouldRemoveElevationDuringAnimation()) { 71 revealView.setTranslationZ(0); 72 } 73 } 74 75 }); 76 77 va.addUpdateListener(v -> { 78 float progress = (Float) v.getAnimatedValue(); 79 setProgress(progress); 80 revealView.invalidateOutline(); 81 }); 82 return va; 83 } 84 85 @Override getOutline(View v, Outline outline)86 public void getOutline(View v, Outline outline) { 87 outline.setRoundRect(mOutline, mOutlineRadius); 88 } 89 getRadius()90 public float getRadius() { 91 return mOutlineRadius; 92 } 93 getOutline(Rect out)94 public void getOutline(Rect out) { 95 out.set(mOutline); 96 } 97 } 98