• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.util.Log;
23 import android.view.View;
24 import android.view.ViewPropertyAnimator;
25 import android.view.ViewTreeObserver;
26 import com.android.launcher3.util.Thunk;
27 
28 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
29 
30 /*
31  *  This is a helper class that listens to updates from the corresponding animation.
32  *  For the first two frames, it adjusts the current play time of the animation to
33  *  prevent jank at the beginning of the animation
34  */
35 public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
36     implements ValueAnimator.AnimatorUpdateListener {
37     private static final String TAG = "FirstFrameAnimatorHlpr";
38     private static final boolean DEBUG = false;
39     private static final int MAX_DELAY = 1000;
40     private final View mTarget;
41     private long mStartFrame;
42     private long mStartTime = -1;
43     private boolean mHandlingOnAnimationUpdate;
44     private boolean mAdjustedSecondFrameTime;
45 
46     private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
47     @Thunk static long sGlobalFrameCounter;
48     private static boolean sVisible;
49 
FirstFrameAnimatorHelper(ValueAnimator animator, View target)50     public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
51         mTarget = target;
52         animator.addUpdateListener(this);
53     }
54 
FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target)55     public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
56         mTarget = target;
57         vpa.setListener(this);
58     }
59 
60     // only used for ViewPropertyAnimators
onAnimationStart(Animator animation)61     public void onAnimationStart(Animator animation) {
62         final ValueAnimator va = (ValueAnimator) animation;
63         va.addUpdateListener(FirstFrameAnimatorHelper.this);
64         onAnimationUpdate(va);
65     }
66 
setIsVisible(boolean visible)67     public static void setIsVisible(boolean visible) {
68         sVisible = visible;
69     }
70 
initializeDrawListener(View view)71     public static void initializeDrawListener(View view) {
72         if (sGlobalDrawListener != null) {
73             view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
74         }
75 
76         sGlobalDrawListener = () -> sGlobalFrameCounter++;
77         view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
78         sVisible = true;
79     }
80 
onAnimationUpdate(final ValueAnimator animation)81     public void onAnimationUpdate(final ValueAnimator animation) {
82         final long currentTime = System.currentTimeMillis();
83         if (mStartTime == -1) {
84             mStartFrame = sGlobalFrameCounter;
85             mStartTime = currentTime;
86         }
87 
88         final long currentPlayTime = animation.getCurrentPlayTime();
89         boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
90 
91         if (!mHandlingOnAnimationUpdate &&
92             sVisible &&
93             // If the current play time exceeds the duration, or the animated fraction is 1,
94             // the animation will get finished, even if we call setCurrentPlayTime -- therefore
95             // don't adjust the animation in that case
96             currentPlayTime < animation.getDuration() && !isFinalFrame) {
97             mHandlingOnAnimationUpdate = true;
98             long frameNum = sGlobalFrameCounter - mStartFrame;
99             // If we haven't drawn our first frame, reset the time to t = 0
100             // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
101             // are no longer in the foreground and no frames are being rendered ever)
102             if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
103                 // The first frame on animations doesn't always trigger an invalidate...
104                 // force an invalidate here to make sure the animation continues to advance
105                 mTarget.getRootView().invalidate();
106                 animation.setCurrentPlayTime(0);
107             // For the second frame, if the first frame took more than 16ms,
108             // adjust the start time and pretend it took only 16ms anyway. This
109             // prevents a large jump in the animation due to an expensive first frame
110             } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
111                        !mAdjustedSecondFrameTime &&
112                        currentTime > mStartTime + SINGLE_FRAME_MS &&
113                        currentPlayTime > SINGLE_FRAME_MS) {
114                 animation.setCurrentPlayTime(SINGLE_FRAME_MS);
115                 mAdjustedSecondFrameTime = true;
116             } else {
117                 if (frameNum > 1) {
118                     mTarget.post(new Runnable() {
119                             public void run() {
120                                 animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
121                             }
122                         });
123                 }
124                 if (DEBUG) print(animation);
125             }
126             mHandlingOnAnimationUpdate = false;
127         } else {
128             if (DEBUG) print(animation);
129         }
130     }
131 
print(ValueAnimator animation)132     public void print(ValueAnimator animation) {
133         float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
134         Log.d(TAG, sGlobalFrameCounter +
135               "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
136               mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
137     }
138 }
139