Merge "Make notification panel delete-all animation smoother" into ics-mr0

This commit is contained in:
Chet Haase
2011-10-18 13:45:00 -07:00
committed by Android (Google) Code Review
4 changed files with 135 additions and 49 deletions

View File

@@ -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_TRACKBALL = false || LOCAL_LOGV;
private static final boolean DEBUG_IMF = 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_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean DEBUG_FPS = false;
private static final boolean WATCH_POINTER = false; private static final boolean WATCH_POINTER = false;
/** /**
@@ -274,6 +275,11 @@ public final class ViewRootImpl extends Handler implements ViewParent,
private Thread mRenderProfiler; private Thread mRenderProfiler;
private volatile boolean mRenderProfilingEnabled; 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)} * 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) { private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface; Surface surface = mSurface;
if (surface == null || !surface.isValid()) { if (surface == null || !surface.isValid()) {
return; return;
} }
if (DEBUG_FPS) {
trackFPS();
}
if (!sFirstDrawComplete) { if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) { synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true; sFirstDrawComplete = true;

View File

@@ -17,6 +17,7 @@
package com.android.systemui; package com.android.systemui;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.Animator.AnimatorListener; import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
@@ -40,6 +41,8 @@ public class SwipeHelper {
public static final int X = 0; public static final int X = 0;
public static final int Y = 1; public static final int Y = 1;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
@@ -199,6 +202,10 @@ public class SwipeHelper {
return mDragging; 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) { public void dismissChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view); final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
@@ -221,22 +228,14 @@ public class SwipeHelper {
duration = DEFAULT_ESCAPE_ANIMATION_DURATION; duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
} }
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator anim = createTranslationAnimation(animView, newPos); ObjectAnimator anim = createTranslationAnimation(animView, newPos);
anim.setInterpolator(new LinearInterpolator()); anim.setInterpolator(sLinearInterpolator);
anim.setDuration(duration); anim.setDuration(duration);
anim.addListener(new AnimatorListener() { anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationStart(Animator animation) {
}
public void onAnimationRepeat(Animator animation) {
}
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view); mCallback.onChildDismissed(view);
} animView.setLayerType(View.LAYER_TYPE_NONE, null);
public void onAnimationCancel(Animator animation) {
mCallback.onChildDismissed(view);
} }
}); });
anim.addUpdateListener(new AnimatorUpdateListener() { anim.addUpdateListener(new AnimatorUpdateListener() {

View File

@@ -50,7 +50,6 @@ import android.view.IWindowManager;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker; import android.view.VelocityTracker;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -213,6 +212,8 @@ public class PhoneStatusBar extends StatusBar {
boolean mAnimatingReveal = false; boolean mAnimatingReveal = false;
int mViewDelta; int mViewDelta;
int[] mAbsPos = new int[2]; int[] mAbsPos = new int[2];
Runnable mPostCollapseCleanup = null;
// for disabling the status bar // for disabling the status bar
int mDisabled = 0; int mDisabled = 0;
@@ -1238,6 +1239,10 @@ public class PhoneStatusBar extends StatusBar {
return; return;
} }
mExpanded = false; mExpanded = false;
if (mPostCollapseCleanup != null) {
mPostCollapseCleanup.run();
mPostCollapseCleanup = null;
}
} }
void doAnimation() { void doAnimation() {
@@ -2066,49 +2071,67 @@ public class PhoneStatusBar extends StatusBar {
} }
public void onClick(View v) { public void onClick(View v) {
synchronized (mNotificationData) { synchronized (mNotificationData) {
// let's also queue up 400ms worth of animated dismissals // animate-swipe all dismissable notifications, then animate the shade closed
final int N = mini(5, mPile.getChildCount()); int numChildren = mPile.getChildCount();
final ArrayList<View> snapshot = new ArrayList<View>(N); int scrollTop = mScrollView.getScrollY();
for (int i=0; i<N; i++) { 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); 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() { new Thread(new Runnable() {
@Override @Override
public void run() { 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() { // Set the shade-animating state to avoid doing other work during
public void run() { // all of these animations. In particular, avoid layout and
animateCollapse(false, 0f); // redrawing when collapsing the shade.
} mPile.setViewRemoval(false);
}, (N-1) * ROW_DELAY);
mHandler.postDelayed(new Runnable() { mPostCollapseCleanup = new Runnable() {
public void run() { public void run() {
try { try {
mPile.setViewRemoval(true);
mBarService.onClearAllNotifications(); 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) { for (View v : snapshot) {
final View _v = v; final View _v = v;
mHandler.post(new Runnable() { mHandler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
mPile.dismissRowAnimated(_v, (int)(ROW_DELAY*0.25f)); mPile.dismissRowAnimated(_v, velocity);
} }
}); }, totalDelay);
try { currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT);
Thread.sleep(ROW_DELAY); totalDelay += currentDelay;
} catch (InterruptedException ex) { }
} }
// Delay the collapse animation until after all swipe animations have
mPile.setAnimateBounds(true); // reenable layout animation // 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(); }).start();
} }

View File

@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.policy;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
@@ -29,7 +28,6 @@ import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.Slog; import android.util.Slog;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -59,6 +57,10 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
private SwipeHelper mSwipeHelper; 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) { public NotificationRowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0); this(context, attrs, 0);
} }
@@ -117,7 +119,7 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
public void onChildDismissed(View v) { public void onChildDismissed(View v) {
final View veto = v.findViewById(R.id.veto); 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(); veto.performClick();
} }
} }
@@ -170,7 +172,6 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
final View childF = child; final View childF = child;
if (mAnimateBounds) { if (mAnimateBounds) {
child.setPivotY(0);
final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f); final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
alphaFade.setDuration(APPEAR_ANIM_LEN); alphaFade.setDuration(APPEAR_ANIM_LEN);
alphaFade.addListener(new AnimatorListenerAdapter() { 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) { public void dismissRowAnimated(View child) {
dismissRowAnimated(child, 0); dismissRowAnimated(child, 0);
} }
@@ -199,16 +210,34 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
@Override @Override
public void removeView(View child) { 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 (mAnimateBounds) {
if (mAppearingViews.containsKey(child)) { if (mAppearingViews.containsKey(child)) {
mAppearingViews.remove(child); mAppearingViews.remove(child);
} }
child.setPivotY(0);
final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f); // Don't fade it out if it already has a low alpha value, but run a non-visual
alphaFade.setDuration(DISAPPEAR_ANIM_LEN); // animation which is used by onLayout() to animate shrinking the gap that it left
alphaFade.addListener(new AnimatorListenerAdapter() { // 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 @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (DEBUG) Slog.d(TAG, "actually removing child: " + childF); if (DEBUG) Slog.d(TAG, "actually removing child: " + childF);
@@ -218,9 +247,8 @@ public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Call
} }
}); });
alphaFade.start(); anim.start();
mDisappearingViews.put(child, anim);
mDisappearingViews.put(child, alphaFade);
requestLayout(); // start the container animation requestLayout(); // start the container animation
} else { } else {