| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.launcher3; |
| |
| import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; |
| |
| import android.animation.ValueAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.View.OnAttachStateChangeListener; |
| import android.view.ViewTreeObserver.OnDrawListener; |
| |
| /* |
| * This is a helper class that listens to updates from the corresponding animation. |
| * For the first two frames, it adjusts the current play time of the animation to |
| * prevent jank at the beginning of the animation |
| */ |
| public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateChangeListener { |
| |
| private static final String TAG = "FirstFrameAnimatorHlpr"; |
| private static final boolean DEBUG = false; |
| private static final int MAX_DELAY = 1000; |
| |
| private View mRootView; |
| private long mGlobalFrameCount; |
| |
| public FirstFrameAnimatorHelper(View target) { |
| target.addOnAttachStateChangeListener(this); |
| if (target.isAttachedToWindow()) { |
| onViewAttachedToWindow(target); |
| } |
| } |
| |
| public <T extends ValueAnimator> T addTo(T anim) { |
| anim.addUpdateListener(new MyListener()); |
| return anim; |
| } |
| |
| @Override |
| public void onDraw() { |
| mGlobalFrameCount ++; |
| } |
| |
| @Override |
| public void onViewAttachedToWindow(View view) { |
| mRootView = view.getRootView(); |
| mRootView.getViewTreeObserver().addOnDrawListener(this); |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View view) { |
| if (mRootView != null) { |
| mRootView.getViewTreeObserver().removeOnDrawListener(this); |
| mRootView = null; |
| } |
| } |
| |
| private class MyListener implements AnimatorUpdateListener { |
| |
| private long mStartFrame; |
| private long mStartTime = -1; |
| private boolean mHandlingOnAnimationUpdate; |
| private boolean mAdjustedSecondFrameTime; |
| |
| @Override |
| public void onAnimationUpdate(final ValueAnimator animation) { |
| final long currentTime = System.currentTimeMillis(); |
| if (mStartTime == -1) { |
| mStartFrame = mGlobalFrameCount; |
| mStartTime = currentTime; |
| } |
| |
| final long currentPlayTime = animation.getCurrentPlayTime(); |
| boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0; |
| |
| if (!mHandlingOnAnimationUpdate && |
| mRootView != null && |
| mRootView.getWindowVisibility() == View.VISIBLE && |
| // If the current play time exceeds the duration, or the animated fraction is 1, |
| // the animation will get finished, even if we call setCurrentPlayTime -- |
| // therefore don't adjust the animation in that case |
| currentPlayTime < animation.getDuration() && !isFinalFrame) { |
| mHandlingOnAnimationUpdate = true; |
| long frameNum = mGlobalFrameCount - mStartFrame; |
| |
| // If we haven't drawn our first frame, reset the time to t = 0 |
| // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we |
| // are no longer in the foreground and no frames are being rendered ever) |
| if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) { |
| // The first frame on animations doesn't always trigger an invalidate... |
| // force an invalidate here to make sure the animation continues to advance |
| mRootView.invalidate(); |
| animation.setCurrentPlayTime(0); |
| // For the second frame, if the first frame took more than 16ms, |
| // adjust the start time and pretend it took only 16ms anyway. This |
| // prevents a large jump in the animation due to an expensive first frame |
| } else { |
| int singleFrameMS = getSingleFrameMs(mRootView.getContext()); |
| if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY && |
| !mAdjustedSecondFrameTime && |
| currentTime > mStartTime + singleFrameMS && |
| currentPlayTime > singleFrameMS) { |
| animation.setCurrentPlayTime(singleFrameMS); |
| mAdjustedSecondFrameTime = true; |
| } else { |
| if (frameNum > 1) { |
| mRootView.post(() -> animation.removeUpdateListener(this)); |
| } |
| if (DEBUG) print(animation); |
| } |
| } |
| mHandlingOnAnimationUpdate = false; |
| } else { |
| if (DEBUG) print(animation); |
| } |
| } |
| |
| public void print(ValueAnimator animation) { |
| float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration(); |
| Log.d(TAG, mGlobalFrameCount + |
| "(" + (mGlobalFrameCount - mStartFrame) + ") " + mRootView + " dirty? " + |
| mRootView.isDirty() + " " + flatFraction + " " + this + " " + animation); |
| } |
| } |
| } |