Merge "Fix janky icon fade-in animation in Recents" into jb-mr2-dev

This commit is contained in:
Michael Jurka
2013-04-09 06:04:35 +00:00
committed by Android (Google) Code Review
2 changed files with 155 additions and 47 deletions

View File

@ -0,0 +1,129 @@
/*
* 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.systemui.recent;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.Animator.AnimatorListener;
import android.util.Log;
import android.view.ViewTreeObserver;
import android.view.View;
import android.view.ViewPropertyAnimator;
/*
* 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 ValueAnimator.AnimatorUpdateListener {
private static final boolean DEBUG = false;
private static final int MAX_DELAY = 1000;
private static final int IDEAL_FRAME_DURATION = 16;
private View mTarget;
private long mStartFrame;
private long mStartTime = -1;
private boolean mHandlingOnAnimationUpdate;
private boolean mAdjustedSecondFrameTime;
private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
private static long sGlobalFrameCounter;
public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
mTarget = target;
animator.addUpdateListener(this);
}
public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
mTarget = target;
vpa.setListener(new AnimatorListenerAdapter() {
public void onAnimationStart (Animator animation) {
final ValueAnimator va = (ValueAnimator) animation;
va.addUpdateListener(FirstFrameAnimatorHelper.this);
onAnimationUpdate(va);
}
});
}
public static void initializeDrawListener(View view) {
if (sGlobalDrawListener != null) {
view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
}
sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
private long mTime = System.currentTimeMillis();
public void onDraw() {
sGlobalFrameCounter++;
if (DEBUG) {
long newTime = System.currentTimeMillis();
Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
mTime = newTime;
}
}
};
view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
}
public void onAnimationUpdate(final ValueAnimator animation) {
final long currentTime = System.currentTimeMillis();
if (mStartTime == -1) {
mStartFrame = sGlobalFrameCounter;
mStartTime = currentTime;
}
if (!mHandlingOnAnimationUpdate) {
mHandlingOnAnimationUpdate = true;
long frameNum = sGlobalFrameCounter - 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) {
// The first frame on animations doesn't always trigger an invalidate...
// force an invalidate here to make sure the animation continues to advance
mTarget.getRootView().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 + IDEAL_FRAME_DURATION) {
animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
mAdjustedSecondFrameTime = true;
} else {
if (frameNum > 1) {
mTarget.post(new Runnable() {
public void run() {
animation.removeUpdateListener(FirstFrameAnimatorHelper.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("FirstFrameAnimatorHelper", sGlobalFrameCounter +
"(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
}
}

View File

@ -45,8 +45,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
@ -181,25 +180,6 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener
}
if (index == 0) {
if (mAnimateIconOfFirstTask) {
ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
if (oldHolder != null) {
oldHolder.iconView.setAlpha(1f);
oldHolder.iconView.setTranslationX(0f);
oldHolder.iconView.setTranslationY(0f);
oldHolder.labelView.setAlpha(1f);
oldHolder.labelView.setTranslationX(0f);
oldHolder.labelView.setTranslationY(0f);
if (oldHolder.calloutLine != null) {
oldHolder.calloutLine.setAlpha(1f);
oldHolder.calloutLine.setTranslationX(0f);
oldHolder.calloutLine.setTranslationY(0f);
}
}
mItemToAnimateInWhenWindowAnimationIsFinished = null;
final ViewTreeObserver observer = getViewTreeObserver();
final OnGlobalLayoutListener animateFirstIcon = new OnGlobalLayoutListener() {
public void onGlobalLayout() {
ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
if (oldHolder != null) {
oldHolder.iconView.setAlpha(1f);
@ -235,10 +215,6 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener
if (!mWaitingForWindowAnimation) {
animateInIconOfFirstTask();
}
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
};
observer.addOnGlobalLayoutListener(animateFirstIcon);
}
}
@ -586,17 +562,20 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener
!mRecentTasksLoader.isFirstScreenful()) {
int timeSinceWindowAnimation =
(int) (System.currentTimeMillis() - mWindowAnimationStartTime);
final int minStartDelay = 150;
final int minStartDelay = 125;
final int startDelay = Math.max(0, Math.min(
minStartDelay - timeSinceWindowAnimation, minStartDelay));
final int duration = 250;
final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished;
final TimeInterpolator cubic = new DecelerateInterpolator(1.5f);
FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView);
for (View v :
new View[] { holder.iconView, holder.labelView, holder.calloutLine }) {
if (v != null) {
v.animate().translationX(0).translationY(0).alpha(1f).setStartDelay(startDelay)
ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0)
.alpha(1f).setStartDelay(startDelay)
.setDuration(duration).setInterpolator(cubic);
FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v);
}
}
mItemToAnimateInWhenWindowAnimationIsFinished = null;