ItemTouchHelper.java revision 3a500f61a8bdf48904f380f2d4925fe420d18ce7
1ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent/* 2ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Copyright (C) 2015 The Android Open Source Project 3ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 4ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Licensed under the Apache License, Version 2.0 (the "License"); 5ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * you may not use this file except in compliance with the License. 6ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * You may obtain a copy of the License at 7ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 8ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * http://www.apache.org/licenses/LICENSE-2.0 9ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 10ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Unless required by applicable law or agreed to in writing, software 11ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * distributed under the License is distributed on an "AS IS" BASIS, 12ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * See the License for the specific language governing permissions and 14ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * limitations under the License. 15ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 16ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 17ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentpackage android.support.v7.widget.helper; 18ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 19ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.content.res.Resources; 20ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.graphics.Canvas; 21ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.graphics.Rect; 22153b9fe667e6e78e0218ff0159353097428c7657Glenn Kastenimport android.os.Build; 23ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.annotation.Nullable; 24ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.animation.AnimatorCompatHelper; 25ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.animation.AnimatorListenerCompat; 26ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.animation.AnimatorUpdateListenerCompat; 27ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.animation.ValueAnimatorCompat; 28ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.view.GestureDetectorCompat; 29ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.view.MotionEventCompat; 30ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.view.VelocityTrackerCompat; 31ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v4.view.ViewCompat; 32ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v7.recyclerview.R; 33ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v7.widget.LinearLayoutManager; 34ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v7.widget.RecyclerView; 35ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v7.widget.RecyclerView.OnItemTouchListener; 36ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.support.v7.widget.RecyclerView.ViewHolder; 37ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.util.Log; 38ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.GestureDetector; 39ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.HapticFeedbackConstants; 40ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.MotionEvent; 41ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.VelocityTracker; 42ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.View; 43ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.ViewConfiguration; 44ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.ViewParent; 45ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport android.view.animation.Interpolator; 46ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 47ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport java.util.ArrayList; 48ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentimport java.util.List; 49ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 50ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent/** 51ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. 52ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 53ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * It works with a RecyclerView and a Callback class, which configures what type of interactions 54ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * are enabled and also receives events when user performs these actions. 55ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 56ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Depending on which functionality you support, you should override 57ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or 58ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link Callback#onSwiped(ViewHolder, int)}. 59ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 60ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This class is designed to work with any LayoutManager but for certain situations, it can be 61ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * optimized for your custom LayoutManager by extending methods in the 62ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler} 63ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * interface in your LayoutManager. 64ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 65ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On 66ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility 67ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * property to move items in response to touch events. You can customize these behaviors by 68ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, 69ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * boolean)} 70ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, 71ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * boolean)}. 72ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p/> 73ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Most of the time, you only need to override <code>onChildDraw</code> but due to limitations of 74ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * platform prior to Honeycomb, you may need to implement <code>onChildDrawOver</code> as well. 75ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 76ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurentpublic class ItemTouchHelper extends RecyclerView.ItemDecoration 77ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent implements RecyclerView.OnChildAttachStateChangeListener { 78ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 79ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 80ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Up direction, used for swipe & drag control. 81ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 82ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int UP = 1; 83ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 84ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 85ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Down direction, used for swipe & drag control. 86ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 87ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int DOWN = 1 << 1; 88ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 89ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 90ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Left direction, used for swipe & drag control. 91ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 92ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int LEFT = 1 << 2; 93ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 94ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 95ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Right direction, used for swipe & drag control. 96ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 97ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int RIGHT = 1 << 3; 98bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent 99ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // If you change these relative direction values, update Callback#convertToAbsoluteDirection, 100ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // Callback#convertToRelativeDirection. 101ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 102ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout 103ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * direction. Used for swipe & drag control. 104ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 105ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int START = LEFT << 2; 106ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 107ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 108ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout 109ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * direction. Used for swipe & drag control. 110ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 111ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int END = RIGHT << 2; 112ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 113ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 114ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ItemTouchHelper is in idle state. At this state, either there is no related motion event by 115ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * the user or latest motion events have not yet triggered a swipe or drag. 116ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 117ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ACTION_STATE_IDLE = 0; 118ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1196e2ebe97f2ad0a21907f20f9ee644c4eacbb7a40Glenn Kasten /** 120ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * A View is currently being swiped. 1216e2ebe97f2ad0a21907f20f9ee644c4eacbb7a40Glenn Kasten */ 122ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ACTION_STATE_SWIPE = 1; 123ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 124ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 125ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * A View is currently being dragged. 126ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 127ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ACTION_STATE_DRAG = 2; 128ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 129ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 130ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Animation type for views which are swiped successfully. 131ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 132ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1; 133ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 134ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 135ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Animation type for views which are not completely swiped thus will animate back to their 136ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * original position. 137ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 138ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2; 139ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 140ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 141ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Animation type for views that were dragged and now will animate to their final position. 142ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 143ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int ANIMATION_TYPE_DRAG = 1 << 3; 144ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 145ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final String TAG = "ItemTouchHelper"; 146ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 147ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final boolean DEBUG = false; 148ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 149ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final int ACTIVE_POINTER_ID_NONE = -1; 150ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 151ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final int DIRECTION_FLAG_COUNT = 8; 152ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 153ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1; 154ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 155ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT; 156ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 157ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT; 158ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 159ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 160ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The unit we are using to track velocity 161ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 162ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final int PIXELS_PER_SECOND = 1000; 163ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 164ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 165ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Views, whose state should be cleared after they are detached from RecyclerView. 166ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This is necessary after swipe dismissing an item. We wait until animator finishes its job 167ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * to clean these views. 168ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 169ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final List<View> mPendingCleanup = new ArrayList<View>(); 170ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 171ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 172ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Re-use array to calculate dx dy for a ViewHolder 173ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 174ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private final float[] mTmpPosition = new float[2]; 175ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 176ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 177ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Currently selected view holder 178ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 179ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder mSelected = null; 180ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 181ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 182ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The reference coordinates for the action start. For drag & drop, this is the time long 183ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * press is completed vs for swipe, this is the initial touch point. 184ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 185ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mInitialTouchX; 186ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 187ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mInitialTouchY; 188ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 189ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 190ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Set when ItemTouchHelper is assigned to a RecyclerView. 191ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 192ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mSwipeEscapeVelocity; 193ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 194ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 195ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Set when ItemTouchHelper is assigned to a RecyclerView. 196ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 197ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mMaxSwipeVelocity; 198ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 199ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 200ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The diff between the last event and initial touch. 201ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 202ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mDx; 203ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 204ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mDy; 205ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 206ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 207ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The coordinates of the selected view at the time it is selected. We record these values 208ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * when action starts so that we can consistently position it even if LayoutManager moves the 209ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * View. 210ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 211ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mSelectedStartX; 212ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 213ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float mSelectedStartY; 214ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 215ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 216ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The pointer we are tracking. 217ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 218ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int mActivePointerId = ACTIVE_POINTER_ID_NONE; 219ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 220ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 221d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent * Developer callback which controls the behavior of ItemTouchHelper. 222d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent */ 223d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent Callback mCallback; 224d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent 225d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent /** 226ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Current mode. 227ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 228d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent int mActionState = ACTION_STATE_IDLE; 229d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent 230d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent /** 231d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent * The direction flags obtained from unmasking 232d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current 233ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * action state. 234ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 235ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int mSelectedFlags; 236ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 237ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 238ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * When a View is dragged or swiped and needs to go back to where it was, we create a Recover 239ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Animation and animate it to its location using this custom Animator, instead of using 240ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * framework Animators. 241ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Using framework animators has the side effect of clashing with ItemAnimator, creating 242ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * jumpy UIs. 243ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 244ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent List<RecoverAnimation> mRecoverAnimations = new ArrayList<RecoverAnimation>(); 245ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 246ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int mSlop; 247ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 248ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent RecyclerView mRecyclerView; 249ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 250ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 251ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * When user drags a view to the edge, we start scrolling the LayoutManager as long as View 252ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * is partially out of bounds. 253ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 254ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final Runnable mScrollRunnable = new Runnable() { 255ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 256ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void run() { 257ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null && scrollIfNecessary()) { 258ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null) { //it might be lost during scrolling 259ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent moveIfNecessary(mSelected); 260ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 261ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.removeCallbacks(mScrollRunnable); 262ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewCompat.postOnAnimation(mRecyclerView, this); 263ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 264ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 265ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 266ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 267ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 268ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Used for detecting fling swipe 269ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 270ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent VelocityTracker mVelocityTracker; 271ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 272ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent //re-used list for selecting a swap target 273ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private List<ViewHolder> mSwapTargets; 274ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 275ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent //re used for for sorting swap targets 276ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private List<Integer> mDistances; 277ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 278ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 279ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If drag & drop is supported, we use child drawing order to bring them to front. 280ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 281ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null; 282ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 283ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 284ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This keeps a reference to the child dragged by the user. Even after user stops dragging, 285ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * until view reaches its final position (end of recover animation), we keep a reference so 286ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * that it can be drawn above other children. 287ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 288ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent View mOverdrawChild = null; 289ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 290ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 291ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * We cache the position of the overdraw child to avoid recalculating it each time child 292ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * position callback is called. This value is invalidated whenever a child is attached or 293ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * detached. 294ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 295ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int mOverdrawChildPosition = -1; 296ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 297ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 298ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Used to detect long press. 299d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent */ 300ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent GestureDetectorCompat mGestureDetector; 301ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 302ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private final OnItemTouchListener mOnItemTouchListener 303ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent = new OnItemTouchListener() { 304ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 305ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) { 306ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mGestureDetector.onTouchEvent(event); 307d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent if (DEBUG) { 308d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event); 309d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 310d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent final int action = MotionEventCompat.getActionMasked(event); 311d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent if (action == MotionEvent.ACTION_DOWN) { 312ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActivePointerId = event.getPointerId(0); 313d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mInitialTouchX = event.getX(); 314d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mInitialTouchY = event.getY(); 315ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent obtainVelocityTracker(); 316ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected == null) { 317d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent final RecoverAnimation animation = findAnimation(event); 318ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (animation != null) { 319d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mInitialTouchX -= animation.mX; 320d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mInitialTouchY -= animation.mY; 321ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent endRecoverAnimation(animation.mViewHolder, true); 322ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mPendingCleanup.remove(animation.mViewHolder.itemView)) { 323ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.clearView(mRecyclerView, animation.mViewHolder); 324d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 325ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(animation.mViewHolder, animation.mActionState); 326ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent updateDxDy(event, mSelectedFlags, 0); 327ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 328ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 329ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 330ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActivePointerId = ACTIVE_POINTER_ID_NONE; 331ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(null, ACTION_STATE_IDLE); 332ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { 333ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // in a non scroll orientation, if distance change is above threshold, we 334ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // can select the item 335ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int index = event.findPointerIndex(mActivePointerId); 336ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (DEBUG) { 337ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.d(TAG, "pointer index " + index); 338ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 339ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (index >= 0) { 340ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent checkSelectForSwipe(action, event, index); 341ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 342ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 343ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mVelocityTracker != null) { 344ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.addMovement(event); 345ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 346ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return mSelected != null; 347ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 348ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 349ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 350ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { 351ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mGestureDetector.onTouchEvent(event); 352ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (DEBUG) { 353ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.d(TAG, 354ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event); 355ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 356ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mVelocityTracker != null) { 357ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.addMovement(event); 358ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 359ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { 360ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 361ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 362ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int action = MotionEventCompat.getActionMasked(event); 363ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int activePointerIndex = event.findPointerIndex(mActivePointerId); 364ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (activePointerIndex >= 0) { 365ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent checkSelectForSwipe(action, event, activePointerIndex); 366d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 367d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent ViewHolder viewHolder = mSelected; 368ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (viewHolder == null) { 369ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 370ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 371ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent switch (action) { 372ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case MotionEvent.ACTION_MOVE: { 373ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // Find the index of the active pointer and fetch its position 374ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (activePointerIndex >= 0) { 375ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent updateDxDy(event, mSelectedFlags, activePointerIndex); 376ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent moveIfNecessary(viewHolder); 377ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.removeCallbacks(mScrollRunnable); 378ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mScrollRunnable.run(); 379ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.invalidate(); 380ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 381ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent break; 382ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 383ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case MotionEvent.ACTION_CANCEL: 384ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mVelocityTracker != null) { 385ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.clear(); 386ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 387ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // fall through 388ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case MotionEvent.ACTION_UP: 389ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(null, ACTION_STATE_IDLE); 390ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActivePointerId = ACTIVE_POINTER_ID_NONE; 391ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent break; 392ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case MotionEvent.ACTION_POINTER_UP: { 393ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int pointerIndex = MotionEventCompat.getActionIndex(event); 394ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int pointerId = event.getPointerId(pointerIndex); 395ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (pointerId == mActivePointerId) { 396ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // This was our active pointer going up. Choose a new 397ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // active pointer and adjust accordingly. 398ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 399ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActivePointerId = event.getPointerId(newPointerIndex); 400ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent updateDxDy(event, mSelectedFlags, pointerIndex); 401ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 402ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent break; 403ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 404ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 405d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 406d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent 407ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 408ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 409ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (!disallowIntercept) { 410ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 411ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 412ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(null, ACTION_STATE_IDLE); 413ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 414ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 415ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 416ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 417ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Temporary rect instance that is used when we need to lookup Item decorations. 418ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 419ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private Rect mTmpRect; 420ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 421ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 422ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * When user started to drag scroll. Reset when we don't scroll 423ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 424ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private long mDragScrollStartTimeInMs; 425ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 426ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 427ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Creates an ItemTouchHelper that will work with the given Callback. 428ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 429ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * You can attach ItemTouchHelper to a RecyclerView via 430ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration, 431ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * an onItemTouchListener and a Child attach / detach listener to the RecyclerView. 432ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 433ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param callback The Callback which controls the behavior of this touch helper. 434ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 435ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public ItemTouchHelper(Callback callback) { 436ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback = callback; 437ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 438ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 439ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static boolean hitTest(View child, float x, float y, float left, float top) { 440ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return x >= left && 441d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent x <= left + child.getWidth() && 442d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent y >= top && 443d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent y <= top + child.getHeight(); 444ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 445ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 446ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 447ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already 448ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * attached to a RecyclerView, it will first detach from the previous one. You can call this 449ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * method with {@code null} to detach it from the current RecyclerView. 450ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 451ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param recyclerView The RecyclerView instance to which you want to add this helper or 452ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@code null} if you want to remove ItemTouchHelper from the current 453ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView. 454ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 455ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { 456ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecyclerView == recyclerView) { 457ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; // nothing to do 458ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 459ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecyclerView != null) { 460ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent destroyCallbacks(); 461ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 462ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView = recyclerView; 463ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecyclerView != null) { 464ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final Resources resources = recyclerView.getResources(); 465ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSwipeEscapeVelocity = resources 466ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity); 467ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mMaxSwipeVelocity = resources 468ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity); 469ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent setupCallbacks(); 470ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 471ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 472ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 473ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private void setupCallbacks() { 474ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext()); 475ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSlop = vc.getScaledTouchSlop(); 476ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.addItemDecoration(this); 477ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); 478ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.addOnChildAttachStateChangeListener(this); 479ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent initGestureDetector(); 480d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 481d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent 482d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent private void destroyCallbacks() { 483bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent mRecyclerView.removeItemDecoration(this); 484ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener); 485ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.removeOnChildAttachStateChangeListener(this); 486ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // clean all attached 487ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int recoverAnimSize = mRecoverAnimations.size(); 488ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent for (int i = recoverAnimSize - 1; i >= 0; i--) { 489ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0); 490ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder); 491bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent } 492ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecoverAnimations.clear(); 493ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mOverdrawChild = null; 494bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent mOverdrawChildPosition = -1; 495bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent releaseVelocityTracker(); 496bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent } 497bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent 498bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent private void initGestureDetector() { 499bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent if (mGestureDetector != null) { 500bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent return; 501bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent } 502bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(), 503bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent new ItemTouchHelperGestureListener()); 504ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 505ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 506ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private void getSelectedDxDy(float[] outPosition) { 507ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((mSelectedFlags & (LEFT | RIGHT)) != 0) { 508ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft(); 509ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 510ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent outPosition[0] = ViewCompat.getTranslationX(mSelected.itemView); 511ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 512bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent if ((mSelectedFlags & (UP | DOWN)) != 0) { 513ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop(); 514ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 515ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent outPosition[1] = ViewCompat.getTranslationY(mSelected.itemView); 516ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 517ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 518ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 519ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 520ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 521ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float dx = 0, dy = 0; 522ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null) { 523ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent getSelectedDxDy(mTmpPosition); 524ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent dx = mTmpPosition[0]; 525ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent dy = mTmpPosition[1]; 526ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 527d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mCallback.onDrawOver(c, parent, mSelected, 528d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mRecoverAnimations, mActionState, dx, dy); 529d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 530ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 531ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 532ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 533ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // we don't know if RV changed something so we should invalidate this index. 534ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mOverdrawChildPosition = -1; 535ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent float dx = 0, dy = 0; 536ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null) { 537ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent getSelectedDxDy(mTmpPosition); 538ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent dx = mTmpPosition[0]; 539ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent dy = mTmpPosition[1]; 540ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 541ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.onDraw(c, parent, mSelected, 542ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecoverAnimations, mActionState, dx, dy); 543ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 544ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 545ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 546ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Starts dragging or swiping the given View. Call with null if you want to clear it. 547ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 548ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the 549ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * current action 550ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param actionState The type of action 551ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 552ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent void select(ViewHolder selected, int actionState) { 553ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (selected == mSelected && actionState == mActionState) { 554ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 555ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 556ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDragScrollStartTimeInMs = Long.MIN_VALUE; 557ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int prevActionState = mActionState; 558ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // prevent duplicate animations 559ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent endRecoverAnimation(selected, true); 560ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActionState = actionState; 561ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (actionState == ACTION_STATE_DRAG) { 562ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // we remove after animation is complete. this means we only elevate the last drag 563ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // child but that should perform good enough as it is very hard to start dragging a 564ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // new child before the previous one settles. 565ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mOverdrawChild = selected.itemView; 566ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent addChildDrawingOrderCallback(); 567ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 568ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)) 569ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent - 1; 570ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean preventLayout = false; 571ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 572ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null) { 573ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final ViewHolder prevSelected = mSelected; 574ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (prevSelected.itemView.getParent() != null) { 575ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0 576ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent : swipeIfNecessary(prevSelected); 577ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent releaseVelocityTracker(); 578ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // find where we should animate to 579ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float targetTranslateX, targetTranslateY; 580ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int animationType; 581ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent switch (swipeDir) { 582ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case LEFT: 583ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case RIGHT: 584ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case START: 585ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case END: 586ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateY = 0; 587ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth(); 588ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent break; 589ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case UP: 590ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent case DOWN: 591ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateX = 0; 592ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight(); 593ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent break; 594ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent default: 595ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateX = 0; 596ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateY = 0; 597ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 598ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (prevActionState == ACTION_STATE_DRAG) { 599ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent animationType = ANIMATION_TYPE_DRAG; 600ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (swipeDir > 0) { 601ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent animationType = ANIMATION_TYPE_SWIPE_SUCCESS; 602ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 603ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent animationType = ANIMATION_TYPE_SWIPE_CANCEL; 604ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 605ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent getSelectedDxDy(mTmpPosition); 606ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float currentTranslateX = mTmpPosition[0]; 607ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float currentTranslateY = mTmpPosition[1]; 608ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType, 609ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent prevActionState, currentTranslateX, currentTranslateY, 610ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateX, targetTranslateY) { 611ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 612ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onAnimationEnd(ValueAnimatorCompat animation) { 613ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent super.onAnimationEnd(animation); 614ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (this.mOverridden) { 615ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 616ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 617ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (swipeDir <= 0) { 618ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // this is a drag or failed swipe. recover immediately 619d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mCallback.clearView(mRecyclerView, prevSelected); 620d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent // full cleanup will happen on onDrawOver 621d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } else { 622d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent // wait until remove animation is complete. 623ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mPendingCleanup.add(prevSelected.itemView); 624ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mIsPendingCleanup = true; 625ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (swipeDir > 0) { 626ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // Animation might be ended by other animators during a layout. 627ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // We defer callback to avoid editing adapter during a layout. 628ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent postDispatchSwipe(this, swipeDir); 629ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 630ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 631ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // removed from the list after it is drawn for the last time 632ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mOverdrawChild == prevSelected.itemView) { 633ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); 634ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 635ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 636ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 637ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType, 638ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY); 639ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent rv.setDuration(duration); 640d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent mRecoverAnimations.add(rv); 641d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent rv.start(); 642d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent preventLayout = true; 643ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 644ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); 645ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.clearView(mRecyclerView, prevSelected); 646ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 647ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelected = null; 648ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 649ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (selected != null) { 650ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelectedFlags = 651ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask) 652ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent >> (mActionState * DIRECTION_FLAG_COUNT); 653ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelectedStartX = selected.itemView.getLeft(); 654ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelectedStartY = selected.itemView.getTop(); 655ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelected = selected; 656ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 657ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (actionState == ACTION_STATE_DRAG) { 658ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 659ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 660ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 661ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final ViewParent rvParent = mRecyclerView.getParent(); 662ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (rvParent != null) { 663ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent rvParent.requestDisallowInterceptTouchEvent(mSelected != null); 664ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 665ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (!preventLayout) { 666ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); 667ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 668ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.onSelectedChanged(mSelected, mActionState); 669ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.invalidate(); 670ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 671ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 672ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) { 673ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // wait until animations are complete. 674ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.post(new Runnable() { 675ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 676ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void run() { 677ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() && 678ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent !anim.mOverridden && 679d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) { 680d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator(); 681d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent // if animator is running or we have other active recover animations, we try 682ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // not to call onSwiped because DefaultItemAnimator is not good at merging 6837e1139c0377b6806942fb2a043737b3b9cf0ae91Eric Laurent // animations. Instead, we wait and batch. 684ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((animator == null || !animator.isRunning(null)) 685ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent && !hasRunningRecoverAnim()) { 686ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.onSwiped(anim.mViewHolder, swipeDir); 687ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 688ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.post(this); 689ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 690ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 691ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 692ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }); 693ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 694ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 695ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean hasRunningRecoverAnim() { 696ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int size = mRecoverAnimations.size(); 697ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent for (int i = 0; i < size; i++) { 698ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (!mRecoverAnimations.get(i).mEnded) { 699ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 700ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 701d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 702d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent return false; 703d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent } 704ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 705ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 706ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If user drags the view to the edge, trigger a scroll if necessary. 707ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 708ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean scrollIfNecessary() { 709ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected == null) { 710ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDragScrollStartTimeInMs = Long.MIN_VALUE; 711ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 712ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 713ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final long now = System.currentTimeMillis(); 714ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final long scrollDuration = mDragScrollStartTimeInMs 715ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs; 716ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); 717ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mTmpRect == null) { 718ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mTmpRect = new Rect(); 719ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 720ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int scrollX = 0; 721ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int scrollY = 0; 722ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect); 723ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (lm.canScrollHorizontally()) { 724d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent int curX = (int) (mSelectedStartX + mDx); 725d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft(); 726d0ebb538599cc25726b856e0f5deeb8215db8c92Eric Laurent if (mDx < 0 && leftDiff < 0) { 727ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollX = leftDiff; 728ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (mDx > 0) { 729ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int rightDiff = 730ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent curX + mSelected.itemView.getWidth() + mTmpRect.right 731ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight()); 732ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (rightDiff > 0) { 733ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollX = rightDiff; 734ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 735ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 736ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 737ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (lm.canScrollVertically()) { 738ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int curY = (int) (mSelectedStartY + mDy); 739ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop(); 740ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mDy < 0 && topDiff < 0) { 741ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollY = topDiff; 742ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (mDy > 0) { 743ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom - 744ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom()); 745ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (bottomDiff > 0) { 746ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollY = bottomDiff; 747ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 748ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 749ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 750ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (scrollX != 0) { 751ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView, 752ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelected.itemView.getWidth(), scrollX, 753ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.getWidth(), scrollDuration); 754ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 755ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (scrollY != 0) { 756ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView, 757ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mSelected.itemView.getHeight(), scrollY, 758ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.getHeight(), scrollDuration); 759ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 760ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (scrollX != 0 || scrollY != 0) { 761ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mDragScrollStartTimeInMs == Long.MIN_VALUE) { 762ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDragScrollStartTimeInMs = now; 763ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 764ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.scrollBy(scrollX, scrollY); 765ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 766ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 767ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDragScrollStartTimeInMs = Long.MIN_VALUE; 7685baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent return false; 7695baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent } 7705baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent 7715baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent private List<ViewHolder> findSwapTargets(ViewHolder viewHolder) { 7725baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent if (mSwapTargets == null) { 7735baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent mSwapTargets = new ArrayList<ViewHolder>(); 7745baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent mDistances = new ArrayList<Integer>(); 7755baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent } else { 7765baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent mSwapTargets.clear(); 7775baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent mDistances.clear(); 7785baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent } 7795baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int margin = mCallback.getBoundingBoxMargin(); 7805baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int left = Math.round(mSelectedStartX + mDx) - margin; 7815baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int top = Math.round(mSelectedStartY + mDy) - margin; 7825baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int right = left + viewHolder.itemView.getWidth() + 2 * margin; 7835baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin; 7845baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int centerX = (left + right) / 2; 7855baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int centerY = (top + bottom) / 2; 7865baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); 7875baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int childCount = lm.getChildCount(); 7885baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent for (int i = 0; i < childCount; i++) { 7895baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent View other = lm.getChildAt(i); 7905baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent if (other == viewHolder.itemView) { 7915baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent continue;//myself! 7925baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent } 7935baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent if (other.getBottom() < top || other.getTop() > bottom 7945baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent || other.getRight() < left || other.getLeft() > right) { 7955baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent continue; 7965baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent } 7975baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other); 7985baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) { 7995baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent // find the index to add 8005baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2); 8015baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2); 8025baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int dist = dx * dx + dy * dy; 8035baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent 8045baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent int pos = 0; 8055baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent final int cnt = mSwapTargets.size(); 8065baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent for (int j = 0; j < cnt; j++) { 8075baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent if (dist > mDistances.get(j)) { 808b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen pos++; 809b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } else { 810b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen break; 811b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 812b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 813b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mSwapTargets.add(pos, otherVh); 814b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mDistances.add(pos, dist); 815b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 816b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 817b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return mSwapTargets; 818b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 819b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 820b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen /** 821b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * Checks if we should swap w/ another view holder. 822b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen */ 823b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen void moveIfNecessary(ViewHolder viewHolder) { 824b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mRecyclerView.isLayoutRequested()) { 825b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 826b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 827b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mActionState != ACTION_STATE_DRAG) { 828b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 829b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 830b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 831b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final float threshold = mCallback.getMoveThreshold(viewHolder); 832b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final int x = (int) (mSelectedStartX + mDx); 833b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final int y = (int) (mSelectedStartY + mDy); 834b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold 835b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen && Math.abs(x - viewHolder.itemView.getLeft()) 836b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen < viewHolder.itemView.getWidth() * threshold) { 837b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 838b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 839b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen List<ViewHolder> swapTargets = findSwapTargets(viewHolder); 840b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (swapTargets.size() == 0) { 841b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 842b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 843b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen // may swap. 844b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y); 845b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (target == null) { 846b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mSwapTargets.clear(); 847b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mDistances.clear(); 848b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 849b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 850b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final int toPosition = target.getAdapterPosition(); 851b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final int fromPosition = viewHolder.getAdapterPosition(); 852b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mCallback.onMove(mRecyclerView, viewHolder, target)) { 853b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen // keep target visible 854b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mCallback.onMoved(mRecyclerView, viewHolder, fromPosition, 855b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen target, toPosition, x, y); 856b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 857b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 858b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 859b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen @Override 860b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen public void onChildViewAttachedToWindow(View view) { 861b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 862b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 863b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen @Override 864b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen public void onChildViewDetachedFromWindow(View view) { 865b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen removeChildDrawingOrderCallbackIfNecessary(view); 866b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final ViewHolder holder = mRecyclerView.getChildViewHolder(view); 867b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (holder == null) { 868b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return; 869b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 870b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mSelected != null && holder == mSelected) { 871b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen select(null, ACTION_STATE_IDLE); 872b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } else { 873b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen endRecoverAnimation(holder, false); // this may push it into pending cleanup list. 874b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mPendingCleanup.remove(holder.itemView)) { 875b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mCallback.clearView(mRecyclerView, holder); 876b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 877b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 878b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 879b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 880b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen /** 881b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * Returns the animation type or 0 if cannot be found. 882b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen */ 883b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen int endRecoverAnimation(ViewHolder viewHolder, boolean override) { 884b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final int recoverAnimSize = mRecoverAnimations.size(); 885b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen for (int i = recoverAnimSize - 1; i >= 0; i--) { 886b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen final RecoverAnimation anim = mRecoverAnimations.get(i); 887b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (anim.mViewHolder == viewHolder) { 888b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen anim.mOverridden |= override; 889b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (!anim.mEnded) { 890b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen anim.cancel(); 891b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 892b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mRecoverAnimations.remove(i); 893b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return anim.mAnimationType; 894b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 895b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 896b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return 0; 897b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 898b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 899b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen @Override 900b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 901b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen RecyclerView.State state) { 902b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen outRect.setEmpty(); 903b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 904b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 905b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen void obtainVelocityTracker() { 906b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mVelocityTracker != null) { 907b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mVelocityTracker.recycle(); 908b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 909b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen mVelocityTracker = VelocityTracker.obtain(); 910b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen } 911b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 912b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen private void releaseVelocityTracker() { 9130f11b51a57bc9062c4fe8af73747319cedabc5d6Glenn Kasten if (mVelocityTracker != null) { 914ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.recycle(); 915ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker = null; 916ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 917ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 918ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 919ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private ViewHolder findSwipedView(MotionEvent motionEvent) { 920ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); 921ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { 922ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 923ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 924ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId); 925ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX; 926ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY; 927ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absDx = Math.abs(dx); 928ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absDy = Math.abs(dy); 929377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT 930377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT if (absDx < mSlop && absDy < mSlop) { 931ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 932ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 933ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (absDx > absDy && lm.canScrollHorizontally()) { 934ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 935ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (absDy > absDx && lm.canScrollVertically()) { 936ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 937ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 938ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent View child = findChildView(motionEvent); 939ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (child == null) { 940ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 941ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 942ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return mRecyclerView.getChildViewHolder(child); 943ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 944ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 945ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 946ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Checks whether we should select a View for swiping. 947b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen */ 948ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { 949b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (mSelected != null || action != MotionEvent.ACTION_MOVE 950b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) { 951ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 952ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 953ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) { 954ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 955ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 956ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final ViewHolder vh = findSwipedView(motionEvent); 957ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (vh == null) { 958ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 959ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 960377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh); 9611d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath 962ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK) 963ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE); 964ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 965b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen if (swipeFlags == 0) { 9661d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath return false; 967377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT } 968ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 969ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // mDx and mDy are only set in allowed directions. We use custom x/y here instead of 970ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // updateDxDy to avoid swiping if user moves more in the other direction 971ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float x = motionEvent.getX(pointerIndex); 9721d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath final float y = motionEvent.getY(pointerIndex); 973377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT 974ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // Calculate the distance moved 975ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float dx = x - mInitialTouchX; 976ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float dy = y - mInitialTouchY; 977b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen // swipe target is chose w/o applying flags so it does not really check if swiping in that 978b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen // direction is allowed. This why here, we use mDx mDy to check slope value again. 979ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absDx = Math.abs(dx); 980ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absDy = Math.abs(dy); 981377b2ec9a2885f9b6405b07ba900a9e3f4349c38Kévin PETIT 982ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (absDx < mSlop && absDy < mSlop) { 983b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return false; 984ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 985ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (absDx > absDy) { 986ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dx < 0 && (swipeFlags & LEFT) == 0) { 98701d3acba9de861cb2b718338e787cff3566fc5ecGlenn Kasten return false; 988ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 989ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dx > 0 && (swipeFlags & RIGHT) == 0) { 990ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 991ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 992ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 993ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dy < 0 && (swipeFlags & UP) == 0) { 994ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 995ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 996ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dy > 0 && (swipeFlags & DOWN) == 0) { 997ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return false; 998ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 999ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1000ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = mDy = 0f; 1001ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mActivePointerId = motionEvent.getPointerId(0); 1002ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(vh, ACTION_STATE_SWIPE); 1003ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 1004ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1005ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1006ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent View findChildView(MotionEvent event) { 1007ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // first check elevated views, if none, then call RV 1008ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float x = event.getX(); 1009ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float y = event.getY(); 1010ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mSelected != null) { 1011ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final View selectedView = mSelected.itemView; 1012ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) { 1013ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return selectedView; 1014ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1015ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1016ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { 1017ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final RecoverAnimation anim = mRecoverAnimations.get(i); 1018ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final View view = anim.mViewHolder.itemView; 1019ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (hitTest(view, x, y, anim.mX, anim.mY)) { 1020ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return view; 1021e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten } 1022e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten } 1023ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return mRecyclerView.findChildViewUnder(x, y); 1024ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1025e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten 1026ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1027ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a 1028e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * View is long pressed. You can disable that behavior by overriding 1029e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}. 1030ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1031ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For this method to work: 1032ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <ul> 1033ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <li>The provided ViewHolder must be a child of the RecyclerView to which this 1034ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ItemTouchHelper 1035ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * is attached.</li> 1036ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <li>{@link ItemTouchHelper.Callback} must have dragging enabled.</li> 1037ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <li>There must be a previous touch event that was reported to the ItemTouchHelper 1038ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener 1039ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * grabs previous events, this should work as expected.</li> 1040ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * </ul> 1041ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1042ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For example, if you would like to let your user to be able to drag an Item by touching one 1043ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * of its descendants, you may implement it as follows: 1044ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <pre> 1045ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() { 1046e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * public boolean onTouch(View v, MotionEvent event) { 1047e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { 1048e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * mItemTouchHelper.startDrag(viewHolder); 1049e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * } 1050e75da4004b2c814987aa2adf8a76190f92d99c65Glenn Kasten * return false; 1051ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1052ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * }); 1053ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * </pre> 1054ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1055ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1056ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder to start dragging. It must be a direct child of 1057ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView. 1058ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled() 1059ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1060ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void startDrag(ViewHolder viewHolder) { 1061ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) { 1062ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.e(TAG, "Start drag has been called but swiping is not enabled"); 1063ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 1064ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1065ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (viewHolder.itemView.getParent() != mRecyclerView) { 1066ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.e(TAG, "Start drag has been called with a view holder which is not a child of " 1067ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent + "the RecyclerView which is controlled by this ItemTouchHelper."); 1068ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 1069ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1070ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent obtainVelocityTracker(); 1071ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = mDy = 0f; 1072ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(viewHolder, ACTION_STATE_DRAG); 1073ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1074ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1075ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1076ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View 1077ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * when user swipes their finger (or mouse pointer) over the View. You can disable this 1078ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * behavior 1079ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * by overriding {@link ItemTouchHelper.Callback} 1080ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1081ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For this method to work: 1082ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <ul> 1083813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * <li>The provided ViewHolder must be a child of the RecyclerView to which this 108459fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * ItemTouchHelper is attached.</li> 108559fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * <li>{@link ItemTouchHelper.Callback} must have swiping enabled.</li> 1086813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * <li>There must be a previous touch event that was reported to the ItemTouchHelper 108759fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener 108859fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * grabs previous events, this should work as expected.</li> 1089813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * </ul> 109059fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * 109159fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * For example, if you would like to let your user to be able to swipe an Item by touching one 109259fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * of its descendants, you may implement it as follows: 109359fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * <pre> 109459fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() { 109559fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * public boolean onTouch(View v, MotionEvent event) { 109659fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { 109759fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent * mItemTouchHelper.startSwipe(viewHolder); 1098813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * } 1099813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * return false; 1100ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1101ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * }); 1102ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * </pre> 1103ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1104ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder to start swiping. It must be a direct child of 1105ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView. 1106ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1107ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void startSwipe(ViewHolder viewHolder) { 1108ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) { 1109ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.e(TAG, "Start swipe has been called but dragging is not enabled"); 1110ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 1111ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1112ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (viewHolder.itemView.getParent() != mRecyclerView) { 1113ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent Log.e(TAG, "Start swipe has been called with a view holder which is not a child of " 1114ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent + "the RecyclerView controlled by this ItemTouchHelper."); 1115ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return; 1116ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1117ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent obtainVelocityTracker(); 1118ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = mDy = 0f; 1119ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent select(viewHolder, ACTION_STATE_SWIPE); 1120ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1121ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1122ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent RecoverAnimation findAnimation(MotionEvent event) { 1123ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mRecoverAnimations.isEmpty()) { 1124ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 1125ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1126ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent View target = findChildView(event); 1127ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { 112859fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent final RecoverAnimation anim = mRecoverAnimations.get(i); 112959fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent if (anim.mViewHolder.itemView == target) { 113059fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent return anim; 113159fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent } 113259fe010bcc072597852454a2ec53d7b0a2002a3bEric Laurent } 1133ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return null; 1134ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1135ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1136ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { 1137ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float x = ev.getX(pointerIndex); 1138ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float y = ev.getY(pointerIndex); 1139ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1140ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // Calculate the distance moved 1141ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = x - mInitialTouchX; 1142ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDy = y - mInitialTouchY; 1143ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((directionFlags & LEFT) == 0) { 1144ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = Math.max(0, mDx); 1145ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1146ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((directionFlags & RIGHT) == 0) { 1147ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDx = Math.min(0, mDx); 1148ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1149ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((directionFlags & UP) == 0) { 1150ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDy = Math.max(0, mDy); 1151ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1152ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((directionFlags & DOWN) == 0) { 1153ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mDy = Math.min(0, mDy); 1154ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1155ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1156ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1157ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int swipeIfNecessary(ViewHolder viewHolder) { 1158ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mActionState == ACTION_STATE_DRAG) { 1159ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1160ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1161ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder); 1162ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection( 1163ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent originalMovementFlags, 1164ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewCompat.getLayoutDirection(mRecyclerView)); 1165021cf9634ab09c0753a40b7c9ef4ba603be5c3daEric Laurent final int flags = (absoluteMovementFlags 1166021cf9634ab09c0753a40b7c9ef4ba603be5c3daEric Laurent & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT); 1167ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (flags == 0) { 1168ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1169ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1170ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int originalFlags = (originalMovementFlags 1171ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT); 1172ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int swipeDir; 1173ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (Math.abs(mDx) > Math.abs(mDy)) { 1174ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) { 1175ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // if swipe dir is not in original flags, it should be the relative direction 1176ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((originalFlags & swipeDir) == 0) { 1177ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // convert to relative 1178ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return Callback.convertToRelativeDirection(swipeDir, 1179ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewCompat.getLayoutDirection(mRecyclerView)); 1180ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1181ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return swipeDir; 1182ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1183ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) { 1184ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return swipeDir; 1185ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1186ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 1187ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) { 1188ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return swipeDir; 1189ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1190ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) { 1191ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // if swipe dir is not in original flags, it should be the relative direction 1192ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((originalFlags & swipeDir) == 0) { 1193ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // convert to relative 1194ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return Callback.convertToRelativeDirection(swipeDir, 1195ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewCompat.getLayoutDirection(mRecyclerView)); 1196ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1197ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return swipeDir; 1198ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1199ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1200ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1201ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1202ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1203ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) { 1204ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((flags & (LEFT | RIGHT)) != 0) { 1205ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int dirFlag = mDx > 0 ? RIGHT : LEFT; 1206ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mVelocityTracker != null && mActivePointerId > -1) { 1207ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, 1208ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); 1209ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float xVelocity = VelocityTrackerCompat 1210ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getXVelocity(mVelocityTracker, mActivePointerId); 1211ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float yVelocity = VelocityTrackerCompat 1212ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getYVelocity(mVelocityTracker, mActivePointerId); 1213ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT; 1214ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absXVelocity = Math.abs(xVelocity); 1215ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag && 1216ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) && 1217ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent absXVelocity > Math.abs(yVelocity)) { 1218ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return velDirFlag; 1219ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1220ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1221ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1222ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float threshold = mRecyclerView.getWidth() * mCallback 1223ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getSwipeThreshold(viewHolder); 1224ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1225ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) { 1226ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return dirFlag; 1227ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1228ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1229ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1230ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1231ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1232ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int checkVerticalSwipe(ViewHolder viewHolder, int flags) { 1233ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((flags & (UP | DOWN)) != 0) { 1234ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int dirFlag = mDy > 0 ? DOWN : UP; 1235ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mVelocityTracker != null && mActivePointerId > -1) { 1236ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, 1237ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); 1238ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float xVelocity = VelocityTrackerCompat 1239ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getXVelocity(mVelocityTracker, mActivePointerId); 1240ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float yVelocity = VelocityTrackerCompat 1241ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getYVelocity(mVelocityTracker, mActivePointerId); 1242ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int velDirFlag = yVelocity > 0f ? DOWN : UP; 1243ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float absYVelocity = Math.abs(yVelocity); 1244ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag && 1245ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) && 1246ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent absYVelocity > Math.abs(xVelocity)) { 1247ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return velDirFlag; 1248ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1249ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1250ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1251ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final float threshold = mRecyclerView.getHeight() * mCallback 1252ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent .getSwipeThreshold(viewHolder); 1253ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) { 1254ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return dirFlag; 1255ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1256ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1257ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1258ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1259ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1260ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private void addChildDrawingOrderCallback() { 1261ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (Build.VERSION.SDK_INT >= 21) { 1262ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return;// we use elevation on Lollipop 1263ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1264ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mChildDrawingOrderCallback == null) { 1265ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() { 1266ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 1267ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public int onGetChildDrawingOrder(int childCount, int i) { 1268ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mOverdrawChild == null) { 1269ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return i; 1270ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1271ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int childPosition = mOverdrawChildPosition; 1272ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (childPosition == -1) { 1273ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent childPosition = mRecyclerView.indexOfChild(mOverdrawChild); 1274ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mOverdrawChildPosition = childPosition; 1275ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1276ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (i == childCount - 1) { 1277ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return childPosition; 1278ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1279ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return i < childPosition ? i : i + 1; 1280ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1281ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 1282ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1283ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback); 1284ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1285ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1286ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent void removeChildDrawingOrderCallbackIfNecessary(View view) { 1287ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (view == mOverdrawChild) { 1288ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mOverdrawChild = null; 1289ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // only remove if we've added 1290ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mChildDrawingOrderCallback != null) { 129101d3acba9de861cb2b718338e787cff3566fc5ecGlenn Kasten mRecyclerView.setChildDrawingOrderCallback(null); 1292ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1293ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1294ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1295b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 1296ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1297ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * An interface which can be implemented by LayoutManager for better integration with 1298b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * {@link ItemTouchHelper}. 1299b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen */ 1300ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static interface ViewDropHandler { 1301ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1302ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1303ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called by the {@link ItemTouchHelper} after a View is dropped over another View. 1304ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1305ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * A LayoutManager should implement this interface to get ready for the upcoming move 1306ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * operation. 1307ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1308ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that 1309ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * the View under drag will be used as an anchor View while calculating the next layout, 1310ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * making layout stay consistent. 1311ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1312ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param view The View which is being dragged. It is very likely that user is still 1313ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragging this View so there might be other 1314ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #prepareForDrop(View, View, int, int)} after this one. 1315ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param target The target view which is being dropped on. 1316ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param x The <code>left</code> offset of the View that is being dragged. This value 1317ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * includes the movement caused by the user. 1318ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param y The <code>top</code> offset of the View that is being dragged. This value 1319ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * includes the movement caused by the user. 1320ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1321ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void prepareForDrop(View view, View target, int x, int y); 1322ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1323ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1324ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1325ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This class is the contract between ItemTouchHelper and your application. It lets you control 1326ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user 1327ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * performs these actions. 1328ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1329ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * To control which actions user can take on each view, you should override 1330ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set 1331ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END}, 1332ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #UP}, {@link #DOWN}). You can use 1333ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use 1334ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link SimpleCallback}. 1335ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1336ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If user drags an item, ItemTouchHelper will call 1337ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder) 1338ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * onMove(recyclerView, dragged, target)}. 1339ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Upon receiving this callback, you should move the item from the old position 1340ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()}) 1341ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}. 1342ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * To control where a View can be dropped, you can override 1343ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a 1344ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragging View overlaps multiple other views, Callback chooses the closest View with which 1345ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragged View might have changed positions. Although this approach works for many use cases, 1346ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * if you have a custom LayoutManager, you can override 1347ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a 1348ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * custom drop target. 1349ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1350ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls 1351ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your 1352ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * adapter (e.g. remove the item) and call related Adapter#notify event. 1353ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1354ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @SuppressWarnings("UnusedParameters") 1355ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public abstract static class Callback { 1356ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1357ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; 1358ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1359ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; 1360ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1361ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static final int RELATIVE_DIR_FLAGS = START | END | 1362ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ((START | END) << DIRECTION_FLAG_COUNT) | 1363ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ((START | END) << (2 * DIRECTION_FLAG_COUNT)); 1364ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1365ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final ItemTouchUIUtil sUICallback; 1366ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1367ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT | 1368ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) | 1369ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT)); 1370ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1371ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final Interpolator sDragScrollInterpolator = new Interpolator() { 1372ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 1373ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getInterpolation(float t) { 1374ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return t * t * t * t * t; 1375ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1376ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 1377ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1378ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() { 1379ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent @Override 1380ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getInterpolation(float t) { 1381ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent t -= 1.0f; 1382ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return t * t * t * t * t + 1.0f; 1383ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1384ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent }; 1385ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1386ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1387ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Drag scroll speed keeps accelerating until this many milliseconds before being capped. 1388ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1389ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000; 1390bfb1b832079bbb9426f72f3863199a54aefd02daEric Laurent 1391ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int mCachedMaxScrollSpeed = -1; 1392ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1393ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent static { 1394ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (Build.VERSION.SDK_INT >= 21) { 1395ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent sUICallback = new ItemTouchUIUtilImpl.Lollipop(); 1396ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else if (Build.VERSION.SDK_INT >= 11) { 1397ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent sUICallback = new ItemTouchUIUtilImpl.Honeycomb(); 1398ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 1399ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent sUICallback = new ItemTouchUIUtilImpl.Gingerbread(); 1400ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1401ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1402ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1403fed6292af65a0b97b583ecbd3c232b3811a3f37bJean-Michel Trivi /** 1404fed6292af65a0b97b583ecbd3c232b3811a3f37bJean-Michel Trivi * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for 1405fed6292af65a0b97b583ecbd3c232b3811a3f37bJean-Michel Trivi * visual 1406fed6292af65a0b97b583ecbd3c232b3811a3f37bJean-Michel Trivi * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different 1407ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * implementations for different platform versions. 1408ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1409ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * By default, {@link Callback} applies these changes on 1410ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link RecyclerView.ViewHolder#itemView}. 1411ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1412ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For example, if you have a use case where you only want the text to move when user 1413ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * swipes over the view, you can do the following: 1414ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <pre> 1415ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){ 1416ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView); 1417ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1418ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { 1419ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * if (viewHolder != null){ 1420ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView); 1421ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1422ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1423ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * public void onChildDraw(Canvas c, RecyclerView recyclerView, 1424ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, 1425ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * boolean isCurrentlyActive) { 1426ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * getDefaultUIUtil().onDraw(c, recyclerView, 1427ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ((ItemTouchViewHolder) viewHolder).textView, dX, dY, 1428ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * actionState, isCurrentlyActive); 1429ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * return true; 1430ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1431ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * public void onChildDrawOver(Canvas c, RecyclerView recyclerView, 1432ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, 1433ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * boolean isCurrentlyActive) { 1434ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * getDefaultUIUtil().onDrawOver(c, recyclerView, 1435ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ((ItemTouchViewHolder) viewHolder).textView, dX, dY, 1436ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * actionState, isCurrentlyActive); 1437ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * return true; 1438ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * } 1439ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * </pre> 1440ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1441ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback} 1442ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1443ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static ItemTouchUIUtil getDefaultUIUtil() { 1444ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return sUICallback; 1445ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1446ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1447ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1448ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Replaces a movement direction with its relative version by taking layout direction into 1449ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * account. 1450ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1451ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param flags The flag value that include any number of movement flags. 1452ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param layoutDirection The layout direction of the View. Can be obtained from 1453ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link ViewCompat#getLayoutDirection(android.view.View)}. 1454ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead 1455ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * of {@link #LEFT}, {@link #RIGHT}. 1456ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #convertToAbsoluteDirection(int, int) 1457ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1458ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static int convertToRelativeDirection(int flags, int layoutDirection) { 1459ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int masked = flags & ABS_HORIZONTAL_DIR_FLAGS; 1460ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (masked == 0) { 1461ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags;// does not have any abs flags, good. 1462ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1463ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags &= ~masked; //remove left / right. 1464ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 1465ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // no change. just OR with 2 bits shifted mask and return 1466ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT. 1467ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags; 1468ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 1469ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // add RIGHT flag as START 1470ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS); 1471ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // first clean RIGHT bit then add LEFT flag as END 1472ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2; 1473ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1474ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags; 1475ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1476ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1477ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1478ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Convenience method to create movement flags. 1479ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1480ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For instance, if you want to let your items be drag & dropped vertically and swiped 1481ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * left to be dismissed, you can call this method with: 1482ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <code>makeMovementFlags(UP | DOWN, LEFT);</code> 1483ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1484ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param dragFlags The directions in which the item can be dragged. 1485ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param swipeFlags The directions in which the item can be swiped. 1486ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return Returns an integer composed of the given drag and swipe flags. 1487ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1488ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static int makeMovementFlags(int dragFlags, int swipeFlags) { 1489ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) | 1490ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG, 1491ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent dragFlags); 1492ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1493ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1494ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1495ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Shifts the given direction flags to the offset of the given action state. 1496ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1497ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param actionState The action state you want to get flags in. Should be one of 1498ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or 1499ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #ACTION_STATE_DRAG}. 1500ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN}, 1501ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}. 1502ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return And integer that represents the given directions in the provided actionState. 1503ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1504ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public static int makeFlag(int actionState, int directions) { 1505ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return directions << (actionState * DIRECTION_FLAG_COUNT); 1506ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1507ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1508ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1509ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Should return a composite flag which defines the enabled move directions in each state 1510ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * (idle, swiping, dragging). 1511ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1512ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int, 1513ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * int)} 1514ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * or {@link #makeFlag(int, int)}. 1515ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1516ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next 1517ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 8 bits are for SWIPE state and third 8 bits are for DRAG state. 1518ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in 1519ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link ItemTouchHelper}. 1520ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1521ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to 1522ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * swipe by swiping RIGHT, you can return: 1523ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <pre> 1524ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT); 1525ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * </pre> 1526ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This means, allow right movement while IDLE and allow right and left movement while 1527ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * swiping. 1528ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1529ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param recyclerView The RecyclerView to which ItemTouchHelper is attached. 1530ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder for which the movement information is necessary. 1531ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return flags specifying which movements are allowed on this ViewHolder. 1532ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #makeMovementFlags(int, int) 1533ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #makeFlag(int, int) 1534ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1535ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public abstract int getMovementFlags(RecyclerView recyclerView, 1536ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder viewHolder); 1537ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1538ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1539ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Converts a given set of flags to absolution direction which means {@link #START} and 1540ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout 1541ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * direction. 1542ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1543ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param flags The flag value that include any number of movement flags. 1544ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param layoutDirection The layout direction of the RecyclerView. 1545ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return Updated flags which includes only absolute direction values. 1546ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1547ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public int convertToAbsoluteDirection(int flags, int layoutDirection) { 1548ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int masked = flags & RELATIVE_DIR_FLAGS; 1549ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (masked == 0) { 1550ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags;// does not have any relative flags, good. 1551ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1552ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags &= ~masked; //remove start / end 1553ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 1554ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // no change. just OR with 2 bits shifted mask and return 1555ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT. 1556ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags; 1557ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } else { 1558ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // add START flag as RIGHT 1559ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS); 1560ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent // first clean start bit then add END flag as LEFT 1561ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2; 1562ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1563ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return flags; 1564ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1565ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1566ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int getAbsoluteMovementFlags(RecyclerView recyclerView, 1567ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder viewHolder) { 1568ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int flags = getMovementFlags(recyclerView, viewHolder); 1569ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView)); 1570ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1571ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1572ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) { 1573ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); 1574ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return (flags & ACTION_MODE_DRAG_MASK) != 0; 1575ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1576ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1577ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent boolean hasSwipeFlag(RecyclerView recyclerView, 1578ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder viewHolder) { 1579ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); 1580ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return (flags & ACTION_MODE_SWIPE_MASK) != 0; 1581ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1582ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1583ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1584ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Return true if the current ViewHolder can be dropped over the the target ViewHolder. 1585ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1586ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This method is used when selecting drop target for the dragged View. After Views are 1587ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * eliminated either via bounds check or via this method, resulting set of views will be 1588ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}. 1589ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1590ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default implementation returns true. 1591ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1592ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to. 1593ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param current The ViewHolder that user is dragging. 1594ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param target The ViewHolder which is below the dragged ViewHolder. 1595ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false 1596ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * otherwise. 1597ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1598ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public boolean canDropOver(RecyclerView recyclerView, ViewHolder current, 1599ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder target) { 1600ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 1601ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1602ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1603ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1604ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called when ItemTouchHelper wants to move the dragged item from its old position to 1605ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * the new position. 1606ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1607ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved 1608ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * to the adapter position of {@code target} ViewHolder 1609ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ({@link ViewHolder#getAdapterPosition() 1610ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ViewHolder#getAdapterPosition()}). 1611ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1612ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If you don't support drag & drop, this method will never be called. 1613ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1614ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to. 1615ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder which is being dragged by the user. 1616ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param target The ViewHolder over which the currently active item is being 1617ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragged. 1618ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return True if the {@code viewHolder} has been moved to the adapter position of 1619ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@code target}. 1620ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int) 1621ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1622ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public abstract boolean onMove(RecyclerView recyclerView, 1623ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder viewHolder, ViewHolder target); 1624ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1625ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1626ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Returns whether ItemTouchHelper should start a drag and drop operation if an item is 1627ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * long pressed. 1628ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1629ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default value returns true but you may want to disable this if you want to start 1630ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragging on a custom view touch using {@link #startDrag(ViewHolder)}. 1631ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1632ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return True if ItemTouchHelper should start dragging an item when it is long pressed, 1633ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * false otherwise. Default value is <code>true</code>. 1634ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #startDrag(ViewHolder) 1635ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1636ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public boolean isLongPressDragEnabled() { 1637ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 1638ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1639ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1640ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1641ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped 1642ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * over the View. 1643ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1644ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default value returns true but you may want to disable this if you want to start 1645ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}. 1646ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1647ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer 1648ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * over the View, false otherwise. Default value is <code>true</code>. 1649ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #startSwipe(ViewHolder) 1650ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1651ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public boolean isItemViewSwipeEnabled() { 1652ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return true; 1653ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1654ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1655ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1656ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * When finding views under a dragged view, by default, ItemTouchHelper searches for views 1657ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * that overlap with the dragged View. By overriding this method, you can extend or shrink 1658ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * the search box. 1659ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1660ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return The extra margin to be added to the hit box of the dragged View. 1661ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1662ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public int getBoundingBoxMargin() { 1663ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return 0; 1664ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1665ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1666ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1667ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Returns the fraction that the user should move the View to be considered as swiped. 1668ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The fraction is calculated with respect to RecyclerView's bounds. 1669ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1670ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default value is .5f, which means, to swipe a View, user must move the View at least 1671ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * half of RecyclerView's width or height, depending on the swipe direction. 1672ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1673ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder that is being dragged. 1674ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return A float value that denotes the fraction of the View size. Default value 1675ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * is .5f . 1676ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1677ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getSwipeThreshold(ViewHolder viewHolder) { 1678ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return .5f; 1679ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1680b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen 1681b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen /** 1682ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Returns the fraction that the user should move the View to be considered as it is 1683ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views 1684b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * below it for a possible drop. 1685b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * 1686b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * @param viewHolder The ViewHolder that is being dragged. 1687b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * @return A float value that denotes the fraction of the View size. Default value is 1688b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * .5f . 1689b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen */ 1690ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getMoveThreshold(ViewHolder viewHolder) { 1691b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen return .5f; 16921d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath } 16931d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath 16941d6fa7af1288b550faabe4ec2cf98684236723dbNarayan Kamath /** 1695b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * Defines the minimum velocity which will be considered as a swipe action by the user. 1696b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * <p> 1697b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * You can increase this value to make it harder to swipe or decrease it to make it easier. 1698b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure 1699b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * current direction velocity is larger then the perpendicular one. Otherwise, user's 1700b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * movement is ambiguous. You can change the threshold by overriding 1701b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * {@link #getSwipeVelocityThreshold(float)}. 1702b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * <p> 1703b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * The velocity is calculated in pixels per second. 1704ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1705ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The default framework value is passed as a parameter so that you can modify it with a 1706b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * multiplier. 1707b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * 1708b220884bf3129253cc5bc8d030bc475411ea4911Marco Nelissen * @param defaultValue The default value (in pixels per second) used by the 1709ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ItemTouchHelper. 1710ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return The minimum swipe velocity. The default implementation returns the 1711ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <code>defaultValue</code> parameter. 1712ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #getSwipeVelocityThreshold(float) 1713ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #getSwipeThreshold(ViewHolder) 1714ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1715ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getSwipeEscapeVelocity(float defaultValue) { 1716ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return defaultValue; 1717ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1718ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1719ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1720ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements. 1721ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1722ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the 1723ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * perpendicular movement. If both directions reach to the max threshold, none of them will 1724ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * be considered as a swipe because it is usually an indication that user rather tried to 1725ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * scroll then swipe. 1726ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1727ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * The velocity is calculated in pixels per second. 1728ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1729ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * You can customize this behavior by changing this method. If you increase the value, it 1730ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * will be easier for the user to swipe diagonally and if you decrease the value, user will 1731ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * need to make a rather straight finger movement to trigger a swipe. 1732ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1733ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper. 1734ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return The velocity cap for pointer movements. The default implementation returns the 1735ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <code>defaultValue</code> parameter. 1736ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #getSwipeEscapeVelocity(float) 1737ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1738ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public float getSwipeVelocityThreshold(float defaultValue) { 1739ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return defaultValue; 1740ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1741ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1742ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1743ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that 1744ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * are under the dragged View. 1745ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1746ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default implementation filters the View with which dragged item have changed position 1747ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * in the drag direction. For instance, if the view is dragged UP, it compares the 1748ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <code>view.getTop()</code> of the two views before and after drag started. If that value 1749ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * is different, the target view passes the filter. 1750ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1751ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Among these Views which pass the test, the one closest to the dragged view is chosen. 1752ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1753ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This method is called on the main thread every time user moves the View. If you want to 1754ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * override it, make sure it does not do any expensive operations. 1755ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1756ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param selected The ViewHolder being dragged by the user. 1757ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param dropTargets The list of ViewHolder that are under the dragged View and 1758ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * candidate as a drop. 1759ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param curX The updated left value of the dragged View after drag translations 1760ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * are applied. This value does not include margins added by 1761ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link RecyclerView.ItemDecoration}s. 1762ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param curY The updated top value of the dragged View after drag translations 1763ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * are applied. This value does not include margins added by 1764ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link RecyclerView.ItemDecoration}s. 1765ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @return A ViewHolder to whose position the dragged ViewHolder should be 1766ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * moved to. 1767ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1768ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public ViewHolder chooseDropTarget(ViewHolder selected, 1769ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent List<ViewHolder> dropTargets, int curX, int curY) { 1770ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int right = curX + selected.itemView.getWidth(); 1771ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int bottom = curY + selected.itemView.getHeight(); 1772ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent ViewHolder winner = null; 1773ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int winnerScore = -1; 1774ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int dx = curX - selected.itemView.getLeft(); 1775ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int dy = curY - selected.itemView.getTop(); 1776ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int targetsSize = dropTargets.size(); 1777ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent for (int i = 0; i < targetsSize; i++) { 1778ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final ViewHolder target = dropTargets.get(i); 1779ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dx > 0) { 1780ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int diff = target.itemView.getRight() - right; 1781ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) { 1782ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int score = Math.abs(diff); 1783ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (score > winnerScore) { 1784ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winnerScore = score; 1785ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winner = target; 1786ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1787ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1788ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1789ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dx < 0) { 1790ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int diff = target.itemView.getLeft() - curX; 1791ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) { 1792ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int score = Math.abs(diff); 1793ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (score > winnerScore) { 1794ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winnerScore = score; 1795ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winner = target; 1796ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1797ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1798ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1799ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dy < 0) { 1800ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int diff = target.itemView.getTop() - curY; 1801ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) { 1802ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int score = Math.abs(diff); 1803ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (score > winnerScore) { 1804ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winnerScore = score; 1805ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winner = target; 1806ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1807ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1808ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1809ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1810ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (dy > 0) { 1811ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent int diff = target.itemView.getBottom() - bottom; 1812ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) { 1813ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent final int score = Math.abs(diff); 1814ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (score > winnerScore) { 1815ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winnerScore = score; 1816ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent winner = target; 1817ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1818ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1819ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1820ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1821ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return winner; 1822ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1823ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1824ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1825ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called when a ViewHolder is swiped by the user. 1826ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1827ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If you are returning relative directions ({@link #START} , {@link #END}) from the 1828ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method 1829ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * will also use relative directions. Otherwise, it will use absolute directions. 1830ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1831ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If you don't support swiping, this method will never be called. 1832ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1833ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ItemTouchHelper will keep a reference to the View until it is detached from 1834ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * RecyclerView. 1835ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * As soon as it is detached, ItemTouchHelper will call 1836ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #clearView(RecyclerView, ViewHolder)}. 1837ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1838ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The ViewHolder which has been swiped by the user. 1839ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param direction The direction to which the ViewHolder is swiped. It is one of 1840ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #UP}, {@link #DOWN}, 1841ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #LEFT} or {@link #RIGHT}. If your 1842ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link #getMovementFlags(RecyclerView, ViewHolder)} 1843ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * method 1844ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * returned relative flags instead of {@link #LEFT} / {@link #RIGHT}; 1845ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * `direction` will be relative as well. ({@link #START} or {@link 1846ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * #END}). 1847ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1848ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public abstract void onSwiped(ViewHolder viewHolder, int direction); 1849ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1850ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1851ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed. 1852ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p/> 1853ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * If you override this method, you should call super. 1854ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1855ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param viewHolder The new ViewHolder that is being swiped or dragged. Might be null if 1856ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * it is cleared. 1857ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE}, 1858ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link ItemTouchHelper#ACTION_STATE_SWIPE} or 1859ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * {@link ItemTouchHelper#ACTION_STATE_DRAG}. 1860ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * @see #clearView(RecyclerView, RecyclerView.ViewHolder) 1861ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent */ 1862ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent public void onSelectedChanged(ViewHolder viewHolder, int actionState) { 1863ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (viewHolder != null) { 1864ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent sUICallback.onSelected(viewHolder.itemView); 1865ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1866ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1867ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1868ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent private int getMaxDragScroll(RecyclerView recyclerView) { 1869ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent if (mCachedMaxScrollSpeed == -1) { 1870ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize( 1871ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent R.dimen.item_touch_helper_max_drag_scroll_per_frame); 1872ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1873ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent return mCachedMaxScrollSpeed; 1874ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent } 1875ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent 1876ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent /** 1877ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true. 1878ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1879ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it 1880ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * modifies the existing View. Because of this reason, it is important that the View is 1881ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * still part of the layout after it is moved. This may not work as intended when swapped 1882ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views 1883ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * which were not eligible for dropping over). 1884ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1885ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * This method is responsible to give necessary hint to the LayoutManager so that it will 1886ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * keep the View in visible area. For example, for LinearLayoutManager, this is as simple 1887ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}. 1888ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * 1889ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's 1890ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * new position is likely to be out of bounds. 1891ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * <p> 1892ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * It is important to ensure the ViewHolder will stay visible as otherwise, it might be 1893ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * removed by the LayoutManager if the move causes the View to go out of bounds. In that 18945baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent * case, drag will end prematurely. 1895813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * 1896813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param recyclerView The RecyclerView controlled by the ItemTouchHelper. 1897813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param viewHolder The ViewHolder under user's control. 1898813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param fromPos The previous adapter position of the dragged item (before it was 18995baf2af52cd186633b7173196c1e4a4cd3435f22Eric Laurent * moved). 1900813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param target The ViewHolder on which the currently active item has been dropped. 1901813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param toPos The new adapter position of the dragged item. 1902813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param x The updated left value of the dragged View after drag translations 1903813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * are applied. This value does not include margins added by 1904813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * {@link RecyclerView.ItemDecoration}s. 1905813e2a74853bde19e37d878c596a044b3f299efcEric Laurent * @param y The updated top value of the dragged View after drag translations 1906ca7cc8273ffd88b9b89655808ee7e3df74162b83Eric Laurent * are applied. This value does not include margins added by 1907 * {@link RecyclerView.ItemDecoration}s. 1908 */ 1909 public void onMoved(final RecyclerView recyclerView, 1910 final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x, 1911 int y) { 1912 final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 1913 if (layoutManager instanceof ViewDropHandler) { 1914 ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView, 1915 target.itemView, x, y); 1916 return; 1917 } 1918 1919 // if layout manager cannot handle it, do some guesswork 1920 if (layoutManager.canScrollHorizontally()) { 1921 final int minLeft = layoutManager.getDecoratedLeft(target.itemView); 1922 if (minLeft <= recyclerView.getPaddingLeft()) { 1923 recyclerView.scrollToPosition(toPos); 1924 } 1925 final int maxRight = layoutManager.getDecoratedRight(target.itemView); 1926 if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) { 1927 recyclerView.scrollToPosition(toPos); 1928 } 1929 } 1930 1931 if (layoutManager.canScrollVertically()) { 1932 final int minTop = layoutManager.getDecoratedTop(target.itemView); 1933 if (minTop <= recyclerView.getPaddingTop()) { 1934 recyclerView.scrollToPosition(toPos); 1935 } 1936 final int maxBottom = layoutManager.getDecoratedBottom(target.itemView); 1937 if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) { 1938 recyclerView.scrollToPosition(toPos); 1939 } 1940 } 1941 } 1942 1943 void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, 1944 List<ItemTouchHelper.RecoverAnimation> recoverAnimationList, 1945 int actionState, float dX, float dY) { 1946 final int recoverAnimSize = recoverAnimationList.size(); 1947 for (int i = 0; i < recoverAnimSize; i++) { 1948 final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); 1949 anim.update(); 1950 final int count = c.save(); 1951 onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, 1952 false); 1953 c.restoreToCount(count); 1954 } 1955 if (selected != null) { 1956 final int count = c.save(); 1957 onChildDraw(c, parent, selected, dX, dY, actionState, true); 1958 c.restoreToCount(count); 1959 } 1960 } 1961 1962 void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected, 1963 List<ItemTouchHelper.RecoverAnimation> recoverAnimationList, 1964 int actionState, float dX, float dY) { 1965 final int recoverAnimSize = recoverAnimationList.size(); 1966 for (int i = 0; i < recoverAnimSize; i++) { 1967 final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); 1968 final int count = c.save(); 1969 onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, 1970 false); 1971 c.restoreToCount(count); 1972 } 1973 if (selected != null) { 1974 final int count = c.save(); 1975 onChildDrawOver(c, parent, selected, dX, dY, actionState, true); 1976 c.restoreToCount(count); 1977 } 1978 boolean hasRunningAnimation = false; 1979 for (int i = recoverAnimSize - 1; i >= 0; i--) { 1980 final RecoverAnimation anim = recoverAnimationList.get(i); 1981 if (anim.mEnded && !anim.mIsPendingCleanup) { 1982 recoverAnimationList.remove(i); 1983 } else if (!anim.mEnded) { 1984 hasRunningAnimation = true; 1985 } 1986 } 1987 if (hasRunningAnimation) { 1988 parent.invalidate(); 1989 } 1990 } 1991 1992 /** 1993 * Called by the ItemTouchHelper when the user interaction with an element is over and it 1994 * also completed its animation. 1995 * <p> 1996 * This is a good place to clear all changes on the View that was done in 1997 * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)}, 1998 * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, 1999 * boolean)} or 2000 * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}. 2001 * 2002 * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper. 2003 * @param viewHolder The View that was interacted by the user. 2004 */ 2005 public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) { 2006 sUICallback.clearView(viewHolder.itemView); 2007 } 2008 2009 /** 2010 * Called by ItemTouchHelper on RecyclerView's onDraw callback. 2011 * <p> 2012 * If you would like to customize how your View's respond to user interactions, this is 2013 * a good place to override. 2014 * <p> 2015 * Default implementation translates the child by the given <code>dX</code>, 2016 * <code>dY</code>. 2017 * ItemTouchHelper also takes care of drawing the child after other children if it is being 2018 * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this 2019 * is 2020 * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L 2021 * and after, it changes View's elevation value to be greater than all other children.) 2022 * 2023 * @param c The canvas which RecyclerView is drawing its children 2024 * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to 2025 * @param viewHolder The ViewHolder which is being interacted by the User or it was 2026 * interacted and simply animating to its original position 2027 * @param dX The amount of horizontal displacement caused by user's action 2028 * @param dY The amount of vertical displacement caused by user's action 2029 * @param actionState The type of interaction on the View. Is either {@link 2030 * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}. 2031 * @param isCurrentlyActive True if this view is currently being controlled by the user or 2032 * false it is simply animating back to its original state. 2033 * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, 2034 * boolean) 2035 */ 2036 public void onChildDraw(Canvas c, RecyclerView recyclerView, 2037 ViewHolder viewHolder, 2038 float dX, float dY, int actionState, boolean isCurrentlyActive) { 2039 sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState, 2040 isCurrentlyActive); 2041 } 2042 2043 /** 2044 * Called by ItemTouchHelper on RecyclerView's onDraw callback. 2045 * <p> 2046 * If you would like to customize how your View's respond to user interactions, this is 2047 * a good place to override. 2048 * <p> 2049 * Default implementation translates the child by the given <code>dX</code>, 2050 * <code>dY</code>. 2051 * ItemTouchHelper also takes care of drawing the child after other children if it is being 2052 * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this 2053 * is 2054 * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L 2055 * and after, it changes View's elevation value to be greater than all other children.) 2056 * 2057 * @param c The canvas which RecyclerView is drawing its children 2058 * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to 2059 * @param viewHolder The ViewHolder which is being interacted by the User or it was 2060 * interacted and simply animating to its original position 2061 * @param dX The amount of horizontal displacement caused by user's action 2062 * @param dY The amount of vertical displacement caused by user's action 2063 * @param actionState The type of interaction on the View. Is either {@link 2064 * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}. 2065 * @param isCurrentlyActive True if this view is currently being controlled by the user or 2066 * false it is simply animating back to its original state. 2067 * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, 2068 * boolean) 2069 */ 2070 public void onChildDrawOver(Canvas c, RecyclerView recyclerView, 2071 ViewHolder viewHolder, 2072 float dX, float dY, int actionState, boolean isCurrentlyActive) { 2073 sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState, 2074 isCurrentlyActive); 2075 } 2076 2077 /** 2078 * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View 2079 * will be animated to its final position. 2080 * <p> 2081 * Default implementation uses ItemAnimator's duration values. If 2082 * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns 2083 * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns 2084 * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have 2085 * any {@link RecyclerView.ItemAnimator} attached, this method returns 2086 * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION} 2087 * depending on the animation type. 2088 * 2089 * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. 2090 * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG}, 2091 * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or 2092 * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}. 2093 * @param animateDx The horizontal distance that the animation will offset 2094 * @param animateDy The vertical distance that the animation will offset 2095 * @return The duration for the animation 2096 */ 2097 public long getAnimationDuration(RecyclerView recyclerView, int animationType, 2098 float animateDx, float animateDy) { 2099 final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator(); 2100 if (itemAnimator == null) { 2101 return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION 2102 : DEFAULT_SWIPE_ANIMATION_DURATION; 2103 } else { 2104 return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration() 2105 : itemAnimator.getRemoveDuration(); 2106 } 2107 } 2108 2109 /** 2110 * Called by the ItemTouchHelper when user is dragging a view out of bounds. 2111 * <p> 2112 * You can override this method to decide how much RecyclerView should scroll in response 2113 * to this action. Default implementation calculates a value based on the amount of View 2114 * out of bounds and the time it spent there. The longer user keeps the View out of bounds, 2115 * the faster the list will scroll. Similarly, the larger portion of the View is out of 2116 * bounds, the faster the RecyclerView will scroll. 2117 * 2118 * @param recyclerView The RecyclerView instance to which ItemTouchHelper is 2119 * attached to. 2120 * @param viewSize The total size of the View in scroll direction, excluding 2121 * item decorations. 2122 * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value 2123 * is negative if the View is dragged towards left or top edge. 2124 * @param totalSize The total size of RecyclerView in the scroll direction. 2125 * @param msSinceStartScroll The time passed since View is kept out of bounds. 2126 * @return The amount that RecyclerView should scroll. Keep in mind that this value will 2127 * be passed to {@link RecyclerView#scrollBy(int, int)} method. 2128 */ 2129 public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, 2130 int viewSize, int viewSizeOutOfBounds, 2131 int totalSize, long msSinceStartScroll) { 2132 final int maxScroll = getMaxDragScroll(recyclerView); 2133 final int absOutOfBounds = Math.abs(viewSizeOutOfBounds); 2134 final int direction = (int) Math.signum(viewSizeOutOfBounds); 2135 // might be negative if other direction 2136 float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize); 2137 final int cappedScroll = (int) (direction * maxScroll * 2138 sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio)); 2139 final float timeRatio; 2140 if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) { 2141 timeRatio = 1f; 2142 } else { 2143 timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS; 2144 } 2145 final int value = (int) (cappedScroll * sDragScrollInterpolator 2146 .getInterpolation(timeRatio)); 2147 if (value == 0) { 2148 return viewSizeOutOfBounds > 0 ? 1 : -1; 2149 } 2150 return value; 2151 } 2152 } 2153 2154 /** 2155 * A simple wrapper to the default Callback which you can construct with drag and swipe 2156 * directions and this class will handle the flag callbacks. You should still override onMove 2157 * or 2158 * onSwiped depending on your use case. 2159 * 2160 * <pre> 2161 * ItemTouchHelper mIth = new ItemTouchHelper( 2162 * new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 2163 * ItemTouchHelper.LEFT) { 2164 * public abstract boolean onMove(RecyclerView recyclerView, 2165 * ViewHolder viewHolder, ViewHolder target) { 2166 * final int fromPos = viewHolder.getAdapterPosition(); 2167 * final int toPos = target.getAdapterPosition(); 2168 * // move item in `fromPos` to `toPos` in adapter. 2169 * return true;// true if moved, false otherwise 2170 * } 2171 * public void onSwiped(ViewHolder viewHolder, int direction) { 2172 * // remove from adapter 2173 * } 2174 * }); 2175 * </pre> 2176 */ 2177 public abstract static class SimpleCallback extends Callback { 2178 2179 private int mDefaultSwipeDirs; 2180 2181 private int mDefaultDragDirs; 2182 2183 /** 2184 * Creates a Callback for the given drag and swipe allowance. These values serve as 2185 * defaults 2186 * and if you want to customize behavior per ViewHolder, you can override 2187 * {@link #getSwipeDirs(RecyclerView, ViewHolder)} 2188 * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}. 2189 * 2190 * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be 2191 * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link 2192 * #END}, 2193 * {@link #UP} and {@link #DOWN}. 2194 * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be 2195 * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link 2196 * #END}, 2197 * {@link #UP} and {@link #DOWN}. 2198 */ 2199 public SimpleCallback(int dragDirs, int swipeDirs) { 2200 mDefaultSwipeDirs = swipeDirs; 2201 mDefaultDragDirs = dragDirs; 2202 } 2203 2204 /** 2205 * Updates the default swipe directions. For example, you can use this method to toggle 2206 * certain directions depending on your use case. 2207 * 2208 * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped. 2209 */ 2210 public void setDefaultSwipeDirs(int defaultSwipeDirs) { 2211 mDefaultSwipeDirs = defaultSwipeDirs; 2212 } 2213 2214 /** 2215 * Updates the default drag directions. For example, you can use this method to toggle 2216 * certain directions depending on your use case. 2217 * 2218 * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged. 2219 */ 2220 public void setDefaultDragDirs(int defaultDragDirs) { 2221 mDefaultDragDirs = defaultDragDirs; 2222 } 2223 2224 /** 2225 * Returns the swipe directions for the provided ViewHolder. 2226 * Default implementation returns the swipe directions that was set via constructor or 2227 * {@link #setDefaultSwipeDirs(int)}. 2228 * 2229 * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. 2230 * @param viewHolder The RecyclerView for which the swipe direction is queried. 2231 * @return A binary OR of direction flags. 2232 */ 2233 public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) { 2234 return mDefaultSwipeDirs; 2235 } 2236 2237 /** 2238 * Returns the drag directions for the provided ViewHolder. 2239 * Default implementation returns the drag directions that was set via constructor or 2240 * {@link #setDefaultDragDirs(int)}. 2241 * 2242 * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. 2243 * @param viewHolder The RecyclerView for which the swipe direction is queried. 2244 * @return A binary OR of direction flags. 2245 */ 2246 public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) { 2247 return mDefaultDragDirs; 2248 } 2249 2250 @Override 2251 public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) { 2252 return makeMovementFlags(getDragDirs(recyclerView, viewHolder), 2253 getSwipeDirs(recyclerView, viewHolder)); 2254 } 2255 } 2256 2257 private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { 2258 2259 ItemTouchHelperGestureListener() { 2260 } 2261 2262 @Override 2263 public boolean onDown(MotionEvent e) { 2264 return true; 2265 } 2266 2267 @Override 2268 public void onLongPress(MotionEvent e) { 2269 View child = findChildView(e); 2270 if (child != null) { 2271 ViewHolder vh = mRecyclerView.getChildViewHolder(child); 2272 if (vh != null) { 2273 if (!mCallback.hasDragFlag(mRecyclerView, vh)) { 2274 return; 2275 } 2276 int pointerId = e.getPointerId(0); 2277 // Long press is deferred. 2278 // Check w/ active pointer id to avoid selecting after motion 2279 // event is canceled. 2280 if (pointerId == mActivePointerId) { 2281 final int index = e.findPointerIndex(mActivePointerId); 2282 final float x = e.getX(index); 2283 final float y = e.getY(index); 2284 mInitialTouchX = x; 2285 mInitialTouchY = y; 2286 mDx = mDy = 0f; 2287 if (DEBUG) { 2288 Log.d(TAG, 2289 "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY); 2290 } 2291 if (mCallback.isLongPressDragEnabled()) { 2292 select(vh, ACTION_STATE_DRAG); 2293 } 2294 } 2295 } 2296 } 2297 } 2298 } 2299 2300 private class RecoverAnimation implements AnimatorListenerCompat { 2301 2302 final float mStartDx; 2303 2304 final float mStartDy; 2305 2306 final float mTargetX; 2307 2308 final float mTargetY; 2309 2310 final ViewHolder mViewHolder; 2311 2312 final int mActionState; 2313 2314 private final ValueAnimatorCompat mValueAnimator; 2315 2316 final int mAnimationType; 2317 2318 public boolean mIsPendingCleanup; 2319 2320 float mX; 2321 2322 float mY; 2323 2324 // if user starts touching a recovering view, we put it into interaction mode again, 2325 // instantly. 2326 boolean mOverridden = false; 2327 2328 boolean mEnded = false; 2329 2330 private float mFraction; 2331 2332 public RecoverAnimation(ViewHolder viewHolder, int animationType, 2333 int actionState, float startDx, float startDy, float targetX, float targetY) { 2334 mActionState = actionState; 2335 mAnimationType = animationType; 2336 mViewHolder = viewHolder; 2337 mStartDx = startDx; 2338 mStartDy = startDy; 2339 mTargetX = targetX; 2340 mTargetY = targetY; 2341 mValueAnimator = AnimatorCompatHelper.emptyValueAnimator(); 2342 mValueAnimator.addUpdateListener( 2343 new AnimatorUpdateListenerCompat() { 2344 @Override 2345 public void onAnimationUpdate(ValueAnimatorCompat animation) { 2346 setFraction(animation.getAnimatedFraction()); 2347 } 2348 }); 2349 mValueAnimator.setTarget(viewHolder.itemView); 2350 mValueAnimator.addListener(this); 2351 setFraction(0f); 2352 } 2353 2354 public void setDuration(long duration) { 2355 mValueAnimator.setDuration(duration); 2356 } 2357 2358 public void start() { 2359 mViewHolder.setIsRecyclable(false); 2360 mValueAnimator.start(); 2361 } 2362 2363 public void cancel() { 2364 mValueAnimator.cancel(); 2365 } 2366 2367 public void setFraction(float fraction) { 2368 mFraction = fraction; 2369 } 2370 2371 /** 2372 * We run updates on onDraw method but use the fraction from animator callback. 2373 * This way, we can sync translate x/y values w/ the animators to avoid one-off frames. 2374 */ 2375 public void update() { 2376 if (mStartDx == mTargetX) { 2377 mX = ViewCompat.getTranslationX(mViewHolder.itemView); 2378 } else { 2379 mX = mStartDx + mFraction * (mTargetX - mStartDx); 2380 } 2381 if (mStartDy == mTargetY) { 2382 mY = ViewCompat.getTranslationY(mViewHolder.itemView); 2383 } else { 2384 mY = mStartDy + mFraction * (mTargetY - mStartDy); 2385 } 2386 } 2387 2388 @Override 2389 public void onAnimationStart(ValueAnimatorCompat animation) { 2390 2391 } 2392 2393 @Override 2394 public void onAnimationEnd(ValueAnimatorCompat animation) { 2395 if (!mEnded) { 2396 mViewHolder.setIsRecyclable(true); 2397 } 2398 mEnded = true; 2399 } 2400 2401 @Override 2402 public void onAnimationCancel(ValueAnimatorCompat animation) { 2403 setFraction(1f); //make sure we recover the view's state. 2404 } 2405 2406 @Override 2407 public void onAnimationRepeat(ValueAnimatorCompat animation) { 2408 2409 } 2410 } 2411}