blob: 8f79bdabedc9051c5d114bac77bce054ec858445 [file] [log] [blame]
Michael Jurka07d40462011-07-19 10:54:38 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui;
18
19import android.animation.Animator;
Chet Haase2f2022a2011-10-11 06:41:59 -070020import android.animation.AnimatorListenerAdapter;
Michael Jurka07d40462011-07-19 10:54:38 -070021import android.animation.ObjectAnimator;
Michael Jurka07d40462011-07-19 10:54:38 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
Dan Sandlereceda3d2014-07-21 15:35:01 -040024import android.content.Context;
Michael Jurka07d40462011-07-19 10:54:38 -070025import android.graphics.RectF;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040026import android.os.Handler;
Michael Jurka07d40462011-07-19 10:54:38 -070027import android.util.Log;
Michael Jurka07d40462011-07-19 10:54:38 -070028import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040031import android.view.ViewConfiguration;
John Spurlockde84f0e2013-06-12 12:41:00 -040032import android.view.accessibility.AccessibilityEvent;
Michael Jurka07d40462011-07-19 10:54:38 -070033
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070034import com.android.systemui.classifier.FalsingManager;
Mady Mellor28796312016-03-08 14:12:42 -080035import com.android.systemui.statusbar.FlingAnimationUtils;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070036
dongwan0605.kim30637e42016-03-02 17:16:47 +090037import java.util.HashMap;
38
Daniel Sandler6a858c32012-03-12 14:38:58 -040039public class SwipeHelper implements Gefingerpoken {
Michael Jurka07d40462011-07-19 10:54:38 -070040 static final String TAG = "com.android.systemui.SwipeHelper";
Daniel Sandler96f48182011-08-17 09:50:35 -040041 private static final boolean DEBUG = false;
Michael Jurka07d40462011-07-19 10:54:38 -070042 private static final boolean DEBUG_INVALIDATE = false;
43 private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
Michael Jurka3cd0a592011-08-16 12:40:30 -070044 private static final boolean CONSTRAIN_SWIPE = true;
45 private static final boolean FADE_OUT_DURING_SWIPE = true;
46 private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
Michael Jurka07d40462011-07-19 10:54:38 -070047
48 public static final int X = 0;
49 public static final int Y = 1;
50
Michael Jurka07d40462011-07-19 10:54:38 -070051 private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
Michael Jurka0e8063a2011-09-09 15:31:55 -070052 private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
53 private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
54 private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
55 private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
Michael Jurka07d40462011-07-19 10:54:38 -070056
Adrian Roos5d9cc662014-05-28 17:08:13 +020057 public static float SWIPE_PROGRESS_FADE_START = 0f; // fraction of thumbnail width
Michael Jurka07d40462011-07-19 10:54:38 -070058 // where fade starts
Adrian Roos5d9cc662014-05-28 17:08:13 +020059 static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
60 // beyond which swipe progress->0
61 private float mMinSwipeProgress = 0f;
62 private float mMaxSwipeProgress = 1f;
Michael Jurka07d40462011-07-19 10:54:38 -070063
Mady Mellor28796312016-03-08 14:12:42 -080064 private FlingAnimationUtils mFlingAnimationUtils;
Michael Jurka07d40462011-07-19 10:54:38 -070065 private float mPagingTouchSlop;
66 private Callback mCallback;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040067 private Handler mHandler;
Michael Jurka07d40462011-07-19 10:54:38 -070068 private int mSwipeDirection;
69 private VelocityTracker mVelocityTracker;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070070 private FalsingManager mFalsingManager;
Michael Jurka07d40462011-07-19 10:54:38 -070071
72 private float mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -080073 private float mPerpendicularInitialTouchPos;
Michael Jurka07d40462011-07-19 10:54:38 -070074 private boolean mDragging;
dongwan0605.kim30637e42016-03-02 17:16:47 +090075 private boolean mSnappingChild;
Michael Jurka07d40462011-07-19 10:54:38 -070076 private View mCurrView;
Michael Jurka3cd0a592011-08-16 12:40:30 -070077 private boolean mCanCurrViewBeDimissed;
Michael Jurka07d40462011-07-19 10:54:38 -070078 private float mDensityScale;
Mady Mellor4b80b102016-01-22 08:03:58 -080079 private float mTranslation = 0;
Michael Jurka07d40462011-07-19 10:54:38 -070080
Daniel Sandlerf7a19562012-04-04 14:04:21 -040081 private boolean mLongPressSent;
Dan Sandler4247a5c2014-07-23 15:58:08 -040082 private LongPressListener mLongPressListener;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040083 private Runnable mWatchLongPress;
Daniel Sandler469e96e2012-05-04 15:56:19 -040084 private long mLongPressTimeout;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040085
Dan Sandler4247a5c2014-07-23 15:58:08 -040086 final private int[] mTmpPos = new int[2];
Selim Cinek19c8c702014-08-25 22:09:19 +020087 private int mFalsingThreshold;
88 private boolean mTouchAboveFalsingThreshold;
Winson671e8f92016-01-12 13:16:56 -080089 private boolean mDisableHwLayers;
Dan Sandler4247a5c2014-07-23 15:58:08 -040090
dongwan0605.kim30637e42016-03-02 17:16:47 +090091 private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
92
Dan Sandlereceda3d2014-07-21 15:35:01 -040093 public SwipeHelper(int swipeDirection, Callback callback, Context context) {
Michael Jurka07d40462011-07-19 10:54:38 -070094 mCallback = callback;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040095 mHandler = new Handler();
Michael Jurka07d40462011-07-19 10:54:38 -070096 mSwipeDirection = swipeDirection;
97 mVelocityTracker = VelocityTracker.obtain();
Dan Sandlereceda3d2014-07-21 15:35:01 -040098 mDensityScale = context.getResources().getDisplayMetrics().density;
99 mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
Daniel Sandler469e96e2012-05-04 15:56:19 -0400100
101 mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
Selim Cinek19c8c702014-08-25 22:09:19 +0200102 mFalsingThreshold = context.getResources().getDimensionPixelSize(
103 R.dimen.swipe_helper_falsing_threshold);
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700104 mFalsingManager = FalsingManager.getInstance(context);
Mady Mellor28796312016-03-08 14:12:42 -0800105 mFlingAnimationUtils = new FlingAnimationUtils(context,
106 MAX_ESCAPE_ANIMATION_DURATION / 1000f /* maxLengthSeconds */);
Michael Jurka07d40462011-07-19 10:54:38 -0700107 }
108
Dan Sandler4247a5c2014-07-23 15:58:08 -0400109 public void setLongPressListener(LongPressListener listener) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400110 mLongPressListener = listener;
111 }
112
Michael Jurka07d40462011-07-19 10:54:38 -0700113 public void setDensityScale(float densityScale) {
114 mDensityScale = densityScale;
115 }
116
117 public void setPagingTouchSlop(float pagingTouchSlop) {
118 mPagingTouchSlop = pagingTouchSlop;
119 }
120
Winson671e8f92016-01-12 13:16:56 -0800121 public void setDisableHardwareLayers(boolean disableHwLayers) {
122 mDisableHwLayers = disableHwLayers;
123 }
124
Michael Jurka07d40462011-07-19 10:54:38 -0700125 private float getPos(MotionEvent ev) {
126 return mSwipeDirection == X ? ev.getX() : ev.getY();
127 }
128
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800129 private float getPerpendicularPos(MotionEvent ev) {
130 return mSwipeDirection == X ? ev.getY() : ev.getX();
131 }
132
Mady Mellor4b80b102016-01-22 08:03:58 -0800133 protected float getTranslation(View v) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700134 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
Michael Jurka07d40462011-07-19 10:54:38 -0700135 }
136
137 private float getVelocity(VelocityTracker vt) {
138 return mSwipeDirection == X ? vt.getXVelocity() :
139 vt.getYVelocity();
140 }
141
Mady Mellor4b80b102016-01-22 08:03:58 -0800142 protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
Michael Jurka07d40462011-07-19 10:54:38 -0700143 ObjectAnimator anim = ObjectAnimator.ofFloat(v,
Winsonc5fd3502016-01-18 15:18:37 -0800144 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
Michael Jurka07d40462011-07-19 10:54:38 -0700145 return anim;
146 }
147
148 private float getPerpendicularVelocity(VelocityTracker vt) {
149 return mSwipeDirection == X ? vt.getYVelocity() :
150 vt.getXVelocity();
151 }
152
Mady Mellor4b80b102016-01-22 08:03:58 -0800153 protected Animator getViewTranslationAnimator(View v, float target,
154 AnimatorUpdateListener listener) {
155 ObjectAnimator anim = createTranslationAnimation(v, target);
Mady Mellor34958fa2016-02-23 09:52:17 -0800156 if (listener != null) {
157 anim.addUpdateListener(listener);
158 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800159 return anim;
160 }
161
162 protected void setTranslation(View v, float translate) {
163 if (v == null) {
164 return;
165 }
Michael Jurka07d40462011-07-19 10:54:38 -0700166 if (mSwipeDirection == X) {
167 v.setTranslationX(translate);
168 } else {
169 v.setTranslationY(translate);
170 }
171 }
172
Winson671e8f92016-01-12 13:16:56 -0800173 protected float getSize(View v) {
Michael Jurka07d40462011-07-19 10:54:38 -0700174 return mSwipeDirection == X ? v.getMeasuredWidth() :
175 v.getMeasuredHeight();
176 }
177
Adrian Roos5d9cc662014-05-28 17:08:13 +0200178 public void setMinSwipeProgress(float minSwipeProgress) {
179 mMinSwipeProgress = minSwipeProgress;
Michael Jurka4eaa9832012-02-29 15:51:49 -0800180 }
181
Adrian Roos5d9cc662014-05-28 17:08:13 +0200182 public void setMaxSwipeProgress(float maxSwipeProgress) {
183 mMaxSwipeProgress = maxSwipeProgress;
Adrian Roosde61fd72014-05-26 14:59:15 +0200184 }
185
Adrian Roos5d9cc662014-05-28 17:08:13 +0200186 private float getSwipeProgressForOffset(View view) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700187 float viewSize = getSize(view);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200188 final float fadeSize = SWIPE_PROGRESS_FADE_END * viewSize;
Michael Jurka07d40462011-07-19 10:54:38 -0700189 float result = 1.0f;
Michael Jurka3cd0a592011-08-16 12:40:30 -0700190 float pos = getTranslation(view);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200191 if (pos >= viewSize * SWIPE_PROGRESS_FADE_START) {
192 result = 1.0f - (pos - viewSize * SWIPE_PROGRESS_FADE_START) / fadeSize;
193 } else if (pos < viewSize * (1.0f - SWIPE_PROGRESS_FADE_START)) {
194 result = 1.0f + (viewSize * SWIPE_PROGRESS_FADE_START + pos) / fadeSize;
Michael Jurka07d40462011-07-19 10:54:38 -0700195 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200196 return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
Michael Jurka07d40462011-07-19 10:54:38 -0700197 }
198
Adrian Roos5d9cc662014-05-28 17:08:13 +0200199 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
200 float swipeProgress = getSwipeProgressForOffset(animView);
201 if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
202 if (FADE_OUT_DURING_SWIPE && dismissable) {
203 float alpha = swipeProgress;
Winson671e8f92016-01-12 13:16:56 -0800204 if (!mDisableHwLayers) {
205 if (alpha != 0f && alpha != 1f) {
206 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
207 } else {
208 animView.setLayerType(View.LAYER_TYPE_NONE, null);
209 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200210 }
211 animView.setAlpha(getSwipeProgressForOffset(animView));
Michael Jurka499293f2013-03-06 18:08:45 +0100212 }
Michael Jurka67b03702013-02-15 17:35:48 +0100213 }
214 invalidateGlobalRegion(animView);
215 }
216
Daniel Sandlera375c942011-07-29 00:33:53 -0400217 // invalidate the view's own bounds all the way up the view hierarchy
218 public static void invalidateGlobalRegion(View view) {
219 invalidateGlobalRegion(
220 view,
221 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
222 }
223
224 // invalidate a rectangle relative to the view's coordinate system all the way up the view
225 // hierarchy
226 public static void invalidateGlobalRegion(View view, RectF childBounds) {
Daniel Sandler96f48182011-08-17 09:50:35 -0400227 //childBounds.offset(view.getTranslationX(), view.getTranslationY());
Michael Jurka07d40462011-07-19 10:54:38 -0700228 if (DEBUG_INVALIDATE)
229 Log.v(TAG, "-------------");
230 while (view.getParent() != null && view.getParent() instanceof View) {
231 view = (View) view.getParent();
232 view.getMatrix().mapRect(childBounds);
233 view.invalidate((int) Math.floor(childBounds.left),
234 (int) Math.floor(childBounds.top),
235 (int) Math.ceil(childBounds.right),
236 (int) Math.ceil(childBounds.bottom));
237 if (DEBUG_INVALIDATE) {
238 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
239 + "," + (int) Math.floor(childBounds.top)
240 + "," + (int) Math.ceil(childBounds.right)
241 + "," + (int) Math.ceil(childBounds.bottom));
242 }
243 }
244 }
245
Daniel Sandler469e96e2012-05-04 15:56:19 -0400246 public void removeLongPressCallback() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400247 if (mWatchLongPress != null) {
248 mHandler.removeCallbacks(mWatchLongPress);
Daniel Sandler491d3a92012-05-16 13:04:06 -0400249 mWatchLongPress = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400250 }
251 }
252
Dan Sandler4247a5c2014-07-23 15:58:08 -0400253 public boolean onInterceptTouchEvent(final MotionEvent ev) {
Michael Jurka07d40462011-07-19 10:54:38 -0700254 final int action = ev.getAction();
255
256 switch (action) {
257 case MotionEvent.ACTION_DOWN:
Selim Cinek19c8c702014-08-25 22:09:19 +0200258 mTouchAboveFalsingThreshold = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700259 mDragging = false;
dongwan0605.kim30637e42016-03-02 17:16:47 +0900260 mSnappingChild = false;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400261 mLongPressSent = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700262 mVelocityTracker.clear();
Mady Mellor4b80b102016-01-22 08:03:58 -0800263 mCurrView = mCallback.getChildAtPosition(ev);
264
Michael Jurka21ce2d82011-09-02 15:28:06 -0700265 if (mCurrView != null) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800266 onDownUpdate(mCurrView);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700267 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
268 mVelocityTracker.addMovement(ev);
269 mInitialTouchPos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800270 mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
Mady Mellor4b80b102016-01-22 08:03:58 -0800271 mTranslation = getTranslation(mCurrView);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400272 if (mLongPressListener != null) {
273 if (mWatchLongPress == null) {
274 mWatchLongPress = new Runnable() {
275 @Override
276 public void run() {
Selim Cinek197823d2016-03-24 13:06:00 -0700277 if (mCurrView != null && !mLongPressSent) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400278 mLongPressSent = true;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400279 mCurrView.sendAccessibilityEvent(
280 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
281 mCurrView.getLocationOnScreen(mTmpPos);
282 final int x = (int) ev.getRawX() - mTmpPos[0];
283 final int y = (int) ev.getRawY() - mTmpPos[1];
284 mLongPressListener.onLongPress(mCurrView, x, y);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400285 }
286 }
287 };
288 }
Daniel Sandler469e96e2012-05-04 15:56:19 -0400289 mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400290 }
Michael Jurka21ce2d82011-09-02 15:28:06 -0700291 }
Michael Jurka07d40462011-07-19 10:54:38 -0700292 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400293
Michael Jurka07d40462011-07-19 10:54:38 -0700294 case MotionEvent.ACTION_MOVE:
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400295 if (mCurrView != null && !mLongPressSent) {
Michael Jurka07d40462011-07-19 10:54:38 -0700296 mVelocityTracker.addMovement(ev);
297 float pos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800298 float perpendicularPos = getPerpendicularPos(ev);
Michael Jurka07d40462011-07-19 10:54:38 -0700299 float delta = pos - mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800300 float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
301 if (Math.abs(delta) > mPagingTouchSlop
302 && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700303 mCallback.onBeginDrag(mCurrView);
304 mDragging = true;
Mady Mellor4b80b102016-01-22 08:03:58 -0800305 mInitialTouchPos = getPos(ev);
306 mTranslation = getTranslation(mCurrView);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400307 removeLongPressCallback();
Michael Jurka07d40462011-07-19 10:54:38 -0700308 }
309 }
310 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400311
Michael Jurka07d40462011-07-19 10:54:38 -0700312 case MotionEvent.ACTION_UP:
Jeff Brown68ebcdf2011-09-12 14:12:17 -0700313 case MotionEvent.ACTION_CANCEL:
Dan Sandler4247a5c2014-07-23 15:58:08 -0400314 final boolean captured = (mDragging || mLongPressSent);
Michael Jurka07d40462011-07-19 10:54:38 -0700315 mDragging = false;
316 mCurrView = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400317 mLongPressSent = false;
Daniel Sandler491d3a92012-05-16 13:04:06 -0400318 removeLongPressCallback();
Dan Sandler4247a5c2014-07-23 15:58:08 -0400319 if (captured) return true;
Michael Jurka07d40462011-07-19 10:54:38 -0700320 break;
321 }
Dan Sandler4247a5c2014-07-23 15:58:08 -0400322 return mDragging || mLongPressSent;
Michael Jurka07d40462011-07-19 10:54:38 -0700323 }
324
Chet Haase2f2022a2011-10-11 06:41:59 -0700325 /**
326 * @param view The view to be dismissed
327 * @param velocity The desired pixels/second speed at which the view should move
328 */
Michael Jurka3cd0a592011-08-16 12:40:30 -0700329 public void dismissChild(final View view, float velocity) {
Mady Mellor28796312016-03-08 14:12:42 -0800330 dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
331 velocity == 0 /* useAccelerateInterpolator */, 0 /* fixedDuration */);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400332 }
333
334 /**
335 * @param view The view to be dismissed
336 * @param velocity The desired pixels/second speed at which the view should move
337 * @param endAction The action to perform at the end
338 * @param delay The delay after which we should start
339 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
340 * @param fixedDuration If not 0, this exact duration will be taken
341 */
Mady Mellor4b80b102016-01-22 08:03:58 -0800342 public void dismissChild(final View animView, float velocity, final Runnable endAction,
Dan Sandlereceda3d2014-07-21 15:35:01 -0400343 long delay, boolean useAccelerateInterpolator, long fixedDuration) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800344 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
Michael Jurka07d40462011-07-19 10:54:38 -0700345 float newPos;
Mady Mellor4b80b102016-01-22 08:03:58 -0800346 boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
Michael Jurkac6461ca2011-09-02 12:12:15 -0700347
348 if (velocity < 0
349 || (velocity == 0 && getTranslation(animView) < 0)
350 // if we use the Menu to dismiss an item in landscape, animate up
Selim Cinek1d59af42014-10-30 21:04:38 +0100351 || (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)
352 // if the language is rtl we prefer swiping to the left
353 || (velocity == 0 && getTranslation(animView) == 0 && isLayoutRtl)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700354 newPos = -getSize(animView);
355 } else {
356 newPos = getSize(animView);
357 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400358 long duration;
359 if (fixedDuration == 0) {
360 duration = MAX_ESCAPE_ANIMATION_DURATION;
361 if (velocity != 0) {
362 duration = Math.min(duration,
363 (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
364 .abs(velocity))
365 );
366 } else {
367 duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
368 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700369 } else {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400370 duration = fixedDuration;
Michael Jurka07d40462011-07-19 10:54:38 -0700371 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700372
Winson671e8f92016-01-12 13:16:56 -0800373 if (!mDisableHwLayers) {
374 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
375 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800376 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
377 public void onAnimationUpdate(ValueAnimator animation) {
378 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
379 }
380 };
381
382 Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800383 if (anim == null) {
384 return;
385 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400386 if (useAccelerateInterpolator) {
Selim Cinekc18010f2016-01-20 13:41:30 -0800387 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Mady Mellor28796312016-03-08 14:12:42 -0800388 anim.setDuration(duration);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400389 } else {
Mady Mellor28796312016-03-08 14:12:42 -0800390 mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
391 newPos, velocity, getSize(animView));
Dan Sandlereceda3d2014-07-21 15:35:01 -0400392 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400393 if (delay > 0) {
394 anim.setStartDelay(delay);
395 }
Chet Haase2f2022a2011-10-11 06:41:59 -0700396 anim.addListener(new AnimatorListenerAdapter() {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900397 private boolean mCancelled;
398
399 public void onAnimationCancel(Animator animation) {
400 mCancelled = true;
401 }
402
Michael Jurka07d40462011-07-19 10:54:38 -0700403 public void onAnimationEnd(Animator animation) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800404 updateSwipeProgressFromOffset(animView, canBeDismissed);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900405 mDismissPendingMap.remove(animView);
406 if (!mCancelled) {
407 mCallback.onChildDismissed(animView);
408 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400409 if (endAction != null) {
410 endAction.run();
411 }
Winson671e8f92016-01-12 13:16:56 -0800412 if (!mDisableHwLayers) {
413 animView.setLayerType(View.LAYER_TYPE_NONE, null);
414 }
Michael Jurka07d40462011-07-19 10:54:38 -0700415 }
416 });
dongwan0605.kim30637e42016-03-02 17:16:47 +0900417
Winson8aa99592016-01-19 15:07:07 -0800418 prepareDismissAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900419 mDismissPendingMap.put(animView, anim);
Michael Jurka07d40462011-07-19 10:54:38 -0700420 anim.start();
421 }
422
Winson8aa99592016-01-19 15:07:07 -0800423 /**
424 * Called to update the dismiss animation.
425 */
426 protected void prepareDismissAnimation(View view, Animator anim) {
427 // Do nothing
428 }
429
Mady Mellor4b80b102016-01-22 08:03:58 -0800430 public void snapChild(final View animView, final float targetLeft, float velocity) {
431 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
432 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
433 public void onAnimationUpdate(ValueAnimator animation) {
434 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
435 }
436 };
437
438 Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800439 if (anim == null) {
440 return;
441 }
Michael Jurka07d40462011-07-19 10:54:38 -0700442 int duration = SNAP_ANIM_LEN;
443 anim.setDuration(duration);
Michael Jurka67b03702013-02-15 17:35:48 +0100444 anim.addListener(new AnimatorListenerAdapter() {
445 public void onAnimationEnd(Animator animator) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900446 mSnappingChild = false;
Mady Mellor4b80b102016-01-22 08:03:58 -0800447 updateSwipeProgressFromOffset(animView, canBeDismissed);
448 mCallback.onChildSnappedBack(animView, targetLeft);
Michael Jurka07d40462011-07-19 10:54:38 -0700449 }
450 });
Winson8aa99592016-01-19 15:07:07 -0800451 prepareSnapBackAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900452 mSnappingChild = true;
Michael Jurka07d40462011-07-19 10:54:38 -0700453 anim.start();
454 }
455
Winsonc5fd3502016-01-18 15:18:37 -0800456 /**
457 * Called to update the snap back animation.
458 */
Winson8aa99592016-01-19 15:07:07 -0800459 protected void prepareSnapBackAnimation(View view, Animator anim) {
Winsonc5fd3502016-01-18 15:18:37 -0800460 // Do nothing
461 }
462
Mady Mellor4b80b102016-01-22 08:03:58 -0800463 /**
464 * Called when there's a down event.
465 */
466 public void onDownUpdate(View currView) {
467 // Do nothing
468 }
469
470 /**
471 * Called on a move event.
472 */
473 protected void onMoveUpdate(View view, float totalTranslation, float delta) {
474 // Do nothing
475 }
476
477 /**
478 * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
479 * view is being animated to dismiss or snap.
480 */
481 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
482 updateSwipeProgressFromOffset(animView, canBeDismissed);
483 }
484
dongwan0605.kim30637e42016-03-02 17:16:47 +0900485 private void snapChildInstantly(final View view) {
486 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
487 setTranslation(view, 0);
488 updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
489 }
490
491 public void snapChildIfNeeded(final View view, boolean animate) {
492 if ((mDragging && mCurrView == view) || mSnappingChild) {
493 return;
494 }
495 boolean needToSnap = false;
496 Animator dismissPendingAnim = mDismissPendingMap.get(view);
497 if (dismissPendingAnim != null) {
498 needToSnap = true;
499 dismissPendingAnim.cancel();
500 } else if (getTranslation(view) != 0) {
501 needToSnap = true;
502 }
503 if (needToSnap) {
504 if (animate) {
505 snapChild(view, 0 /* targetLeft */, 0.0f /* velocity */);
506 } else {
507 snapChildInstantly(view);
508 }
509 }
510 }
511
Michael Jurka07d40462011-07-19 10:54:38 -0700512 public boolean onTouchEvent(MotionEvent ev) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400513 if (mLongPressSent) {
514 return true;
515 }
516
Michael Jurka07d40462011-07-19 10:54:38 -0700517 if (!mDragging) {
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200518 if (mCallback.getChildAtPosition(ev) != null) {
519
520 // We are dragging directly over a card, make sure that we also catch the gesture
521 // even if nobody else wants the touch event.
522 onInterceptTouchEvent(ev);
523 return true;
524 } else {
525
526 // We are not doing anything, make sure the long press callback
527 // is not still ticking like a bomb waiting to go off.
528 removeLongPressCallback();
529 return false;
530 }
Michael Jurka07d40462011-07-19 10:54:38 -0700531 }
532
533 mVelocityTracker.addMovement(ev);
534 final int action = ev.getAction();
535 switch (action) {
536 case MotionEvent.ACTION_OUTSIDE:
537 case MotionEvent.ACTION_MOVE:
538 if (mCurrView != null) {
539 float delta = getPos(ev) - mInitialTouchPos;
Selim Cinek19c8c702014-08-25 22:09:19 +0200540 float absDelta = Math.abs(delta);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200541 if (absDelta >= getFalsingThreshold()) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200542 mTouchAboveFalsingThreshold = true;
543 }
Michael Jurka07d40462011-07-19 10:54:38 -0700544 // don't let items that can't be dismissed be dragged more than
545 // maxScrollDistance
546 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800547 float size = getSize(mCurrView);
548 float maxScrollDistance = 0.25f * size;
Selim Cinek19c8c702014-08-25 22:09:19 +0200549 if (absDelta >= size) {
Michael Jurka07d40462011-07-19 10:54:38 -0700550 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
551 } else {
552 delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
553 }
554 }
Michael Jurka67b03702013-02-15 17:35:48 +0100555
Mady Mellor4b80b102016-01-22 08:03:58 -0800556 setTranslation(mCurrView, mTranslation + delta);
557 updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
558 onMoveUpdate(mCurrView, mTranslation + delta, delta);
Michael Jurka07d40462011-07-19 10:54:38 -0700559 }
560 break;
561 case MotionEvent.ACTION_UP:
562 case MotionEvent.ACTION_CANCEL:
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000563 if (mCurrView == null) {
564 break;
565 }
566 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
567 float velocity = getVelocity(mVelocityTracker);
Michael Jurka07d40462011-07-19 10:54:38 -0700568
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000569 if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
570 if (isDismissGesture(ev)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700571 // flingadingy
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000572 dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f);
Michael Jurka07d40462011-07-19 10:54:38 -0700573 } else {
574 // snappity
Peter Ng622a9762011-08-29 10:56:53 -0700575 mCallback.onDragCancelled(mCurrView);
Mady Mellor4b80b102016-01-22 08:03:58 -0800576 snapChild(mCurrView, 0 /* leftTarget */, velocity);
Michael Jurka07d40462011-07-19 10:54:38 -0700577 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900578 mCurrView = null;
Michael Jurka07d40462011-07-19 10:54:38 -0700579 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900580 mDragging = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700581 break;
582 }
583 return true;
584 }
585
Selim Cinek34cf5c42014-09-26 15:39:00 +0200586 private int getFalsingThreshold() {
587 float factor = mCallback.getFalsingThresholdFactor();
588 return (int) (mFalsingThreshold * factor);
589 }
590
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000591 private float getMaxVelocity() {
592 return MAX_DISMISS_VELOCITY * mDensityScale;
593 }
594
595 protected float getEscapeVelocity() {
596 return SWIPE_ESCAPE_VELOCITY * mDensityScale;
597 }
598
599 protected boolean swipedFarEnough() {
600 float translation = getTranslation(mCurrView);
601 return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
602 }
603
604 protected boolean isDismissGesture(MotionEvent ev) {
605 boolean falsingDetected = mCallback.isAntiFalsingNeeded();
606 if (mFalsingManager.isClassiferEnabled()) {
607 falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
608 } else {
609 falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
610 }
611 return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
612 && ev.getActionMasked() == MotionEvent.ACTION_UP
613 && mCallback.canChildBeDismissed(mCurrView);
614 }
615
616 protected boolean swipedFastEnough() {
617 float velocity = getVelocity(mVelocityTracker);
618 float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
619 float translation = getTranslation(mCurrView);
620 boolean ret = (Math.abs(velocity) > getEscapeVelocity()) &&
621 (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
622 (velocity > 0) == (translation > 0);
623 return ret;
624 }
625
626 protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
627 float translation) {
628 return false;
629 }
630
Michael Jurka07d40462011-07-19 10:54:38 -0700631 public interface Callback {
632 View getChildAtPosition(MotionEvent ev);
633
Michael Jurka07d40462011-07-19 10:54:38 -0700634 boolean canChildBeDismissed(View v);
635
Selim Cinek19c8c702014-08-25 22:09:19 +0200636 boolean isAntiFalsingNeeded();
637
Michael Jurka07d40462011-07-19 10:54:38 -0700638 void onBeginDrag(View v);
639
640 void onChildDismissed(View v);
Peter Ng622a9762011-08-29 10:56:53 -0700641
642 void onDragCancelled(View v);
Selim Cinekeb973562014-05-02 17:07:49 +0200643
Mady Mellor4b80b102016-01-22 08:03:58 -0800644 /**
645 * Called when the child is snapped to a position.
646 *
647 * @param animView the view that was snapped.
648 * @param targetLeft the left position the view was snapped to.
649 */
650 void onChildSnappedBack(View animView, float targetLeft);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200651
652 /**
653 * Updates the swipe progress on a child.
654 *
655 * @return if true, prevents the default alpha fading.
656 */
657 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200658
659 /**
660 * @return The factor the falsing threshold should be multiplied with
661 */
662 float getFalsingThresholdFactor();
Michael Jurka07d40462011-07-19 10:54:38 -0700663 }
Dan Sandler4247a5c2014-07-23 15:58:08 -0400664
665 /**
666 * Equivalent to View.OnLongClickListener with coordinates
667 */
668 public interface LongPressListener {
669 /**
670 * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
671 * @return whether the longpress was handled
672 */
673 boolean onLongPress(View v, int x, int y);
674 }
Michael Jurka07d40462011-07-19 10:54:38 -0700675}