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

Commit 2f2022af authored by Chet Haase's avatar Chet Haase
Browse files

Make notification panel delete-all animation smoother

Making the notfication delete-all animation smoother by carefully
choreographing the various parts of it. The problem with the previous
animation was that there was simply too much going on at the
same time, causing things like layout and recreating display-lists
in the middle of animations that became choppy as a result. This
approach swipes all items off quickly, then scrolls the shade up to the
top, making both sets of animations smoother as a result.

Fixes #5431207: Notification delete-all should be smoother

Change-Id: Iefe8ab5d661e05adcd10379dab85227d17904450
parent f522e095
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
    private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
    private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
    private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
    private static final boolean DEBUG_FPS = false;
    private static final boolean WATCH_POINTER = false;

    /**
@@ -274,6 +275,11 @@ public final class ViewRootImpl extends Handler implements ViewParent,
    private Thread mRenderProfiler;
    private volatile boolean mRenderProfilingEnabled;

    // Variables to track frames per second, enabled via DEBUG_FPS flag
    private long mFpsStartTime = -1;
    private long mFpsPrevTime = -1;
    private int mFpsNumFrames;

    /**
     * see {@link #playSoundEffect(int)}
     */
@@ -1766,12 +1772,42 @@ public final class ViewRootImpl extends Handler implements ViewParent,
        }
    }

    /**
     * Called from draw() when DEBUG_FPS is enabled
     */
    private void trackFPS() {
        // Tracks frames per second drawn. First value in a series of draws may be bogus
        // because it down not account for the intervening idle time
        long nowTime = System.currentTimeMillis();
        if (mFpsStartTime < 0) {
            mFpsStartTime = mFpsPrevTime = nowTime;
            mFpsNumFrames = 0;
        } else {
            ++mFpsNumFrames;
            String thisHash = Integer.toHexString(System.identityHashCode(this));
            long frameTime = nowTime - mFpsPrevTime;
            long totalTime = nowTime - mFpsStartTime;
            Log.v(TAG, "0x" + thisHash + "\tFrame time:\t" + frameTime);
            mFpsPrevTime = nowTime;
            if (totalTime > 1000) {
                float fps = (float) mFpsNumFrames * 1000 / totalTime;
                Log.v(TAG, "0x" + thisHash + "\tFPS:\t" + fps);
                mFpsStartTime = nowTime;
                mFpsNumFrames = 0;
            }
        }
    }

    private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (surface == null || !surface.isValid()) {
            return;
        }

        if (DEBUG_FPS) {
            trackFPS();
        }

        if (!sFirstDrawComplete) {
            synchronized (sFirstDrawHandlers) {
                sFirstDrawComplete = true;
+11 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
@@ -40,6 +41,8 @@ public class SwipeHelper {
    public static final int X = 0;
    public static final int Y = 1;

    private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();

    private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
    private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
    private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
@@ -199,6 +202,10 @@ public class SwipeHelper {
        return mDragging;
    }

    /**
     * @param view The view to be dismissed
     * @param velocity The desired pixels/second speed at which the view should move
     */
    public void dismissChild(final View view, float velocity) {
        final View animView = mCallback.getChildContentView(view);
        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
@@ -221,22 +228,14 @@ public class SwipeHelper {
            duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
        }

        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        ObjectAnimator anim = createTranslationAnimation(animView, newPos);
        anim.setInterpolator(new LinearInterpolator());
        anim.setInterpolator(sLinearInterpolator);
        anim.setDuration(duration);
        anim.addListener(new AnimatorListener() {
            public void onAnimationStart(Animator animation) {
            }

            public void onAnimationRepeat(Animator animation) {
            }

        anim.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator animation) {
                mCallback.onChildDismissed(view);
            }

            public void onAnimationCancel(Animator animation) {
                mCallback.onChildDismissed(view);
                animView.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
        anim.addUpdateListener(new AnimatorUpdateListener() {
+50 −27
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
@@ -213,6 +212,8 @@ public class PhoneStatusBar extends StatusBar {
    boolean mAnimatingReveal = false;
    int mViewDelta;
    int[] mAbsPos = new int[2];
    Runnable mPostCollapseCleanup = null;


    // for disabling the status bar
    int mDisabled = 0;
@@ -1238,6 +1239,10 @@ public class PhoneStatusBar extends StatusBar {
            return;
        }
        mExpanded = false;
        if (mPostCollapseCleanup != null) {
            mPostCollapseCleanup.run();
            mPostCollapseCleanup = null;
        }
    }

    void doAnimation() {
@@ -2066,49 +2071,67 @@ public class PhoneStatusBar extends StatusBar {
        }
        public void onClick(View v) {
            synchronized (mNotificationData) {
                // let's also queue up 400ms worth of animated dismissals
                final int N = mini(5, mPile.getChildCount());
                // animate-swipe all dismissable notifications, then animate the shade closed
                int numChildren = mPile.getChildCount();

                final ArrayList<View> snapshot = new ArrayList<View>(N);
                for (int i=0; i<N; i++) {
                int scrollTop = mScrollView.getScrollY();
                int scrollBottom = scrollTop + mScrollView.getHeight();
                final ArrayList<View> snapshot = new ArrayList<View>(numChildren);
                for (int i=0; i<numChildren; i++) {
                    final View child = mPile.getChildAt(i);
                    if (mPile.canChildBeDismissed(child)) snapshot.add(child);
                    if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop &&
                            child.getTop() < scrollBottom) {
                        snapshot.add(child);
                    }
                }
                final int N = snapshot.size();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        final int ROW_DELAY = 100;

                        mHandler.postDelayed(new Runnable() {
                            public void run() {
                                animateCollapse(false, 0f);
                            }
                        }, (N-1) * ROW_DELAY);

                        mHandler.postDelayed(new Runnable() {
                        // Decrease the delay for every row we animate to give the sense of
                        // accelerating the swipes
                        final int ROW_DELAY_DECREMENT = 10;
                        int currentDelay = 140;
                        int totalDelay = 0;

                        // Set the shade-animating state to avoid doing other work during
                        // all of these animations. In particular, avoid layout and
                        // redrawing when collapsing the shade.
                        mPile.setViewRemoval(false);

                        mPostCollapseCleanup = new Runnable() {
                            public void run() {
                                try {
                                    mPile.setViewRemoval(true);
                                    mBarService.onClearAllNotifications();
                                } catch (RemoteException ex) { }
                                } catch (Exception ex) { }
                            }
                        }, N * ROW_DELAY + 500);

                        mPile.setAnimateBounds(false); // temporarily disable some re-layouts
                        };

                        View sampleView = snapshot.get(0);
                        int width = sampleView.getWidth();
                        final int velocity = (int)(width * 8); // 1000/8 = 125 ms duration
                        for (View v : snapshot) {
                            final View _v = v;
                            mHandler.post(new Runnable() {
                            mHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    mPile.dismissRowAnimated(_v, (int)(ROW_DELAY*0.25f));
                                    mPile.dismissRowAnimated(_v, velocity);
                                }
                            });
                            try {
                                Thread.sleep(ROW_DELAY);
                            } catch (InterruptedException ex) { }
                            }, totalDelay);
                            currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT);
                            totalDelay += currentDelay;
                        }
                        
                        mPile.setAnimateBounds(true); // reenable layout animation
                        // Delay the collapse animation until after all swipe animations have
                        // finished. Provide some buffer because there may be some extra delay
                        // before actually starting each swipe animation. Ideally, we'd
                        // synchronize the end of those animations with the start of the collaps
                        // exactly.
                        mHandler.postDelayed(new Runnable() {
                            public void run() {
                                animateCollapse(false);
                            }
                        }, totalDelay + 225);
                    }
                }).start();
            }
+40 −12
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.policy;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -29,7 +28,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -59,6 +57,10 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call

    private SwipeHelper mSwipeHelper;

    // Flag set during notification removal animation to avoid causing too much work until
    // animation is done
    boolean mRemoveViews = true;

    public NotificationRowLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
@@ -117,7 +119,7 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call

    public void onChildDismissed(View v) {
        final View veto = v.findViewById(R.id.veto);
        if (veto != null && veto.getVisibility() != View.GONE) {
        if (veto != null && veto.getVisibility() != View.GONE && mRemoveViews) {
            veto.performClick();
        }
    }
@@ -170,7 +172,6 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
        final View childF = child;

        if (mAnimateBounds) {
            child.setPivotY(0);
            final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
            alphaFade.setDuration(APPEAR_ANIM_LEN);
            alphaFade.addListener(new AnimatorListenerAdapter() {
@@ -189,6 +190,16 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
        }
    }

    /**
     * Sets a flag to tell us whether to actually remove views. Removal is delayed by setting this
     * to false during some animations to smooth out performance. Callers should restore the
     * flag to true after the animation is done, and then they should make sure that the views
     * get removed properly.
     */
    public void setViewRemoval(boolean removeViews) {
        mRemoveViews = removeViews;
    }

    public void dismissRowAnimated(View child) {
        dismissRowAnimated(child, 0);
    }
@@ -199,16 +210,34 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call

    @Override
    public void removeView(View child) {
        final View childF = child;
        if (!mRemoveViews) {
            // This flag is cleared during an animation that removes all notifications. There
            // should be a call to remove all notifications when the animation is done, at which
            // time the view will be removed.
            return;
        }
        if (mAnimateBounds) {
            if (mAppearingViews.containsKey(child)) {
                mAppearingViews.remove(child);
            }
            child.setPivotY(0);

            final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f);
            alphaFade.setDuration(DISAPPEAR_ANIM_LEN);
            alphaFade.addListener(new AnimatorListenerAdapter() {
            // Don't fade it out if it already has a low alpha value, but run a non-visual
            // animation which is used by onLayout() to animate shrinking the gap that it left
            // in the list
            ValueAnimator anim;
            float currentAlpha = child.getAlpha();
            if (currentAlpha > .1) {
                anim = ObjectAnimator.ofFloat(child, "alpha", currentAlpha, 0);
            } else {
                if (currentAlpha > 0) {
                    // Just make it go away - no need to render it anymore
                    child.setAlpha(0);
                }
                anim = ValueAnimator.ofFloat(0, 1);
            }
            anim.setDuration(DISAPPEAR_ANIM_LEN);
            final View childF = child;
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (DEBUG) Slog.d(TAG, "actually removing child: " + childF);
@@ -218,9 +247,8 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
                }
            });

            alphaFade.start();

            mDisappearingViews.put(child, alphaFade);
            anim.start();
            mDisappearingViews.put(child, anim);

            requestLayout(); // start the container animation
        } else {