Skip to content

Commit 87bc53d

Browse files
chethaaseAndroid (Google) Code Review
authored andcommitted
Merge "Make notification panel delete-all animation smoother" into ics-mr0
2 parents 0784884 + 2f2022a commit 87bc53d

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)