blob: fdf0101e663e62651409eaf97c835729409d2cfa [file] [log] [blame]
/*
* 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);
}
}
}