Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 104ca45a authored by Michael Jurka's avatar Michael Jurka Committed by Android (Google) Code Review
Browse files

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

parents cf5ccb8f e0523f7c
Loading
Loading
Loading
Loading
+129 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+26 −47
Original line number Diff line number Diff line
@@ -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;