diff options
author | Michael Jurka <mikejurka@google.com> | 2013-04-08 15:44:00 -0700 |
---|---|---|
committer | Michael Jurka <mikejurka@google.com> | 2013-04-08 18:20:20 -0700 |
commit | e0523f7c803506090b8cb45dca2a8bd5e36af456 (patch) | |
tree | f9591f5c4a3a5a4bcb9d62dba1552a915f0fffc4 /packages/SystemUI/src/com/android/systemui/recent | |
parent | 26c134398422a4f4e703cdf7b87fb1cb08d542c4 (diff) | |
download | frameworks_base-e0523f7c803506090b8cb45dca2a8bd5e36af456.zip frameworks_base-e0523f7c803506090b8cb45dca2a8bd5e36af456.tar.gz frameworks_base-e0523f7c803506090b8cb45dca2a8bd5e36af456.tar.bz2 |
Fix janky icon fade-in animation in Recents
Change-Id: I15d2274add8903820dac3da0fa47c9e54f6ec97d
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/recent')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java | 129 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java | 73 |
2 files changed, 155 insertions, 47 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java new file mode 100644 index 0000000..2fc7dfc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java @@ -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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index 9c2bca9..32759de 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -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; @@ -195,50 +194,27 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener 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); - 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 = holder; - int translation = -getResources().getDimensionPixelSize( - R.dimen.status_bar_recents_app_icon_translate_distance); - final Configuration config = getResources().getConfiguration(); - if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { - if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - translation = -translation; - } - holder.iconView.setAlpha(0f); - holder.iconView.setTranslationX(translation); - holder.labelView.setAlpha(0f); - holder.labelView.setTranslationX(translation); - holder.calloutLine.setAlpha(0f); - holder.calloutLine.setTranslationX(translation); - } else { - holder.iconView.setAlpha(0f); - holder.iconView.setTranslationY(translation); - } - if (!mWaitingForWindowAnimation) { - animateInIconOfFirstTask(); - } - getViewTreeObserver().removeOnGlobalLayoutListener(this); + mItemToAnimateInWhenWindowAnimationIsFinished = holder; + int translation = -getResources().getDimensionPixelSize( + R.dimen.status_bar_recents_app_icon_translate_distance); + final Configuration config = getResources().getConfiguration(); + if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { + if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + translation = -translation; } - }; - observer.addOnGlobalLayoutListener(animateFirstIcon); + holder.iconView.setAlpha(0f); + holder.iconView.setTranslationX(translation); + holder.labelView.setAlpha(0f); + holder.labelView.setTranslationX(translation); + holder.calloutLine.setAlpha(0f); + holder.calloutLine.setTranslationX(translation); + } else { + holder.iconView.setAlpha(0f); + holder.iconView.setTranslationY(translation); + } + if (!mWaitingForWindowAnimation) { + animateInIconOfFirstTask(); + } } } @@ -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; |