summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ned Burns <pixel@google.com> 2016-12-02 17:25:33 -0500
committer Ned Burns <pixel@google.com> 2016-12-05 19:38:14 -0500
commit7d6cb913de9b51dba0bae79e527b7d4fe79eb35d (patch)
treea068b099cfc314538fe8a791379ed6ac2cf03f0c
parentf7964be938338380654aaa41317b28335ed19084 (diff)
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
-rw-r--r--core/java/android/app/Activity.java5
-rw-r--r--core/java/android/app/Dialog.java2
-rw-r--r--core/java/android/view/Window.java9
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java8
-rw-r--r--core/java/com/android/internal/widget/DecorCaptionView.java3
-rw-r--r--core/java/com/android/internal/widget/SwipeDismissLayout.java116
6 files changed, 126 insertions, 17 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e4880b0f6a43..e28e74335036 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -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);
+ }
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index f29e576ff215..9c1778010f43 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -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();
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 1cca0742877f..a36127db559f 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -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);
}
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 713190d426bf..5d25b1b9de08 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -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;
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index fc68b849ed20..04767b00bdff 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -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;
}
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 31e67bd63116..83b49eb47e56 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -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) {
+ }
+ }
}