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:
Ned Burns
2016-12-02 17:25:33 -05:00
parent f7964be938
commit 7d6cb913de
6 changed files with 126 additions and 17 deletions

View File

@@ -2946,8 +2946,11 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
@Override
public void onWindowDismissed(boolean finishTask) {
public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
if (suppressWindowTransition) {
overridePendingTransition(0, 0);
}
}

View File

@@ -744,7 +744,7 @@ public class Dialog implements DialogInterface, Window.Callback,
/** @hide */
@Override
public void onWindowDismissed(boolean finishTask) {
public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
dismiss();
}

View File

@@ -581,8 +581,10 @@ public abstract class Window {
* Called when a window is dismissed. This informs the callback that the
* window is gone, and it should finish itself.
* @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 */
@@ -871,9 +873,10 @@ public abstract class Window {
}
/** @hide */
public final void dispatchOnWindowDismissed(boolean finishTask) {
public final void dispatchOnWindowDismissed(
boolean finishTask, boolean suppressWindowTransition) {
if (mOnWindowDismissedCallback != null) {
mOnWindowDismissedCallback.onWindowDismissed(finishTask);
mOnWindowDismissedCallback.onWindowDismissed(finishTask, suppressWindowTransition);
}
}

View File

@@ -2990,19 +2990,17 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
@Override
public void onDismissed(SwipeDismissLayout layout) {
dispatchOnWindowDismissed(false /*finishTask*/);
dispatchOnWindowDismissed(false /*finishTask*/, true /*suppressWindowTransition*/);
}
});
swipeDismiss.setOnSwipeProgressChangedListener(
new SwipeDismissLayout.OnSwipeProgressChangedListener() {
private static final float ALPHA_DECREASE = 0.5f;
private boolean mIsTranslucent = false;
@Override
public void onSwipeProgressChanged(
SwipeDismissLayout layout, float progress, float translate) {
SwipeDismissLayout layout, float alpha, float translate) {
WindowManager.LayoutParams newParams = getAttributes();
newParams.x = (int) translate;
newParams.alpha = 1 - (progress * ALPHA_DECREASE);
newParams.alpha = alpha;
setAttributes(newParams);
int flags = 0;

View File

@@ -416,7 +416,8 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
if (mClickTarget == mMaximize) {
maximizeWindow();
} else if (mClickTarget == mClose) {
mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
mOwner.dispatchOnWindowDismissed(
true /*finishTask*/, false /*suppressWindowTransition*/);
}
return true;
}

View File

@@ -16,6 +16,10 @@
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.content.BroadcastReceiver;
import android.content.Context;
@@ -30,6 +34,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
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.
*
* @param progress A number in [0, 1] representing how far to the
* right the window has been swiped
* @param alpha A number in [0, 1] representing what the alpha transparency of the window
* should be.
* @param translate A number in [0, w], where w is the width of the
* 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);
}
@@ -72,6 +77,9 @@ public class SwipeDismissLayout extends FrameLayout {
private boolean mDiscardIntercept;
private VelocityTracker mVelocityTracker;
private float mTranslationX;
private boolean mBlockGesture = false;
private final DismissAnimator mDismissAnimator = new DismissAnimator();
private OnDismissedListener mDismissedListener;
private OnSwipeProgressChangedListener mProgressListener;
@@ -168,6 +176,10 @@ public class SwipeDismissLayout extends FrameLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
checkGesture((ev));
if (mBlockGesture) {
return true;
}
if (!mDismissable) {
return super.onInterceptTouchEvent(ev);
}
@@ -231,6 +243,10 @@ public class SwipeDismissLayout extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
checkGesture((ev));
if (mBlockGesture) {
return true;
}
if (mVelocityTracker == null || !mDismissable) {
return super.onTouchEvent(ev);
}
@@ -240,9 +256,9 @@ public class SwipeDismissLayout extends FrameLayout {
case MotionEvent.ACTION_UP:
updateDismiss(ev);
if (mDismissed) {
dismiss();
mDismissAnimator.animateDismissal(ev.getRawX() - mDownX);
} else if (mSwiping) {
cancel();
mDismissAnimator.animateRecovery(ev.getRawX() - mDownX);
}
resetMembers();
break;
@@ -270,7 +286,8 @@ public class SwipeDismissLayout extends FrameLayout {
private void setProgress(float deltaX) {
mTranslationX = deltaX;
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;
}
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) {
}
}
}