/*
|
* 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.Utilities.SINGLE_FRAME_MS;
|
|
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 if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
|
!mAdjustedSecondFrameTime &&
|
currentTime > mStartTime + SINGLE_FRAME_MS &&
|
currentPlayTime > SINGLE_FRAME_MS) {
|
animation.setCurrentPlayTime(SINGLE_FRAME_MS);
|
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);
|
}
|
}
|
}
|