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
This commit is contained in:
@@ -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,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;
|
||||
// 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;
|
||||
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
animateCollapse(false, 0f);
|
||||
}
|
||||
}, (N-1) * ROW_DELAY);
|
||||
// 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);
|
||||
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user