Modify SwipeDismissLayout to perform its own exit animation
Instead of relying on the window animation system, in the special case of a swipe-dismiss, disable any default window exit animation and perform a custom animation. This bypasses some bugs in the window animator codebase and allows us to have a nice "rebound" animation if the user doesn't swipe far/fast enough to trigger a dismiss. Bug: 33041168 Change-Id: Ied45700d35a59950bacef1ba0650eaa5bc60fadb
This commit is contained in:
@@ -2946,8 +2946,11 @@ public class Activity extends ContextThemeWrapper
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWindowDismissed(boolean finishTask) {
|
public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
|
||||||
finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
|
finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
|
||||||
|
if (suppressWindowTransition) {
|
||||||
|
overridePendingTransition(0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ public class Dialog implements DialogInterface, Window.Callback,
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Override
|
@Override
|
||||||
public void onWindowDismissed(boolean finishTask) {
|
public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -581,8 +581,10 @@ public abstract class Window {
|
|||||||
* Called when a window is dismissed. This informs the callback that the
|
* Called when a window is dismissed. This informs the callback that the
|
||||||
* window is gone, and it should finish itself.
|
* window is gone, and it should finish itself.
|
||||||
* @param finishTask True if the task should also be finished.
|
* @param finishTask True if the task should also be finished.
|
||||||
|
* @param suppressWindowTransition True if the resulting exit and enter window transition
|
||||||
|
* animations should be suppressed.
|
||||||
*/
|
*/
|
||||||
void onWindowDismissed(boolean finishTask);
|
void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@@ -871,9 +873,10 @@ public abstract class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final void dispatchOnWindowDismissed(boolean finishTask) {
|
public final void dispatchOnWindowDismissed(
|
||||||
|
boolean finishTask, boolean suppressWindowTransition) {
|
||||||
if (mOnWindowDismissedCallback != null) {
|
if (mOnWindowDismissedCallback != null) {
|
||||||
mOnWindowDismissedCallback.onWindowDismissed(finishTask);
|
mOnWindowDismissedCallback.onWindowDismissed(finishTask, suppressWindowTransition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2990,19 +2990,17 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
|
|||||||
swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
|
swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDismissed(SwipeDismissLayout layout) {
|
public void onDismissed(SwipeDismissLayout layout) {
|
||||||
dispatchOnWindowDismissed(false /*finishTask*/);
|
dispatchOnWindowDismissed(false /*finishTask*/, true /*suppressWindowTransition*/);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
swipeDismiss.setOnSwipeProgressChangedListener(
|
swipeDismiss.setOnSwipeProgressChangedListener(
|
||||||
new SwipeDismissLayout.OnSwipeProgressChangedListener() {
|
new SwipeDismissLayout.OnSwipeProgressChangedListener() {
|
||||||
private static final float ALPHA_DECREASE = 0.5f;
|
|
||||||
private boolean mIsTranslucent = false;
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwipeProgressChanged(
|
public void onSwipeProgressChanged(
|
||||||
SwipeDismissLayout layout, float progress, float translate) {
|
SwipeDismissLayout layout, float alpha, float translate) {
|
||||||
WindowManager.LayoutParams newParams = getAttributes();
|
WindowManager.LayoutParams newParams = getAttributes();
|
||||||
newParams.x = (int) translate;
|
newParams.x = (int) translate;
|
||||||
newParams.alpha = 1 - (progress * ALPHA_DECREASE);
|
newParams.alpha = alpha;
|
||||||
setAttributes(newParams);
|
setAttributes(newParams);
|
||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|||||||
@@ -416,7 +416,8 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
|
|||||||
if (mClickTarget == mMaximize) {
|
if (mClickTarget == mMaximize) {
|
||||||
maximizeWindow();
|
maximizeWindow();
|
||||||
} else if (mClickTarget == mClose) {
|
} else if (mClickTarget == mClose) {
|
||||||
mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
|
mOwner.dispatchOnWindowDismissed(
|
||||||
|
true /*finishTask*/, false /*suppressWindowTransition*/);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
package com.android.internal.widget;
|
package com.android.internal.widget;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.TimeInterpolator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -30,6 +34,7 @@ import android.view.View;
|
|||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,12 +54,12 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
/**
|
/**
|
||||||
* Called when the layout has been swiped and the position of the window should change.
|
* Called when the layout has been swiped and the position of the window should change.
|
||||||
*
|
*
|
||||||
* @param progress A number in [0, 1] representing how far to the
|
* @param alpha A number in [0, 1] representing what the alpha transparency of the window
|
||||||
* right the window has been swiped
|
* should be.
|
||||||
* @param translate A number in [0, w], where w is the width of the
|
* @param translate A number in [0, w], where w is the width of the
|
||||||
* layout. This is equivalent to progress * layout.getWidth().
|
* layout. This is equivalent to progress * layout.getWidth().
|
||||||
*/
|
*/
|
||||||
void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate);
|
void onSwipeProgressChanged(SwipeDismissLayout layout, float alpha, float translate);
|
||||||
|
|
||||||
void onSwipeCancelled(SwipeDismissLayout layout);
|
void onSwipeCancelled(SwipeDismissLayout layout);
|
||||||
}
|
}
|
||||||
@@ -72,6 +77,9 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
private boolean mDiscardIntercept;
|
private boolean mDiscardIntercept;
|
||||||
private VelocityTracker mVelocityTracker;
|
private VelocityTracker mVelocityTracker;
|
||||||
private float mTranslationX;
|
private float mTranslationX;
|
||||||
|
private boolean mBlockGesture = false;
|
||||||
|
|
||||||
|
private final DismissAnimator mDismissAnimator = new DismissAnimator();
|
||||||
|
|
||||||
private OnDismissedListener mDismissedListener;
|
private OnDismissedListener mDismissedListener;
|
||||||
private OnSwipeProgressChangedListener mProgressListener;
|
private OnSwipeProgressChangedListener mProgressListener;
|
||||||
@@ -168,6 +176,10 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
checkGesture((ev));
|
||||||
|
if (mBlockGesture) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!mDismissable) {
|
if (!mDismissable) {
|
||||||
return super.onInterceptTouchEvent(ev);
|
return super.onInterceptTouchEvent(ev);
|
||||||
}
|
}
|
||||||
@@ -231,6 +243,10 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
checkGesture((ev));
|
||||||
|
if (mBlockGesture) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (mVelocityTracker == null || !mDismissable) {
|
if (mVelocityTracker == null || !mDismissable) {
|
||||||
return super.onTouchEvent(ev);
|
return super.onTouchEvent(ev);
|
||||||
}
|
}
|
||||||
@@ -240,9 +256,9 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
updateDismiss(ev);
|
updateDismiss(ev);
|
||||||
if (mDismissed) {
|
if (mDismissed) {
|
||||||
dismiss();
|
mDismissAnimator.animateDismissal(ev.getRawX() - mDownX);
|
||||||
} else if (mSwiping) {
|
} else if (mSwiping) {
|
||||||
cancel();
|
mDismissAnimator.animateRecovery(ev.getRawX() - mDownX);
|
||||||
}
|
}
|
||||||
resetMembers();
|
resetMembers();
|
||||||
break;
|
break;
|
||||||
@@ -270,7 +286,8 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
private void setProgress(float deltaX) {
|
private void setProgress(float deltaX) {
|
||||||
mTranslationX = deltaX;
|
mTranslationX = deltaX;
|
||||||
if (mProgressListener != null && deltaX >= 0) {
|
if (mProgressListener != null && deltaX >= 0) {
|
||||||
mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX);
|
mProgressListener.onSwipeProgressChanged(
|
||||||
|
this, progressToAlpha(deltaX / getWidth()), deltaX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,4 +395,91 @@ public class SwipeDismissLayout extends FrameLayout {
|
|||||||
|
|
||||||
mDismissable = dismissable;
|
mDismissable = dismissable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkGesture(MotionEvent ev) {
|
||||||
|
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
|
mBlockGesture = mDismissAnimator.isAnimating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float progressToAlpha(float progress) {
|
||||||
|
return 1 - progress * progress * progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DismissAnimator implements AnimatorUpdateListener, Animator.AnimatorListener {
|
||||||
|
private final TimeInterpolator DISMISS_INTERPOLATOR = new DecelerateInterpolator(1.5f);
|
||||||
|
private final long DISMISS_DURATION = 250;
|
||||||
|
|
||||||
|
private final ValueAnimator mDismissAnimator = new ValueAnimator();
|
||||||
|
private boolean mWasCanceled = false;
|
||||||
|
private boolean mDismissOnComplete = false;
|
||||||
|
|
||||||
|
/* package */ DismissAnimator() {
|
||||||
|
mDismissAnimator.addUpdateListener(this);
|
||||||
|
mDismissAnimator.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void animateDismissal(float currentTranslation) {
|
||||||
|
animate(
|
||||||
|
currentTranslation / getWidth(),
|
||||||
|
1,
|
||||||
|
DISMISS_DURATION,
|
||||||
|
DISMISS_INTERPOLATOR,
|
||||||
|
true /* dismiss */);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void animateRecovery(float currentTranslation) {
|
||||||
|
animate(
|
||||||
|
currentTranslation / getWidth(),
|
||||||
|
0,
|
||||||
|
DISMISS_DURATION,
|
||||||
|
DISMISS_INTERPOLATOR,
|
||||||
|
false /* don't dismiss */);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ boolean isAnimating() {
|
||||||
|
return mDismissAnimator.isStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(float from, float to, long duration, TimeInterpolator interpolator,
|
||||||
|
boolean dismissOnComplete) {
|
||||||
|
mDismissAnimator.cancel();
|
||||||
|
mDismissOnComplete = dismissOnComplete;
|
||||||
|
mDismissAnimator.setFloatValues(from, to);
|
||||||
|
mDismissAnimator.setDuration(duration);
|
||||||
|
mDismissAnimator.setInterpolator(interpolator);
|
||||||
|
mDismissAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
float value = (Float) animation.getAnimatedValue();
|
||||||
|
setProgress(value * getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
mWasCanceled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
mWasCanceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
if (!mWasCanceled) {
|
||||||
|
if (mDismissOnComplete) {
|
||||||
|
dismiss();
|
||||||
|
} else {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user