Home | History | Annotate | Download | only in anim
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.launcher3.anim;
     17 
     18 import android.animation.Animator;
     19 import android.animation.Animator.AnimatorListener;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.TimeInterpolator;
     23 import android.animation.ValueAnimator;
     24 
     25 import java.util.ArrayList;
     26 import java.util.Collections;
     27 import java.util.List;
     28 
     29 /**
     30  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
     31  * and durations.
     32  *
     33  * Note: The implementation does not support start delays on child animations or
     34  * sequential playbacks.
     35  */
     36 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
     37 
     38     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
     39         return wrap(anim, duration, null);
     40     }
     41 
     42     /**
     43      * Creates an animation controller for the provided animation.
     44      * The actual duration does not matter as the animation is manually controlled. It just
     45      * needs to be larger than the total number of pixels so that we don't have jittering due
     46      * to float (animation-fraction * total duration) to int conversion.
     47      */
     48     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
     49             Runnable onCancelRunnable) {
     50 
     51         /**
     52          * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
     53          */
     54         return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
     55     }
     56 
     57     private final ValueAnimator mAnimationPlayer;
     58     private final long mDuration;
     59 
     60     protected final AnimatorSet mAnim;
     61 
     62     protected float mCurrentFraction;
     63     private Runnable mEndAction;
     64 
     65     protected boolean mTargetCancelled = false;
     66     protected Runnable mOnCancelRunnable;
     67 
     68     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
     69             Runnable onCancelRunnable) {
     70         mAnim = anim;
     71         mDuration = duration;
     72         mOnCancelRunnable = onCancelRunnable;
     73 
     74         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
     75         mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
     76         mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
     77         mAnimationPlayer.addUpdateListener(this);
     78 
     79         mAnim.addListener(new AnimatorListenerAdapter() {
     80             @Override
     81             public void onAnimationCancel(Animator animation) {
     82                 mTargetCancelled = true;
     83                 if (mOnCancelRunnable != null) {
     84                     mOnCancelRunnable.run();
     85                     mOnCancelRunnable = null;
     86                 }
     87             }
     88 
     89             @Override
     90             public void onAnimationEnd(Animator animation) {
     91                 mTargetCancelled = false;
     92                 mOnCancelRunnable = null;
     93             }
     94 
     95             @Override
     96             public void onAnimationStart(Animator animation) {
     97                 mTargetCancelled = false;
     98             }
     99         });
    100     }
    101 
    102     public AnimatorSet getTarget() {
    103         return mAnim;
    104     }
    105 
    106     public long getDuration() {
    107         return mDuration;
    108     }
    109 
    110     /**
    111      * Starts playing the animation forward from current position.
    112      */
    113     public void start() {
    114         mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
    115         mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
    116         mAnimationPlayer.start();
    117     }
    118 
    119     /**
    120      * Starts playing the animation backwards from current position
    121      */
    122     public void reverse() {
    123         mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
    124         mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
    125         mAnimationPlayer.start();
    126     }
    127 
    128     /**
    129      * Pauses the currently playing animation.
    130      */
    131     public void pause() {
    132         mAnimationPlayer.cancel();
    133     }
    134 
    135     /**
    136      * Returns the underlying animation used for controlling the set.
    137      */
    138     public ValueAnimator getAnimationPlayer() {
    139         return mAnimationPlayer;
    140     }
    141 
    142     /**
    143      * Sets the current animation position and updates all the child animators accordingly.
    144      */
    145     public abstract void setPlayFraction(float fraction);
    146 
    147     public float getProgressFraction() {
    148         return mCurrentFraction;
    149     }
    150 
    151     /**
    152      * Sets the action to be called when the animation is completed. Also clears any
    153      * previously set action.
    154      */
    155     public void setEndAction(Runnable runnable) {
    156         mEndAction = runnable;
    157     }
    158 
    159     @Override
    160     public void onAnimationUpdate(ValueAnimator valueAnimator) {
    161         setPlayFraction((float) valueAnimator.getAnimatedValue());
    162     }
    163 
    164     protected long clampDuration(float fraction) {
    165         float playPos = mDuration * fraction;
    166         if (playPos <= 0) {
    167             return 0;
    168         } else {
    169             return Math.min((long) playPos, mDuration);
    170         }
    171     }
    172 
    173     public void dispatchOnStart() {
    174         dispatchOnStartRecursively(mAnim);
    175     }
    176 
    177     private void dispatchOnStartRecursively(Animator animator) {
    178         for (AnimatorListener l : nonNullList(animator.getListeners())) {
    179             l.onAnimationStart(animator);
    180         }
    181 
    182         if (animator instanceof AnimatorSet) {
    183             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
    184                 dispatchOnStartRecursively(anim);
    185             }
    186         }
    187     }
    188 
    189     public void dispatchOnCancel() {
    190         dispatchOnCancelRecursively(mAnim);
    191     }
    192 
    193     private void dispatchOnCancelRecursively(Animator animator) {
    194         for (AnimatorListener l : nonNullList(animator.getListeners())) {
    195             l.onAnimationCancel(animator);
    196         }
    197 
    198         if (animator instanceof AnimatorSet) {
    199             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
    200                 dispatchOnCancelRecursively(anim);
    201             }
    202         }
    203     }
    204 
    205     public void setOnCancelRunnable(Runnable runnable) {
    206         mOnCancelRunnable = runnable;
    207     }
    208 
    209     public Runnable getOnCancelRunnable() {
    210         return mOnCancelRunnable;
    211     }
    212 
    213     public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
    214 
    215         private final ValueAnimator[] mChildAnimations;
    216 
    217         private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
    218                 Runnable onCancelRunnable) {
    219             super(anim, duration, onCancelRunnable);
    220 
    221             // Build animation list
    222             ArrayList<ValueAnimator> childAnims = new ArrayList<>();
    223             getAnimationsRecur(mAnim, childAnims);
    224             mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
    225         }
    226 
    227         private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
    228             long forceDuration = anim.getDuration();
    229             TimeInterpolator forceInterpolator = anim.getInterpolator();
    230             for (Animator child : anim.getChildAnimations()) {
    231                 if (forceDuration > 0) {
    232                     child.setDuration(forceDuration);
    233                 }
    234                 if (forceInterpolator != null) {
    235                     child.setInterpolator(forceInterpolator);
    236                 }
    237                 if (child instanceof ValueAnimator) {
    238                     out.add((ValueAnimator) child);
    239                 } else if (child instanceof AnimatorSet) {
    240                     getAnimationsRecur((AnimatorSet) child, out);
    241                 } else {
    242                     throw new RuntimeException("Unknown animation type " + child);
    243                 }
    244             }
    245         }
    246 
    247         @Override
    248         public void setPlayFraction(float fraction) {
    249             mCurrentFraction = fraction;
    250             // Let the animator report the progress but don't apply the progress to child
    251             // animations if it has been cancelled.
    252             if (mTargetCancelled) {
    253                 return;
    254             }
    255             long playPos = clampDuration(fraction);
    256             for (ValueAnimator anim : mChildAnimations) {
    257                 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
    258             }
    259         }
    260     }
    261 
    262     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
    263 
    264         @Override
    265         public void onAnimationStart(Animator animation) {
    266             mCancelled = false;
    267         }
    268 
    269         @Override
    270         public void onAnimationSuccess(Animator animator) {
    271             dispatchOnEndRecursively(mAnim);
    272             if (mEndAction != null) {
    273                 mEndAction.run();
    274             }
    275         }
    276 
    277         private void dispatchOnEndRecursively(Animator animator) {
    278             for (AnimatorListener l : nonNullList(animator.getListeners())) {
    279                 l.onAnimationEnd(animator);
    280             }
    281 
    282             if (animator instanceof AnimatorSet) {
    283                 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
    284                     dispatchOnEndRecursively(anim);
    285                 }
    286             }
    287         }
    288     }
    289 
    290     private static <T> List<T> nonNullList(ArrayList<T> list) {
    291         return list == null ? Collections.emptyList() : list;
    292     }
    293 }
    294