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 package com.android.launcher3; 17 18 import static com.android.launcher3.util.DisplayController.getSingleFrameMs; 19 20 import android.animation.ValueAnimator; 21 import android.animation.ValueAnimator.AnimatorUpdateListener; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.View.OnAttachStateChangeListener; 25 import android.view.ViewTreeObserver.OnDrawListener; 26 27 /* 28 * This is a helper class that listens to updates from the corresponding animation. 29 * For the first two frames, it adjusts the current play time of the animation to 30 * prevent jank at the beginning of the animation 31 */ 32 public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateChangeListener { 33 34 private static final String TAG = "FirstFrameAnimatorHlpr"; 35 private static final boolean DEBUG = false; 36 private static final int MAX_DELAY = 1000; 37 38 private View mRootView; 39 private long mGlobalFrameCount; 40 FirstFrameAnimatorHelper(View target)41 public FirstFrameAnimatorHelper(View target) { 42 target.addOnAttachStateChangeListener(this); 43 if (target.isAttachedToWindow()) { 44 onViewAttachedToWindow(target); 45 } 46 } 47 addTo(T anim)48 public <T extends ValueAnimator> T addTo(T anim) { 49 anim.addUpdateListener(new MyListener()); 50 return anim; 51 } 52 53 @Override onDraw()54 public void onDraw() { 55 mGlobalFrameCount ++; 56 } 57 58 @Override onViewAttachedToWindow(View view)59 public void onViewAttachedToWindow(View view) { 60 mRootView = view.getRootView(); 61 mRootView.getViewTreeObserver().addOnDrawListener(this); 62 } 63 64 @Override onViewDetachedFromWindow(View view)65 public void onViewDetachedFromWindow(View view) { 66 if (mRootView != null) { 67 mRootView.getViewTreeObserver().removeOnDrawListener(this); 68 mRootView = null; 69 } 70 } 71 72 private class MyListener implements AnimatorUpdateListener { 73 74 private long mStartFrame; 75 private long mStartTime = -1; 76 private boolean mHandlingOnAnimationUpdate; 77 private boolean mAdjustedSecondFrameTime; 78 79 @Override onAnimationUpdate(final ValueAnimator animation)80 public void onAnimationUpdate(final ValueAnimator animation) { 81 final long currentTime = System.currentTimeMillis(); 82 if (mStartTime == -1) { 83 mStartFrame = mGlobalFrameCount; 84 mStartTime = currentTime; 85 } 86 87 final long currentPlayTime = animation.getCurrentPlayTime(); 88 boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0; 89 90 if (!mHandlingOnAnimationUpdate && 91 mRootView != null && 92 mRootView.getWindowVisibility() == View.VISIBLE && 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 -- 95 // therefore don't adjust the animation in that case 96 currentPlayTime < animation.getDuration() && !isFinalFrame) { 97 mHandlingOnAnimationUpdate = true; 98 long frameNum = mGlobalFrameCount - mStartFrame; 99 100 // If we haven't drawn our first frame, reset the time to t = 0 101 // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we 102 // are no longer in the foreground and no frames are being rendered ever) 103 if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) { 104 // The first frame on animations doesn't always trigger an invalidate... 105 // force an invalidate here to make sure the animation continues to advance 106 mRootView.invalidate(); 107 animation.setCurrentPlayTime(0); 108 // For the second frame, if the first frame took more than 16ms, 109 // adjust the start time and pretend it took only 16ms anyway. This 110 // prevents a large jump in the animation due to an expensive first frame 111 } else { 112 int singleFrameMS = getSingleFrameMs(mRootView.getContext()); 113 if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY && 114 !mAdjustedSecondFrameTime && 115 currentTime > mStartTime + singleFrameMS && 116 currentPlayTime > singleFrameMS) { 117 animation.setCurrentPlayTime(singleFrameMS); 118 mAdjustedSecondFrameTime = true; 119 } else { 120 if (frameNum > 1) { 121 mRootView.post(() -> animation.removeUpdateListener(this)); 122 } 123 if (DEBUG) print(animation); 124 } 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, mGlobalFrameCount + 135 "(" + (mGlobalFrameCount - mStartFrame) + ") " + mRootView + " dirty? " + 136 mRootView.isDirty() + " " + flatFraction + " " + this + " " + animation); 137 } 138 } 139 } 140