Skip to content

Commit 2f2022a

Browse files
committed
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
1 parent f522e09 commit 2f2022a

File tree

4 files changed

+137
-51
lines changed

4 files changed

+137
-51
lines changed

core/java/android/view/ViewRootImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
108108
private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
109109
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
110110
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
111+
private static final boolean DEBUG_FPS = false;
111112
private static final boolean WATCH_POINTER = false;
112113

113114
/**
@@ -274,6 +275,11 @@ class ResizedInfo {
274275
private Thread mRenderProfiler;
275276
private volatile boolean mRenderProfilingEnabled;
276277

278+
// Variables to track frames per second, enabled via DEBUG_FPS flag
279+
private long mFpsStartTime = -1;
280+
private long mFpsPrevTime = -1;
281+
private int mFpsNumFrames;
282+
277283
/**
278284
* see {@link #playSoundEffect(int)}
279285
*/
@@ -1766,12 +1772,42 @@ public void run() {
17661772
}
17671773
}
17681774

1775+
/**
1776+
* Called from draw() when DEBUG_FPS is enabled
1777+
*/
1778+
private void trackFPS() {
1779+
// Tracks frames per second drawn. First value in a series of draws may be bogus
1780+
// because it down not account for the intervening idle time
1781+
long nowTime = System.currentTimeMillis();
1782+
if (mFpsStartTime < 0) {
1783+
mFpsStartTime = mFpsPrevTime = nowTime;
1784+
mFpsNumFrames = 0;
1785+
} else {
1786+
++mFpsNumFrames;
1787+
String thisHash = Integer.toHexString(System.identityHashCode(this));
1788+
long frameTime = nowTime - mFpsPrevTime;
1789+
long totalTime = nowTime - mFpsStartTime;
1790+
Log.v(TAG, "0x" + thisHash + "\tFrame time:\t" + frameTime);
1791+
mFpsPrevTime = nowTime;
1792+
if (totalTime > 1000) {
1793+
float fps = (float) mFpsNumFrames * 1000 / totalTime;
1794+
Log.v(TAG, "0x" + thisHash + "\tFPS:\t" + fps);
1795+
mFpsStartTime = nowTime;
1796+
mFpsNumFrames = 0;
1797+
}
1798+
}
1799+
}
1800+
17691801
private void draw(boolean fullRedrawNeeded) {
17701802
Surface surface = mSurface;
17711803
if (surface == null || !surface.isValid()) {
17721804
return;
17731805
}
17741806

1807+
if (DEBUG_FPS) {
1808+
trackFPS();
1809+
}
1810+
17751811
if (!sFirstDrawComplete) {
17761812
synchronized (sFirstDrawHandlers) {
17771813
sFirstDrawComplete = true;

packages/SystemUI/src/com/android/systemui/SwipeHelper.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.android.systemui;
1818

1919
import android.animation.Animator;
20+
import android.animation.AnimatorListenerAdapter;
2021
import android.animation.ObjectAnimator;
2122
import android.animation.Animator.AnimatorListener;
2223
import android.animation.ValueAnimator;
@@ -40,6 +41,8 @@ public class SwipeHelper {
4041
public static final int X = 0;
4142
public static final int Y = 1;
4243

44+
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
45+
4346
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
4447
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
4548
private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
@@ -199,6 +202,10 @@ public boolean onInterceptTouchEvent(MotionEvent ev) {
199202
return mDragging;
200203
}
201204

205+
/**
206+
* @param view The view to be dismissed
207+
* @param velocity The desired pixels/second speed at which the view should move
208+
*/
202209
public void dismissChild(final View view, float velocity) {
203210
final View animView = mCallback.getChildContentView(view);
204211
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
@@ -221,22 +228,14 @@ public void dismissChild(final View view, float velocity) {
221228
duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
222229
}
223230

231+
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
224232
ObjectAnimator anim = createTranslationAnimation(animView, newPos);
225-
anim.setInterpolator(new LinearInterpolator());
233+
anim.setInterpolator(sLinearInterpolator);
226234
anim.setDuration(duration);
227-
anim.addListener(new AnimatorListener() {
228-
public void onAnimationStart(Animator animation) {
229-
}
230-
231-
public void onAnimationRepeat(Animator animation) {
232-
}
233-
235+
anim.addListener(new AnimatorListenerAdapter() {
234236
public void onAnimationEnd(Animator animation) {
235237
mCallback.onChildDismissed(view);
236-
}
237-
238-
public void onAnimationCancel(Animator animation) {
239-
mCallback.onChildDismissed(view);
238+
animView.setLayerType(View.LAYER_TYPE_NONE, null);
240239
}
241240
});
242241
anim.addUpdateListener(new AnimatorUpdateListener() {

packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import android.view.KeyEvent;
5151
import android.view.LayoutInflater;
5252
import android.view.MotionEvent;
53-
import android.view.Surface;
5453
import android.view.VelocityTracker;
5554
import android.view.View;
5655
import android.view.ViewGroup;
@@ -213,6 +212,8 @@ public class PhoneStatusBar extends StatusBar {
213212
boolean mAnimatingReveal = false;
214213
int mViewDelta;
215214
int[] mAbsPos = new int[2];
215+
Runnable mPostCollapseCleanup = null;
216+
216217

217218
// for disabling the status bar
218219
int mDisabled = 0;
@@ -1238,6 +1239,10 @@ void performCollapse() {
12381239
return;
12391240
}
12401241
mExpanded = false;
1242+
if (mPostCollapseCleanup != null) {
1243+
mPostCollapseCleanup.run();
1244+
mPostCollapseCleanup = null;
1245+
}
12411246
}
12421247

12431248
void doAnimation() {
@@ -2066,49 +2071,67 @@ final int mini(int a, int b) {
20662071
}
20672072
public void onClick(View v) {
20682073
synchronized (mNotificationData) {
2069-
// let's also queue up 400ms worth of animated dismissals
2070-
final int N = mini(5, mPile.getChildCount());
2074+
// animate-swipe all dismissable notifications, then animate the shade closed
2075+
int numChildren = mPile.getChildCount();
20712076

2072-
final ArrayList<View> snapshot = new ArrayList<View>(N);
2073-
for (int i=0; i<N; i++) {
2077+
int scrollTop = mScrollView.getScrollY();
2078+
int scrollBottom = scrollTop + mScrollView.getHeight();
2079+
final ArrayList<View> snapshot = new ArrayList<View>(numChildren);
2080+
for (int i=0; i<numChildren; i++) {
20742081
final View child = mPile.getChildAt(i);
2075-
if (mPile.canChildBeDismissed(child)) snapshot.add(child);
2082+
if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop &&
2083+
child.getTop() < scrollBottom) {
2084+
snapshot.add(child);
2085+
}
20762086
}
2087+
final int N = snapshot.size();
20772088
new Thread(new Runnable() {
20782089
@Override
20792090
public void run() {
2080-
final int ROW_DELAY = 100;
2081-
2082-
mHandler.postDelayed(new Runnable() {
2083-
public void run() {
2084-
animateCollapse(false, 0f);
2085-
}
2086-
}, (N-1) * ROW_DELAY);
2087-
2088-
mHandler.postDelayed(new Runnable() {
2091+
// Decrease the delay for every row we animate to give the sense of
2092+
// accelerating the swipes
2093+
final int ROW_DELAY_DECREMENT = 10;
2094+
int currentDelay = 140;
2095+
int totalDelay = 0;
2096+
2097+
// Set the shade-animating state to avoid doing other work during
2098+
// all of these animations. In particular, avoid layout and
2099+
// redrawing when collapsing the shade.
2100+
mPile.setViewRemoval(false);
2101+
2102+
mPostCollapseCleanup = new Runnable() {
20892103
public void run() {
20902104
try {
2105+
mPile.setViewRemoval(true);
20912106
mBarService.onClearAllNotifications();
2092-
} catch (RemoteException ex) { }
2107+
} catch (Exception ex) { }
20932108
}
2094-
}, N * ROW_DELAY + 500);
2095-
2096-
mPile.setAnimateBounds(false); // temporarily disable some re-layouts
2109+
};
20972110

2111+
View sampleView = snapshot.get(0);
2112+
int width = sampleView.getWidth();
2113+
final int velocity = (int)(width * 8); // 1000/8 = 125 ms duration
20982114
for (View v : snapshot) {
20992115
final View _v = v;
2100-
mHandler.post(new Runnable() {
2116+
mHandler.postDelayed(new Runnable() {
21012117
@Override
21022118
public void run() {
2103-
mPile.dismissRowAnimated(_v, (int)(ROW_DELAY*0.25f));
2119+
mPile.dismissRowAnimated(_v, velocity);
21042120
}
2105-
});
2106-
try {
2107-
Thread.sleep(ROW_DELAY);
2108-
} catch (InterruptedException ex) { }
2121+
}, totalDelay);
2122+
currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT);
2123+
totalDelay += currentDelay;
21092124
}
2110-
2111-
mPile.setAnimateBounds(true); // reenable layout animation
2125+
// Delay the collapse animation until after all swipe animations have
2126+
// finished. Provide some buffer because there may be some extra delay
2127+
// before actually starting each swipe animation. Ideally, we'd
2128+
// synchronize the end of those animations with the start of the collaps
2129+
// exactly.
2130+
mHandler.postDelayed(new Runnable() {
2131+
public void run() {
2132+
animateCollapse(false);
2133+
}
2134+
}, totalDelay + 225);
21122135
}
21132136
}).start();
21142137
}

packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import android.animation.Animator;
2020
import android.animation.AnimatorListenerAdapter;
21-
import android.animation.AnimatorSet;
2221
import android.animation.ObjectAnimator;
2322
import android.animation.ValueAnimator;
2423
import android.content.Context;
@@ -29,7 +28,6 @@
2928
import android.util.Log;
3029
import android.util.Slog;
3130
import android.view.MotionEvent;
32-
import android.view.VelocityTracker;
3331
import android.view.View;
3432
import android.view.ViewConfiguration;
3533
import android.view.ViewGroup;
@@ -59,6 +57,10 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
5957

6058
private SwipeHelper mSwipeHelper;
6159

60+
// Flag set during notification removal animation to avoid causing too much work until
61+
// animation is done
62+
boolean mRemoveViews = true;
63+
6264
public NotificationRowLayout(Context context, AttributeSet attrs) {
6365
this(context, attrs, 0);
6466
}
@@ -117,7 +119,7 @@ public boolean canChildBeDismissed(View v) {
117119

118120
public void onChildDismissed(View v) {
119121
final View veto = v.findViewById(R.id.veto);
120-
if (veto != null && veto.getVisibility() != View.GONE) {
122+
if (veto != null && veto.getVisibility() != View.GONE && mRemoveViews) {
121123
veto.performClick();
122124
}
123125
}
@@ -170,7 +172,6 @@ public void addView(View child, int index, LayoutParams params) {
170172
final View childF = child;
171173

172174
if (mAnimateBounds) {
173-
child.setPivotY(0);
174175
final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
175176
alphaFade.setDuration(APPEAR_ANIM_LEN);
176177
alphaFade.addListener(new AnimatorListenerAdapter() {
@@ -189,6 +190,16 @@ public void onAnimationEnd(Animator animation) {
189190
}
190191
}
191192

193+
/**
194+
* Sets a flag to tell us whether to actually remove views. Removal is delayed by setting this
195+
* to false during some animations to smooth out performance. Callers should restore the
196+
* flag to true after the animation is done, and then they should make sure that the views
197+
* get removed properly.
198+
*/
199+
public void setViewRemoval(boolean removeViews) {
200+
mRemoveViews = removeViews;
201+
}
202+
192203
public void dismissRowAnimated(View child) {
193204
dismissRowAnimated(child, 0);
194205
}
@@ -199,16 +210,34 @@ public void dismissRowAnimated(View child, int vel) {
199210

200211
@Override
201212
public void removeView(View child) {
202-
final View childF = child;
213+
if (!mRemoveViews) {
214+
// This flag is cleared during an animation that removes all notifications. There
215+
// should be a call to remove all notifications when the animation is done, at which
216+
// time the view will be removed.
217+
return;
218+
}
203219
if (mAnimateBounds) {
204220
if (mAppearingViews.containsKey(child)) {
205221
mAppearingViews.remove(child);
206222
}
207-
child.setPivotY(0);
208223

209-
final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f);
210-
alphaFade.setDuration(DISAPPEAR_ANIM_LEN);
211-
alphaFade.addListener(new AnimatorListenerAdapter() {
224+
// Don't fade it out if it already has a low alpha value, but run a non-visual
225+
// animation which is used by onLayout() to animate shrinking the gap that it left
226+
// in the list
227+
ValueAnimator anim;
228+
float currentAlpha = child.getAlpha();
229+
if (currentAlpha > .1) {
230+
anim = ObjectAnimator.ofFloat(child, "alpha", currentAlpha, 0);
231+
} else {
232+
if (currentAlpha > 0) {
233+
// Just make it go away - no need to render it anymore
234+
child.setAlpha(0);
235+
}
236+
anim = ValueAnimator.ofFloat(0, 1);
237+
}
238+
anim.setDuration(DISAPPEAR_ANIM_LEN);
239+
final View childF = child;
240+
anim.addListener(new AnimatorListenerAdapter() {
212241
@Override
213242
public void onAnimationEnd(Animator animation) {
214243
if (DEBUG) Slog.d(TAG, "actually removing child: " + childF);
@@ -218,9 +247,8 @@ public void onAnimationEnd(Animator animation) {
218247
}
219248
});
220249

221-
alphaFade.start();
222-
223-
mDisappearingViews.put(child, alphaFade);
250+
anim.start();
251+
mDisappearingViews.put(child, anim);
224252

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

0 commit comments

Comments
 (0)