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 * @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);
}
} }

View File

@@ -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();
} }

View File

@@ -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);
} }
} }

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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) {
}
}
} }