blob: a51ddd4b882756bb411a1a79d5c79afb433fef53 [file] [log] [blame]
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001/*
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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010018
Michael Jurka39b599e2013-04-08 18:28:15 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010021import android.animation.ValueAnimator;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010022import android.util.Log;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010023import android.view.View;
Michael Jurka39b599e2013-04-08 18:28:15 -070024import android.view.ViewPropertyAnimator;
Michael Jurka032e6ba2013-04-22 15:08:42 +020025import android.view.ViewTreeObserver;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010026
Adam Cohen091440a2015-03-18 14:16:05 -070027import com.android.launcher3.util.Thunk;
28
Michael Jurkaf1ad6082013-03-13 12:55:46 +010029/*
30 * This is a helper class that listens to updates from the corresponding animation.
31 * For the first two frames, it adjusts the current play time of the animation to
32 * prevent jank at the beginning of the animation
33 */
Michael Jurka39b599e2013-04-08 18:28:15 -070034public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
35 implements ValueAnimator.AnimatorUpdateListener {
Michael Jurkaf1ad6082013-03-13 12:55:46 +010036 private static final boolean DEBUG = false;
Michael Jurka39b599e2013-04-08 18:28:15 -070037 private static final int MAX_DELAY = 1000;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010038 private static final int IDEAL_FRAME_DURATION = 16;
39 private View mTarget;
40 private long mStartFrame;
41 private long mStartTime = -1;
42 private boolean mHandlingOnAnimationUpdate;
43 private boolean mAdjustedSecondFrameTime;
44
45 private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
Adam Cohen091440a2015-03-18 14:16:05 -070046 @Thunk static long sGlobalFrameCounter;
Michael Jurkacd496d72013-04-11 11:32:45 -070047 private static boolean sVisible;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010048
49 public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
50 mTarget = target;
51 animator.addUpdateListener(this);
52 }
53
Michael Jurka39b599e2013-04-08 18:28:15 -070054 public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
55 mTarget = target;
56 vpa.setListener(this);
57 }
58
59 // only used for ViewPropertyAnimators
60 public void onAnimationStart(Animator animation) {
61 final ValueAnimator va = (ValueAnimator) animation;
62 va.addUpdateListener(FirstFrameAnimatorHelper.this);
63 onAnimationUpdate(va);
64 }
65
Michael Jurkacd496d72013-04-11 11:32:45 -070066 public static void setIsVisible(boolean visible) {
67 sVisible = visible;
68 }
69
Michael Jurkaf1ad6082013-03-13 12:55:46 +010070 public static void initializeDrawListener(View view) {
Michael Jurka39b599e2013-04-08 18:28:15 -070071 if (sGlobalDrawListener != null) {
72 view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
73 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +010074 sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
75 private long mTime = System.currentTimeMillis();
76 public void onDraw() {
77 sGlobalFrameCounter++;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010078 if (DEBUG) {
Michael Jurka39b599e2013-04-08 18:28:15 -070079 long newTime = System.currentTimeMillis();
Michael Jurkaf1ad6082013-03-13 12:55:46 +010080 Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
Michael Jurka39b599e2013-04-08 18:28:15 -070081 mTime = newTime;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010082 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +010083 }
84 };
85 view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
Michael Jurkacd496d72013-04-11 11:32:45 -070086 sVisible = true;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010087 }
88
Michael Jurka98720c92013-03-28 13:39:35 -070089 public void onAnimationUpdate(final ValueAnimator animation) {
Michael Jurka39b599e2013-04-08 18:28:15 -070090 final long currentTime = System.currentTimeMillis();
Michael Jurkaf1ad6082013-03-13 12:55:46 +010091 if (mStartTime == -1) {
92 mStartFrame = sGlobalFrameCounter;
Michael Jurka39b599e2013-04-08 18:28:15 -070093 mStartTime = currentTime;
Michael Jurkaf1ad6082013-03-13 12:55:46 +010094 }
95
Michael Jurka19d10a52013-11-06 17:06:57 +010096 final long currentPlayTime = animation.getCurrentPlayTime();
Adam Cohen5c18b802014-12-02 15:24:52 -080097 boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
98
Michael Jurka39b599e2013-04-08 18:28:15 -070099 if (!mHandlingOnAnimationUpdate &&
Michael Jurkacd496d72013-04-11 11:32:45 -0700100 sVisible &&
Adam Cohen5c18b802014-12-02 15:24:52 -0800101 // If the current play time exceeds the duration, or the animated fraction is 1,
102 // the animation will get finished, even if we call setCurrentPlayTime -- therefore
Michael Jurka39b599e2013-04-08 18:28:15 -0700103 // don't adjust the animation in that case
Adam Cohen5c18b802014-12-02 15:24:52 -0800104 currentPlayTime < animation.getDuration() && !isFinalFrame) {
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100105 mHandlingOnAnimationUpdate = true;
106 long frameNum = sGlobalFrameCounter - mStartFrame;
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100107 // If we haven't drawn our first frame, reset the time to t = 0
Michael Jurka39b599e2013-04-08 18:28:15 -0700108 // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
109 // are no longer in the foreground and no frames are being rendered ever)
Michael Jurka19d10a52013-11-06 17:06:57 +0100110 if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
Michael Jurka39b599e2013-04-08 18:28:15 -0700111 // The first frame on animations doesn't always trigger an invalidate...
112 // force an invalidate here to make sure the animation continues to advance
113 mTarget.getRootView().invalidate();
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100114 animation.setCurrentPlayTime(0);
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100115 // For the second frame, if the first frame took more than 16ms,
116 // adjust the start time and pretend it took only 16ms anyway. This
117 // prevents a large jump in the animation due to an expensive first frame
Michael Jurka39b599e2013-04-08 18:28:15 -0700118 } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
119 !mAdjustedSecondFrameTime &&
Michael Jurka19d10a52013-11-06 17:06:57 +0100120 currentTime > mStartTime + IDEAL_FRAME_DURATION &&
121 currentPlayTime > IDEAL_FRAME_DURATION) {
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100122 animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
123 mAdjustedSecondFrameTime = true;
124 } else {
125 if (frameNum > 1) {
Michael Jurka98720c92013-03-28 13:39:35 -0700126 mTarget.post(new Runnable() {
127 public void run() {
128 animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
129 }
130 });
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100131 }
132 if (DEBUG) print(animation);
133 }
134 mHandlingOnAnimationUpdate = false;
135 } else {
136 if (DEBUG) print(animation);
137 }
138 }
139
140 public void print(ValueAnimator animation) {
141 float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
142 Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
143 "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
144 mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
145 }
146}