-
Notifications
You must be signed in to change notification settings - Fork 554
5.x | Drag&Drop and Swipe
- Activation
- Custom activation per Item Type
- Behaviors in action
- Dragging an item
- Swiping the front view
- Callbacks
// First, assign the adapter to the RecyclerView
mRecyclerView.setAdapter(mAdapter);
// Enable long press drag
mAdapter.setLongPressDragEnabled(true);
// Enable handle drag (needs an inner view in ViewHolder)
mAdapter.setHandleDragEnabled(true);
// Enable Swipe-To-Dismiss (needs views setup in ViewHolder)
mAdapter.setSwipeEnabled(true);
There are 2 ways to customize if a specific item type is Draggable or Swipeable:
- From 5.0.4, all items are draggable and swipeable by default: do nothing.
- Disable/enable features at runtime in the binding (depends by your use case): from implementation of
IFlexible
interface orAbstractFlexibleItem
extension:setDraggable(false/true)
,setSwipeable(false/true)
.
ℹ️ Previously in 5.0.3 and lower versions: override the 2 methods
isDraggable()
,isSwipeable()
inside the implementation of theFlexibleViewHolder
to return alwaystrue
ORsetDraggable(true)
,setSwipeable(true)
in the item constructor.
Override the following methods from FlexibleViewHolder
class (displaying default values):
/**
* Checks if the instance of the item represented by this ViewHolder is Draggable.
*/
@Override
public boolean isDraggable() {
IFlexible item = mAdapter.getItem(getFlexibleAdapterPosition());
return item != null && item.isDraggable();
}
/**
* Checks if the instance of the item represented by this ViewHolder is Swipeable.
*/
@Override
public boolean isSwipeable() {
IFlexible item = mAdapter.getItem(getFlexibleAdapterPosition());
return item != null && item.isSwipeable();
}
/**
* Allows to set elevation while the view is activated.
* Override to return desired value of elevation on this itemView.
*
* @return never elevate, returns 0dp if not overridden
*/
public float getActivationElevation() {
return 0f;
}
/**
* Allows to activate the itemView when Swipe event occurs.
* This method returns always false; Extend with "return true" to Not expand or
* collapse this ItemView onClick events.
*
* @return always false, if not overridden
*/
protected boolean shouldActivateViewWhileSwiping() {
return false;
}
/**
* Allows to add and keep item selection if ActionMode is active.
* This method returns always false; Extend with "return true" to add item to the
* ActionMode count.
*
* @return always false, if not overridden
*/
protected boolean shouldAddSelectionInActionMode() {
return false;
}
If the instance of the item is compliant with the Draggable status, it is sufficient to call the method:
mAdapter.setLongPressDragEnabled(true);
Everytime user drags an item, the following chain of methods are called:
/**
* Evaluate if positions are compatible for swapping.
*/
@Override
public boolean shouldMove(int fromPosition, int toPosition) {
return (mItemMoveListener == null || mItemMoveListener.shouldMoveItem(fromPosition, toPosition)) &&
!(isExpandable(getItem(fromPosition)) && getExpandableOf(toPosition) != null);
}
/**
* Called when an item has been dragged far enough to trigger a move. This is
* called every time an item is shifted, and NOT at the end of a "drop" event!!!
*/
@Override
@CallSuper
public boolean onItemMove(int fromPosition, int toPosition) {
swapItems(fromPosition, toPosition);
//After the swap, delegate further actions to the user
if (mItemMoveListener != null) {
mItemMoveListener.onItemMove(fromPosition, toPosition);
}
return true;
}
/**
* Swaps the elements of list at indices fromPosition and toPosition and notify
* the change. Selection of swiped elements is automatically updated.
*/
public void swapItems(int fromPosition, int toPosition) {
//This implementation takes care automatically:
//- The Header Linkage and notification of the items involved
// (for more details, see Headers And Section wiki page);
//- Collapse of expanded expandable;
//- Active selection swapping.
}
This functionality is compatible with Long Press Drag, both can be used simultaneously.
The Handle View is created in the ViewHolder:
static final class ViewHolder extends FlexibleViewHolder {
...
public ImageView mHandleView;
public ViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
//I left this part of the code free to be customized by the developer
//So he can decide what to do in case the functionality is
//enabled/disabled at runtime
this.mHandleView = (ImageView) view.findViewById(R.id.row_handle);
//Until the method is final, the logic inside the next method
//can be inserted here.
setDragHandleView(mHandleView);
}
//In beta7 this method is still declared final and cannot be overridden.
//With next release it will become available to be customized.
@Override
protected void setDragHandleView(@NonNull View view) {
if (mAdapter.isHandleDragEnabled()) {
view.setVisibility(View.VISIBLE);
super.setDragHandleView(view);
} else {
view.setVisibility(View.GONE);
}
}
}
SwipeRefreshLayout
is not compatible with dragging, at the bottom of the page the resolution is provided.
Different background can be shown by overriding the following methods of FlexibleViewHolder
class:
//Inner class
static final class ViewHolder extends FlexibleViewHolder {
...
private View frontView;
private View rearLeftView;
private View rearRightView;
public ViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
...
this.frontView = view.findViewById(R.id.front_view);
this.rearLeftView = view.findViewById(R.id.rear_left_view);
this.rearRightView = view.findViewById(R.id.rear_right_view);
}
@Override
public View getFrontView() {
return frontView;//default itemView
}
@Override
public View getRearLeftView() {
return rearLeftView;//default null
}
@Override
public View getRearRightView() {
return rearRightView;//default null
}
}
XML Layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout ...>
<RelativeLayout
android:id="@+id/rear_left_view"
android:visibility="gone">
.../>
<RelativeLayout
android:id="@+id/rear_right_view"
android:visibility="gone"
.../>
<!-- FrontView Must be at the lower level to be drawn Over the rear views -->
<RelativeLayout
android:id="@+id/front_view"
.../>
</FrameLayout>
In theory, only swipe-to-dismiss is supported. Swipe feature relies on androidx.recyclerview.widget.ItemTouchHelper
which is not flexible enough to support half-swipe, clicks on rear view or swipe the item back to how it was before.
However, half swipe can be achieved with others means, please see issues #98 and #100. See also commits of Apr 25, 2016.
The same way, if your feature does not involve removing the item after it has been swiped (for instance, when you use the swipe feature to mark an email as read/unread), you can leave the dataset untouched and use adapter.notifyItemChanged(viewHolder.getAdapterPosition());
to swipe back the item to its initial position. Please note that no animation will be performed (see this StackOverflow thread as a reference).
If you want to improve the swipe feature, please refer to this issue which mentions a few alternative libraries that may serve as an example for further improvements.
The Adapter method getItemTouchHelperCallback()
returns the instance of the Callback already initialized by the Adapter so you can add more customization for dragging and swiping, such as SwipeFlags LEFT
RIGHT
UP
DOWN
and SwipeThreshold limits.
There are 2 methods already implemented from the internal interface ViewHolderCallback
to handle the activation/selection in combination with the ActionMode. You can override if you want more customization.
public void onActionStateChanged(int position, int actionState);
public void onItemReleased(int position);
-
onActionStateChanged()
is called every time an event of Drag or Swipe changes state. When you have theSwipeRefreshLayout
or similar, I recommend to implement this method in the Activity/Fragment from theOnActionStateListener
interface, because it is not compatible with dragging and so you can properly disable/enable the refresh widget. -
onItemReleased()
is instead called when the ItemTouchHelper has completed the move or swipe, and the active item state should be cleared.FlexibleViewHolder
class already provides an implementation to disable the active state.
ℹ️ Note:
OnActionStateListener
is already extended by the listenersOnItemMoveListener
andOnItemSwipeListener
.
- Update Data Set
- Selection modes
- Headers and Sections
- Scrollable Headers and Footers
- Expandable items
- Drag&Drop and Swipe
- EndlessScroll / On Load More
- Search Filter
- FastScroller
- Adapter Animations
- Third party Layout Managers
- Payload
- Smooth Layout Managers
- Flexible Item Decoration
- Utils
- ActionModeHelper
- AnimatorHelper
- EmptyViewHelper
- UndoHelper
* = Under revision!