diff options
| author | 2016-12-02 17:25:33 -0500 | |
|---|---|---|
| committer | 2016-12-05 19:38:14 -0500 | |
| commit | 7d6cb913de9b51dba0bae79e527b7d4fe79eb35d (patch) | |
| tree | a068b099cfc314538fe8a791379ed6ac2cf03f0c | |
| parent | f7964be938338380654aaa41317b28335ed19084 (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.java | 5 | ||||
| -rw-r--r-- | core/java/android/app/Dialog.java | 2 | ||||
| -rw-r--r-- | core/java/android/view/Window.java | 9 | ||||
| -rw-r--r-- | core/java/com/android/internal/policy/PhoneWindow.java | 8 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/DecorCaptionView.java | 3 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/SwipeDismissLayout.java | 116 |
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) { + } + } } |