Code Monkey home page Code Monkey logo

dragdropswiperecyclerview's Introduction

Drag & Drop n' Swipe Recyclerview

issues pull requests contributors

Highly customizable Android library written in Kotlin that uses AndroidX and extends RecyclerView to include extra features, such as support for drag & drop and swipe gestures, among others. It works with vertical, horizontal and grid lists.

Demo

Drag & drop and swipe recycler view; demo with vertical list

Drag & drop and swipe recycler view; demo with grid list

Support this library

The creation (and maintenance) of this library requires time and effort. If you find it useful and want to support it, please use the link below:

Buy me a coffee!

How to use it

1. Reference the library

Add the library to your project via mavenCentral by adding the following in the app's build.gradle file:

dependencies {
    ...
    implementation 'com.ernestoyaquello.dragdropswiperecyclerview:drag-drop-swipe-recyclerview:1.2.0'
}

NOTE: Make sure you are using AndroidX instead of the old support libraries; otherwise this library might not work.

2. Add the list to your layout

Place the DragDropSwipeRecyclerview inside your layout using XML:

<!-- layout/view_layout.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:item_layout="@layout/list_item_layout"
        app:divider="@drawable/list_divider"/>

</FrameLayout>

As you can see in the code above, we specify the list item layout through the attribute item_layout. This way, the library will take care of inflating the item layout automatically, so you won't have to do it manually in the adapter.

In addition, in this example you can also see that the optional attribute divider is being used to specify the drawable that will be displayed between list items (for more information about available attributes, see Customization).

Referenced resource files

Just in case they are of any help, these are the example resource files referenced in the code above:

<!-- layout/list_item_layout.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center"
    android:padding="16dp"
    android:background="#eeeeee">
    <TextView
        android:id="@+id/item_text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"/>
    <ImageView
        android:id="@+id/drag_icon"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@drawable/ic_drag"/>
</LinearLayout>
<!-- drawable/list_divider.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size android:height="1dp" />
    <solid android:color="#e1e1e1" />
</shape>
<!-- drawable/ic_drag.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="24.0"
    android:viewportWidth="24.0">
    <path
        android:fillColor="#333333"
        android:pathData="M7,19V17H9V19H7M11,19V17H13V19H11M15,19V17H17V19H15M7,15V13H9V15H7M11,15V13H13V15H11M15,15V13H17V15H15M7,11V9H> 9V11H7M11,11V9H13V11H11M15,11V9H17V11H15M7,7V5H9V7H7M11,7V5H13V7H11M15,7V5H17V7H15Z" />
</vector>

3. Create the adapter

The next step is to implement your adapter (and viewholder) by extending the class DragDropSwipeAdapter<T, U>, where T will be your item type and U will be your viewholder type:

class MyAdapter(dataSet: List<String> = emptyList())
    : DragDropSwipeAdapter<String, MyAdapter.ViewHolder>(dataSet) {
    
    class ViewHolder(itemView: View) : DragDropSwipeAdapter.ViewHolder(itemView) {
        val itemText: TextView = itemView.findViewById(R.id.item_text)
        val dragIcon: ImageView = itemView.findViewById(R.id.drag_icon)
    }

    override fun getViewHolder(itemLayout: View) = MyAdapter.ViewHolder(itemLayout)

    override fun onBindViewHolder(item: String, viewHolder: MyAdapter.ViewHolder, position: Int) {
        // Here we update the contents of the view holder's views to reflect the item's data
        viewHolder.itemText.text = item
    }

    override fun getViewToTouchToStartDraggingItem(item: String, viewHolder: MyAdapter.ViewHolder, position: Int): View? {
        // We return the view holder's view on which the user has to touch to drag the item
        return viewHolder.dragIcon 
    }
}

This is just a basic implementation, but there are more methods in the adapter that you can override to customize the list and its behaviour (see Customization).

4. Setup the list

Finally, you should setup the list to make it work and take advantage of its features.

Set up the adapter

Inside onCreate or onCreateView, find the list and set it with a layout manager and your adapter:

val dataSet = listOf("Item 1", "Item 2", "Item 3")
mAdapter = MyAdapter(dataSet)
mList = findViewById(R.id.list)
mList.layoutManager = LinearLayoutManager(this)
mList.adapter = mAdapter

Set up the orientation

Then, specify the list orientation. For example:

mList.orientation = DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
Using grid orientation with dividers

Take into account that if and only if you want to show dividers in a list with a grid orientation, you also need to set one of these two properties:

// Set this property if your grid can be scrolled vertically
mList.numOfColumnsPerRowInGridList = <numberOfColumns>

Or:

// Set this property if your grid can be scrolled horizontally
mList.numOfRowsPerColumnInGridList = <numberOfRows>
Restricting swiping and dragging directions

In case you want to disallow dragging or swiping actions in certain directions, you can do the following right after specifying the list orientation:

// This disallows swiping items to the right
mList.disableSwipeDirection(ListOrientation.DirectionFlag.RIGHT)

Or:

// This disallows dragging items up
mList.disableDragDirection(ListOrientation.DirectionFlag.UP)

Set event listeners

Finally, create event listeners for the events you want to handle. For example, these are the listeners for actions of swiping, dragging & dropping and scrolling:

private val onItemSwipeListener = object : OnItemSwipeListener<String> {
    override fun onItemSwiped(position: Int, direction: OnItemSwipeListener.SwipeDirection, item: String): Boolean {
        // Handle action of item swiped
        // Return false to indicate that the swiped item should be removed from the adapter's data set (default behaviour)
        // Return true to stop the swiped item from being automatically removed from the adapter's data set (in this case, it will be your responsibility to manually update the data set as necessary)
        return false
    }
}

private val onItemDragListener = object : OnItemDragListener<String> {
    override fun onItemDragged(previousPosition: Int, newPosition: Int, item: String) {
        // Handle action of item being dragged from one position to another
    }

    override fun onItemDropped(initialPosition: Int, finalPosition: Int, item: String) {
        // Handle action of item dropped
    }
}

private val onListScrollListener = object : OnListScrollListener {
    override fun onListScrollStateChanged(scrollState: OnListScrollListener.ScrollState) {
        // Handle change on list scroll state
    }

    override fun onListScrolled(scrollDirection: OnListScrollListener.ScrollDirection, distance: Int) {
        // Handle scrolling
    }
}

Then, set the listeners...

mList.swipeListener = onItemSwipeListener
mList.dragListener = onItemDragListener
mList.scrollListener = onListScrollListener

And that's it! Your list with support for swipe and drag & drop should be fully working now.

Updating the items

Without implementing DiffUtil

The simplest way to update the items of your list is by setting the new list of items in the property dataSet of the adapter. This setter will take care of notifying the recyclerview about the changes, so all you need to do is set it.

On the other hand, in case you want to apply small updates to the collection of items instead of replacing it entirely at once, please take a look at the convenience methods that the adapter offers, such as addItem(), inserItem(), removeItem(), and moveItem(). These methods will take care of notifying the recyclerview about the specific changes that have been applied to the data set, which will result in the correct animations being run.

Implementing DiffUtil

In case you want to use DiffUtil to be able to replace the entire list of items while ensuring that the the correct animations are always run, please extend the class DragDropSwipeDiffCallback. Then, override the method createDiffUtil() of the adapter and make it return an instance of your implementation.

Customization

DragDropSwipeRecyclerView customization

There are several XML attributes that you can set in the DragDropSwipeRecyclerView in order to customize the style of the list and its items:

Drag & drop and swipe recycler view; available XML attributes


item_layout

The layout that will be used to populate each list item.

It can also be set in code doing

mList.itemLayoutId = R.layout.your_layout

divider

The drawable that will be displayed as a divider between list items. If set to null, no divider will be drawn. Null by default.

Please note that, for vertical lists, the divider drawable's height must be defined. In the same way, horizontal lists need a divider drawable with a defined width, and grid lists need a divider drawable with both width and height defined. Also note that the divider should have no transparency to be drawn correctly at all times.

It can also be set in Kotlin:

mList.dividerDrawableId = R.drawable.your_divider

behind_swiped_item_icon

The drawable of the icon to display behind an item that is being swiped in the default direction (i.e., left for horizontal swiping and down for vertical swiping). If set to null, no icon will be displayed behind swiped items. Null by default.

Please note that if there is a secondary icon defined (i.e., if behind_swiped_item_icon_secondary is defined), this icon will only be displayed when swiping in the default direction (i.e., left or down). However, if there isn't a secondary icon defined, this one will be displayed when swiping in any direction.

It can also be set in Kotlin:

mList.behindSwipedItemIconDrawableId = R.drawable.your_icon

behind_swiped_item_icon_secondary

The drawable of the icon to display behind an item that is being swiped in the secondary direction (i.e., right for horizontal swiping and up for vertical swiping). If set to null, the main icon -if defined- will be the only one to be displayed in all swipe directions. Null by default.

It can also be set in Kotlin:

mList.behindSwipedItemIconSecondaryDrawableId = R.drawable.your_icon

behind_swiped_item_icon_margin

The distance between the icon displayed behind an item that is being swiped and the side of the item from which the swiping started. 0 if not specified; ignored if behind_swiped_item_icon_centered is true.

It can also be set in Kotlin:

mList.behindSwipedItemIconMargin = <margin_value_as_float>

behind_swiped_item_icon_centered

Determines whether the icon displayed behind an item that is being swiped should be centered. If true, the icon will be centered; if false, the icon will be displayed near the side from which the swiping started. False by default.

It can also be set in Kotlin:

mList.behindSwipedItemCenterIcon = true

behind_swiped_item_bg_color

The background color to be displayed behind an item that is being swiped in the default direction (i.e., left for horizontal swiping and down for vertical swiping). If set to null, no color will be displayed behind items that are being swiped. Null by default.

Please note that if there is a secondary color defined (i.e., if behind_swiped_item_bg_color_secondary is defined), this one will only be displayed when swiping in the default direction (i.e., either left or down). However, if there isn't a secondary color defined, it will be displayed when swiping in any direction.

It can also be set in Kotlin:

mList.behindSwipedItemBackgroundColor = <color_value_as_integer>

behind_swiped_item_bg_color_secondary

The background color to be displayed behind an item that is being swiped in the secondary direction (i.e., right for horizontal swiping and up for vertical swiping). If set to null, the main background color -if defined- will be the only one to be displayed in all swipe directions. Null by default.

It can also be set in Kotlin:

mList.behindSwipedItemBackgroundSecondaryColor = <color_value_as_integer>

swiped_item_opacity_fades_on_swiping

Determines whether the item that is being swiped should appear more transparent the further it gets from its original position. False by default.

It can also be set in Kotlin:

mList.reduceItemAlphaOnSwiping = true

long_press_to_start_dragging

Determines whether the start of the dragging action will be triggered by a long press or by a standard one. True for long press; false for standard press. False by default.

It can also be set in Kotlin:

mList.longPressToStartDragging = true

Customizing the swipe action using custom layouts

In case you want to create your own layouts to display behind the swiped items, you can use these two attributes:


behind_swiped_item_custom_layout

The custom layout to be displayed behind an item that is being swiped. If it is set to null, no custom layout will be displayed behind swiped items. Null by default.

Please note that if there is a secondary layout defined (i.e., if behind_swiped_item_custom_layout_secondary is not null), this one will only be displayed when swiping in the default direction (i.e., either left or down). However, if there isn't a secondary layout defined, this one will be displayed when swiping in any direction.

It can also be set in Kotlin:

mList.behindSwipedItemLayoutId = R.layout.your_custom_layout

behind_swiped_item_custom_layout_secondary

The custom layout to be displayed behind an item that is being swiped in the secondary direction (i.e., right for horizontal swiping and up for vertical swiping). If set to null, the main custom layout -if defined- will be the only one to be displayed in all swipe directions. Null by default.

It can also be set in Kotlin:

mList.behindSwipedItemSecondaryLayoutId = R.layout.your_custom_layout

Adapter customization

Customizing the layout behind a swiped item

Some of the adapter methods can be extended to customize the layout that will be displayed behind a specific item when swiped:


getBehindSwipedItemLayoutId(item: T, viewHolder: U, position: Int): Int?

Called automatically to get the ID of the layout that will be displayed behind this specific item when swiped in the main direction (i.e., when swiped either left or down). If there isn't a secondary layout ID defined for this item, this one will also be displayed behind the item when swiped in the secondary direction (i.e., either right or up).

If set, the layout will be accessible for customization inside onBindViewHolder() via holder.behindSwipedItemLayout.

If null, this will be ignored and the default behind-swiped layout of the list, if any, will be used. Null by default.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns The ID of the layout that will be displayed behind this item when swiping it.


getBehindSwipedItemSecondaryLayoutId(item: T, viewHolder: U, position: Int): Int?

Called automatically to get the ID of the layout that will be displayed behind this specific item when swiped in the secondary direction (i.e., when swiped either right or up).

If set, the layout will be accessible for customization inside onBindViewHolder() via holder.behindSwipedItemSecondaryLayout.

If null, this will be ignored and the main behind-swiped layout of this item, if any, will be used. If there isn't one, the default behind-swiped layout of the list, if any, will be used. Null by default.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns The ID of the layout that will be displayed behind this item when swiping it in the secondary direction.


Customizing item behaviour

Some of the adapter methods can be extended to customize the behaviour of the list items:


getViewToTouchToStartDraggingItem(item: T, viewHolder: U, position: Int): View?

Called automatically to get the item view on which the user has to touch to drag the item. If it returns null, the main view of the item will be used for dragging.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns The item view on which the user has to touch to drag the item, or null if the view of the item that will be used for dragging is the main one.


canBeDragged(item: T, viewHolder: U, position: Int): Boolean

Called automatically to know if the specified item can be dragged.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns True if the item can be dragged; false otherwise. True by default.


canBeDroppedOver(item: T, viewHolder: U, position: Int): Boolean

Called automatically to know if the specified item accepts being exchanged by another one being dropped over it.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns True if the item accepts to be exchanged with another one being dragged over it; false otherwise. True by default.


canBeSwiped(item: T, viewHolder: U, position: Int): Boolean

Called automatically to know if the specified item can be swiped.

item The item as read from the corresponding position of the data set.

viewHolder The corresponding view holder.

position The position of the item within the adapter's data set.

returns True if the item can be swiped; false otherwise. True by default.


Event Handling within the adapter

Some of the adapter methods are callbacks that can be extended to customize the items after certain events, such as DragStarted, SwipeStarted, IsDragging, IsSwiping, DragFinished, SwipeFinished, etc. For example, you might want to update some of the item's views to change its appearance whenever the item is being dragged or swiped.

On this regard, please note that these methods are intended for item customization only. If you just want to be aware of the occurrence of basic list events (e.g., onItemDragged, onItemDropped, onItemSwiped), all you need to do is to subscribe to the listeners of the DragDropSwipeRecyclerView (see above the section How to use it).


onDragStarted(item: T, viewHolder: U)

Called when the dragging starts.

item The item as read from the corresponding position of the data set.

viewHolder The view holder for which the dragging action has started.


onSwipeStarted(item: T, viewHolder: U)

Called when the swiping starts.

item The item as read from the corresponding position of the data set.

viewHolder The view holder for which the swiping action has started.


onIsDragging(item: T?, viewHolder: U, offsetX: Int, offsetY: Int, canvasUnder: Canvas?, canvasOver: Canvas?, isUserControlled: Boolean)

Called when the dragging action (or animation) is occurring.

item The item as read from the corresponding position of the data set. It may be null in the unusual case when the dragged item is being momentaneously repositioned to an inexistent position by the system.

viewHolder The view holder for which the dragging action is occurring.

offsetX The offset in the X axis caused by the horizontal movement of the item. This offset is the distance measured from the current position of the item, which may be a different position than the one the item initially had when the dragging action started (the position of an item can change while the item is being dragged).

offsetY The offset in the Y axis caused by the vertical movement of the item. This offset is the distance measured from the current position of the item, which may be a different position than the one the item initially had when the dragging action started (the position of an item can change while the item is being dragged).

canvasUnder A canvas positioned just in front of the recycler view and behind all its items.

canvasOver A canvas positioned just in front of the recycler view and all its items.

isUserControlled True if the item is still being controlled manually by the user; false if it is just being animated automatically by the system after the user has stopped touching it.


onIsSwiping(item: T?, viewHolder: U, offsetX: Int, offsetY: Int, canvasUnder: Canvas?, canvasOver: Canvas?, isUserControlled: Boolean)

Called when the swiping action (or animation) is occurring.

item The item as read from the corresponding position of the data set. It may be null if this method is being called because the item layout is still being animated by the system but the item itself has already been removed from the data set.

viewHolder The view holder for which the swiping action is occurring.

offsetX The offset in the X axis caused by the horizontal movement of the item.

offsetY The offset in the Y axis caused by the vertical movement of the item.

canvasUnder A canvas positioned just in front of the recycler view and behind all its items.

canvasOver A canvas positioned just in front of the recycler view and all its items.

isUserControlled True if the item is still being controlled manually by the user; false if it is just being animated automatically by the system (which is usually the case when the system is finishing the swiping animation in order to move the item to its final position right after the user has already stopped touching it).


onDragFinished(item: T, viewHolder: U)

Called when the dragging finishes (i.e., when the item is dropped).

item The item as read from the corresponding position of the data set.

viewHolder The view holder for which the dragging action has finished.


onSwipeAnimationFinished(viewHolder: U)

Called when the swiping animation executed by the system to complete the swiping has finished. At the time this method gets called, the item has already been removed from the data set.

viewHolder The view holder for which the swiping animation has finished.


Complete Example

Check out the Sample App that is included in this repository: it has vertical lists, horizontal lists and grid lists, and it makes use of most of the library's features.

Contribution

Feel free to contribute to this library, any help will be welcomed!

License

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

dragdropswiperecyclerview's People

Contributors

ernestoyaquello avatar jonathanimperato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dragdropswiperecyclerview's Issues

CI?

Hello ernestoyaquello,

It would be good if this repo had some sort of CI, so that we could see if there are any breaking changes.

Love
Yer best pal in the world
and your next of kin

Change view properties in the BehindSwipedItemLayout

Hi, first thank you for the wonderful lib

My question, is that any way to manipulate BehindSwipedItemLayout, for exemple i want to show and hide a progressBar onItemSwiped

    private val onItemSwipeListener = object : OnItemSwipeListener<MyItem> {
        override fun onItemSwiped(position: Int, direction: OnItemSwipeListener.SwipeDirection, item: MyItem): Boolean {
                // Show loading view
                Handler(Looper.getMainLooper()).postDelayed({
                    dataSet[position].status = "3"
                    Log.v("status : ", "3")
                    mAdapter.notifyItemChanged(position)
                    Log.v("delay : ", "2 Sec")
                }, 2000)
                // Hide loading view
                return true
        }
    }

Thank you!

[Features] Firestore/Firebase support

Hi @ernestoyaquello,

I'm trying to integrate this with some workaround implementation of firestore adapter from Firebase UI samples. However, it gives me an error throwing out of bounds exception (since I cannot access the internal code of your library). It would be nice to have a firebase or firestore adapter support.

PS. I want to mention that I'm having fun using your library. Thank you for your impressive open-source work.

Crash - ArrayIndexOutOfBoundsException

PID: 28622
java.lang.ArrayIndexOutOfBoundsException: length=6; index=-1
at java.util.ArrayList.get(ArrayList.java:413)
at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter.onIsDraggingImpl(DragDropSwipeAdapter.kt:571)
at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter.access$onIsDraggingImpl(DragDropSwipeAdapter.kt:26)
at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter$itemLayoutPositionListener$1.onPositionChanged(DragDropSwipeAdapter.kt:344)
at com.ernestoyaquello.dragdropswiperecyclerview.util.DragDropSwipeTouchHelper.onChildDrawImpl(DragDropSwipeTouchHelper.kt:211)
at com.ernestoyaquello.dragdropswiperecyclerview.util.DragDropSwipeTouchHelper.onChildDraw(DragDropSwipeTouchHelper.kt:159)
at androidx.recyclerview.widget.ItemTouchHelper$Callback.onDraw(ItemTouchHelper.java:1983)
at androidx.recyclerview.widget.ItemTouchHelper.onDraw(ItemTouchHelper.java:561)
at androidx.recyclerview.widget.RecyclerView.onDraw(RecyclerView.java:4284)
at android.view.View.draw(View.java:18410)
at androidx.recyclerview.widget.RecyclerView.draw(RecyclerView.java:4219)
at android.view.View.updateDisplayListIfDirty(View.java:17388)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.updateDisplayListIfDirty(View.java:17383)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.draw(View.java:18413)
at android.view.View.updateDisplayListIfDirty(View.java:17388)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2023)
at android.view.View.draw(View.java:18413)
at android.view.View.updateDisplayListIfDirty(View.java:17388)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.updateDisplayListIfDirty(View.java:17383)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.updateDisplayListIfDirty(View.java:17383)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.updateDisplayListIfDirty(View.java:17383)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.updateDisplayListIfDirty(View.java:17383)
at android.view.View.draw(View.java:18172)
at android.view.ViewGroup.drawChild(ViewGroup.java:3969)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3755)
at android.view.View.draw(View.java:18413)
at com.android.internal.policy.DecorView.draw(DecorView.java:967)
at android.view.View.updateDisplayListIfDirty(View.java:17388)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:722)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:728)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:836)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3181)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2977)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2565)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1550)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7189)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:959)
2019-08-25 15:29:06.805 28622-28622/com.example.sv.testingApp E/AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:734)
at android.view.Choreographer.doFrame(Choreographer.java:670)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:945)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

Remove default delete action

Thanks for working on the swipe functionality. I have one further suggestion. Currently the library fires the ‘onItemSwiped’ callback and immediately removes the item from the array list. This makes it impossible to add undo/other functionality as I cannot intercept the delete.

I have pulled the library and simply deleted the ‘remove’ function call. I can then call ‘remove’ from my own app whenever I like. In my case I show a confirmation dialog in the onItemSwiped before calling remove.

Long Click Event not fired while refresh items short periodically

Thanks for your great library.
I have implemented my fragments which shows live prices with your recyclerview.
But I have a problem with item long click listener.
The issue is that item long click events are not fired when refresh items per 500ms or less time.
How can I solve this issue?

Best.

Swipe background color corner radius

Hi @ernestoyaquello
First of all thanks a lot for this wonderful library 👍

I've just started using it so I might miss something but I am using MaterialCardView for the list item layout - swipe background comes without corner radius on it 😞

It can be easily demonstrated in your sample app if you set the corner radius to 20dp (in list_item_vertical_list_cardview.xml) for example.
I've made a quick demo video on this:

ezgif com-video-to-gif

In my code, I need to set the corner radius to 0dp to avoid such an issue 😞
Can you please help with this?

Grid issue

Hello,
maybe I've not really gotten it but I can't create a grid view with 5 columns and 2 rows.

mList.numOfColumnsPerRowInGridList = 5 or mList.numOfRowsPerColumnInGridList = 2 don't have any effect.

Any hints are appreciated.

Does not draw divider and behindSwipedItemIcon etc.

Hi, my divider is not displayed and everything that should appear in the background (icons and color). Swipe works, but when the swipe does not appear the icon and the background color, as well as the listeners do not work. I specified parameters both through the XML and in the code. Nothing has changed. Perhaps this is due to the fact that the list is inside in ViewPager?!

My main layout

<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorBackground"
        android:fitsSystemWindows="true">

    <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/report_app_bar_height"
            android:background="@color/colorBackground"
            android:theme="@style/AppTheme.AppBarOverlay">

        <com.google.android.material.appbar.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                android:minHeight="@dimen/bar_default_height"
                android:theme="@style/WalletCollapsedBar"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                app:titleEnabled="false">

            <RelativeLayout
                    android:id="@+id/dashboard_diagram_container"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/diagram_height"
                    android:paddingBottom="@dimen/default_very_small_margin"
                    android:background="@color/colorPrimary"/>

            <TextView
                    android:id="@+id/diagram_place_holder"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="@string/diagram_place_holder"
                    android:layout_gravity="center"
                    android:textColor="@color/colorAccent"/>

            <com.gigamole.navigationtabstrip.NavigationTabStrip
                    android:id="@+id/nav_tab_period"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/tab_period_height"
                    android:layout_gravity="bottom"
                    android:layout_marginBottom="@dimen/default_very_small_margin"
                    android:fitsSystemWindows="true"
                    app:layout_collapseMode="pin"
                    app:nts_animation_duration="300"
                    app:nts_color="@color/colorAccent"
                    app:nts_corners_radius="@dimen/tab_period_radius"
                    app:nts_inactive_color="@color/colorPrimaryDark"
                    app:nts_active_color="@color/colorAccent"
                    app:nts_size="@dimen/default_middle_margin"
                    app:nts_type="point"
                    app:nts_weight="@dimen/tab_period_nts_weight"/>

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
            android:id="@+id/dashboard_view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

ViewPager has two fragments in which lists

<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/total_sum_layout"
        android:background="@color/colorBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <View android:id="@+id/sum_background_view"
          android:layout_width="match_parent"
          android:layout_height="80dp"
          android:background="@color/colorSeparator"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintEnd_toEndOf="parent"/>

    <TextView
            android:id="@+id/main_tv_total_sum_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/total_sum_layout"
            android:textColor="@android:color/black"
            android:textSize="@dimen/default_big_text_size"
            app:layout_constraintStart_toStartOf="@+id/sum_background_view"
            app:layout_constraintEnd_toEndOf="@+id/sum_background_view"
            android:layout_marginTop="@dimen/default_small_margin"
            app:layout_constraintTop_toTopOf="@+id/sum_background_view"/>

    <TextView
            android:id="@+id/main_tv_total_sum"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/default_middle_margin"
            android:layout_marginTop="@dimen/default_very_small_margin"
            android:textColor="@color/colorAccent"
            android:gravity="center"
            android:textSize="@dimen/default_middle_text_size"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/main_tv_total_sum_title"
            android:layout_marginEnd="@dimen/default_middle_margin"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="@+id/sum_background_view"/>


    <com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
            android:id="@+id/recycler_spend"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:item_layout="@layout/item_spend_list"
            app:divider="@drawable/list_divider"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/sum_background_view"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Adapter

class SpendAdapter(
    private val items: MutableList<Spend>,
    val presenter: DashboardContract.Presenter
) : DragDropSwipeAdapter<Spend, SpendAdapter.SpendViewHolder>(items) {


    override fun onBindViewHolder(item: Spend, viewHolder: SpendViewHolder, position: Int) {
        val wallet = presenter.getWalletByID(item.walletId)
        viewHolder.tvWalletName.text = wallet.name
        viewHolder.tvSpendSum.text = "${item.value} ${wallet.currency.shortName}"
        viewHolder.tvCategory.text = presenter.getCategoryByID(item.categoryId).name
        viewHolder.tvDate.text = DateUtils.getFormattedDate(item.timeStamp)
        viewHolder.tvNote.text = item.note
    }

    override fun getViewHolder(itemView: View) =
        SpendViewHolder(itemView)

    override fun getViewToTouchToStartDraggingItem(item: Spend, viewHolder: SpendViewHolder, position: Int): View? {
        return viewHolder.fgContainer
    }

    internal fun addButch(items: MutableList<Spend>) {
        this.items.clear()
        this.items.addAll(items)
    }

    class SpendViewHolder : DragDropSwipeAdapter.ViewHolder {

        @BindView(R.id.spend_item_row_fg)
        internal lateinit var fgContainer: ConstraintLayout
        @BindView(R.id.spend_tv_wallet_name)
        internal lateinit var tvWalletName: TextView
        @BindView(R.id.spend_tv_sum)
        internal lateinit var tvSpendSum: TextView
        @BindView(R.id.spend_tv_category_name)
        internal lateinit var tvCategory: TextView
        @BindView(R.id.spend_tv_date)
        internal lateinit var tvDate: TextView
        @BindView(R.id.spend_tv_note_name)
        internal lateinit var tvNote: TextView

        constructor(itemView: View) : super(itemView) {
            ButterKnife.bind(this, itemView)
        }
    }
}

 Code in fragment

    @BindView(R.id.recycler_spend)
    internal lateinit var recyclerSpend: DragDropSwipeRecyclerView
    private var spendAdapter: SpendAdapter? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initList()
    }

    private fun initList() {
        recyclerSpend.layoutManager = LinearLayoutManager(activity)
        recyclerSpend.orientation = DragDropSwipeRecyclerView.ListOrientation.HORIZONTAL_LIST_WITH_HORIZONTAL_DRAGGING
        recyclerSpend.orientation?.removeSwipeDirectionFlag(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.DOWN)
        recyclerSpend.orientation?.removeSwipeDirectionFlag(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.UP)
        recyclerSpend.behindSwipedItemIconDrawableId = R.drawable.ic_remove_white
        recyclerSpend.behindSwipedItemIconSecondaryDrawableId = R.drawable.ic_edit_white
        recyclerSpend.behindSwipedItemIconMargin = 16.toFloat()
        recyclerSpend.behindSwipedItemBackgroundColor = R.color.colorSnackBarBackground
        recyclerSpend.behindSwipedItemBackgroundSecondaryColor = R.color.colorAccent
        recyclerSpend.reduceItemAlphaOnSwiping = true

        recyclerSpend.swipeListener = object : OnItemSwipeListener<String> {
            override fun onItemSwiped(position: Int, direction: OnItemSwipeListener.SwipeDirection, item: String): Boolean {
                // Return false to indicate that the swiped item should be removed from the adapter's data set (default behaviour)
                // Return true to stop the swiped item from being automatically removed from the adapter's data set (in this case, it will be your responsibility to manually update the data set as necessary)
                return false
            }
        }
    }

    internal fun updateSpendList(list: MutableList<Spend>) {
        spendAdapter = SpendAdapter(list, presenter) //How to update adapter recyclerSpend.adapter?.notifyDataSetChanged() - doesn't work
        recyclerSpend.adapter = spendAdapter
    }

Error inflating class com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView

Error: Binary XML file line #19: Binary XML file line #19: Error inflating class com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
This is my layout file:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@drawable/app_background"
android:paddingTop="?actionBarSize">

<FrameLayout
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView
        android:id="@+id/favsRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible"
        app:item_layout="@layout/song_item" />

</FrameLayout>

<TextView
    android:id="@+id/no_fav_text"
    style="@style/TrackText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:animateLayoutChanges="true"
    android:gravity="center"
    android:padding="24dp"
    android:text="@string/no_fav_message"
    android:textAlignment="center"
    android:visibility="invisible"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:visibility="visible" />

</androidx.constraintlayout.widget.ConstraintLayout>

What is wrong? I am using AndroidX

By the way the library is really well made. Thanks a lot for your effort!

No updateDataSet() Method

I see that you made the adapter take in the dataset inside the constructor. Would it be possible to update this to include either the dataset in the constructor or use a method called updateDataset(list:Mutablelist){}. This would allow the use of DiffUtil in adapters. Would you be able to possibly add that as a method and allow the constructor list to be null-able?

Inheritance

can we use this adapter without inheritance. I mean I must use composition. How to do that

Swipe only one Direction and DiffUtil

Hey, is it possible only to allow swiping in one direction (primary or secondary)? Possibly they could be renamed to Left/Right or Start/End to follow convention?
Also it would be great if the Adapter would feature the DiffUtil ListAdapter for nice animations :)

Bug - Empty items are drawn on on dragging

I faced a strange bug. I have grid recycler (3 cols) with for example 50 draggable and 50 non-draggable items. When I'm dragging an item to the botom and release after the point where last draggable item goes out of the screen and gets recycled, scroll up to the place of last draggable position (where my dragged view is located now), I'm seeing 3 empty cells
image

Disable Swipe

I want to disable swipe on some screen programmatically?unable to do this

Two different recyclers with different ListOrientation objects share removed swipe direction flags

I have 2 completely separated recyclers which are in different fragments in ViewPager. When I remove a swipe or drag flag from one of them it is automatically removed in the second which I find confusing.

Fragment 1:
recyclerOne.layoutManager = LinearLayoutManager(context!!)
recyclerOne.adapter = adapterOne
val orientation = DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
orientation.removeSwipeDirectionFlag(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
recyclerOne.orientation = orientation

Fragment 2:
recyclerTwo.layoutManager = LinearLayoutManager(context!!)
recyclerTwo.adapter = adapterTwo
val orientation = DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
orientation.removeSwipeDirectionFlag(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.LEFT)
recyclerTwo.orientation = orientation

The above code will make both recyclers unswipable in either directions instead of making then swipable in different directions.

No Java example.

Spent a long time trying to decode the Kotlin example to work in Java, and still having troubles. At this point, i'm not sure if it's me, or it simply isn't compatible. A Java example would be useful.

disableSwipeDirection & enableSwipeDirection not working as expected

In my OnCreateView fragment, I am setting these properties based on a "lock UI" preference, which prevents swiping left/right/reordering.

In short, I need to restart the activity for this to take effect, it doesn't work setting this within the fragment OnCreateView, even though the code paths are being executed at the expected time. Is there something else I need to do here, or is this a bug?

Thanks.

_binding.list.setOrientation(DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING);

if (PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("UI_LOCKED", false))
{
    _binding.list.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.LEFT);
    _binding.list.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT);
}
else
{
    _binding.list.enableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.LEFT);
    _binding.list.enableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT);
}

Crash

Process: com.macoev.nanana, PID: 7851 java.lang.ArrayIndexOutOfBoundsException: length=2; index=-1 at java.util.ArrayList.get(ArrayList.java:439) at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter.onIsDraggingImpl(DragDropSwipeAdapter.kt:549) at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter.access$onIsDraggingImpl(DragDropSwipeAdapter.kt:22) at com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeAdapter$itemLayoutPositionListener$1.onPositionChanged(DragDropSwipeAdapter.kt:339) at com.ernestoyaquello.dragdropswiperecyclerview.util.DragDropSwipeTouchHelper.onChildDrawImpl(DragDropSwipeTouchHelper.kt:211) at com.ernestoyaquello.dragdropswiperecyclerview.util.DragDropSwipeTouchHelper.onChildDraw(DragDropSwipeTouchHelper.kt:159) at androidx.recyclerview.widget.ItemTouchHelper$Callback.onDraw(ItemTouchHelper.java:1983) at androidx.recyclerview.widget.ItemTouchHelper.onDraw(ItemTouchHelper.java:561) at androidx.recyclerview.widget.RecyclerView.onDraw(RecyclerView.java:4284) at android.view.View.draw(View.java:19225) at androidx.recyclerview.widget.RecyclerView.draw(RecyclerView.java:4219) at android.view.View.updateDisplayListIfDirty(View.java:18175) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.draw(View.java:19228) at android.view.View.updateDisplayListIfDirty(View.java:18175) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2070) at android.view.View.draw(View.java:19228) at android.view.View.updateDisplayListIfDirty(View.java:18175) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2070) at android.view.View.draw(View.java:19228) at android.view.View.updateDisplayListIfDirty(View.java:18175) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.updateDisplayListIfDirty(View.java:18166) at android.view.View.draw(View.java:18953) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.draw(View.java:19228) at com.android.internal.policy.DecorView.draw(DecorView.java:786) at android.view.View.updateDisplayListIfDirty(View.java:18175) 2019-01-08 18:23:05.652 7851-7851/com.macoev.nanana E/AndroidRuntime: at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:647) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:653) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:761) at android.view.ViewRootImpl.draw(ViewRootImpl.java:3027) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2831) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2384) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1416) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6845) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966) at android.view.Choreographer.doCallbacks(Choreographer.java:778) at android.view.Choreographer.doFrame(Choreographer.java:713) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6798) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

How to simulate:
Create a draggable adapter with at least 2 elements.
Quickly click on one and next on the other until the crash comes.
Please let me know if you need any further info or explanation.
I need this to be fixed. Thanks in advance!

Feature request

Is there any way to change position only two list items (swipe only two) views. That means the the dragged one and the one where the dragged item is dropped.

Add onListScrollStateChanged to OnListScrollListener

As far as I've seen from the code, onScrolled(RecyclerView recyclerView, int dx, int dy) is used for onListScrolled(scrollDirection: OnListScrollListener.ScrollDirection, distance: Int).
But onScrollStateChanged(RecyclerView recyclerView, int newState) is unsued.

What about adding a new method to the listener to indicate changes in the state of the scroll? I think the changes in the code would be pretty small.

This feature could be helpful in some situations.

Looking forward to feedback.

Java example?

Hi is there a java example of this ? :) would be nice

Sometimes items in the Reyclerview become less opaque after dataSet changes

This happens to me when I delete items from my data source (LiveData) and then I try to update the dataSet.

viewModel.items.observe(this@MyFragment, Observer { adapter.dataSet = it })

Also if you try to move the opaque item, his opacity will become normal again.

This does not happen when you use the default behavior of the swipe, which as far as I've seen uses removeItem(position: Int)

On the other hand, when adding an item everything seems to be ok.
I'm not sure this happens. I will try to reproduce it in a smaller project asap.

ezgif com-video-to-gif

Some questions regarding the example app

I noticed a few things in your example app:

  1. You set the divider and item_layout via XML, but at the same time, the BaseListFragment subclasses set the divider and item_layout programmatically. Should users follow this? If this is the case the README should be updated since the README only talks about setting it in via XML.

  2. Should we implement the same logic found in onItemAddedListener in the BaseListFragment, in order to add new items to the RecyclerView? Specifically, I'm puzzled as to why you had to call adapter.insertItem(position, item)--does this mean the adapter doesn't hold a reference to the list that was passed to its constructor?

  3. In fragment_vertical_list.xml, why are you setting behind_swiped_item_bg_color to @color/swipeBehindBackground, but on VerticalListFragment's setupLayoutBehindItemLayoutOnSwiping method, you are setting behindSwipedItemBackgroundColor to @null? Why are you duplicating the setting of the color?

DragDropSwipeRecyclerview does not recycle when is within NestedScrollView

I've been trying to use the recycler view within a NestedScrollView but it seems to lose all of the recycle-ability, I've searched around stack overflow and I've tried enabling/disabling isNestedScrollEnabled but doesn't seem to work.
Normal RecyclerView works just normal in nestedScroll View

[QUESTION]: Can it use with NestedScrollView ?

when using it inside NestedScrollView the auto scroll not working. try to use android:fillViewport="true"
android:orientation="vertical", requestDisallowInterceptTouchEvent with no success

How do you limit swipe direction?

Seems to default to both directions but hoping for right to left only. Looks like something to do with the ListOrientation but cannot figure out the Java syntax.

Difficulty creating views of different types

My recycler, receives some different types of item views, in my current implementation, I use the viewType inside onCreateViewHolder, to differentiate and inflate different layouts. I wonder if it is possible to do this with your library?

New features - drag on long click & drag item animator

Hi!
The first thing I want to mention is it would be nice if you add an option to start dragging on long click (where the current touch listener in TouchHelper is)
Also it'd be a great thing if you add a feature to animate dragged view (kinda DragViewAnimator interface) (For example to scale view up on drag started and down on ended)

behindSwipedItemLayout disappears on reinitializing the adapter

View Used:: DragDropSwipeRecyclerView
Adapter Used:: DragDropSwipeAdapter

XML

    `<com.ernestoyaquello.dragdropswiperecyclerview.DragDropSwipeRecyclerView`
        android:layout_width="match_parent"
        android:id="@+id/rv_orders"
        app:behind_swiped_item_custom_layout="@layout/behind_swiped_vertical_list"
        app:behind_swiped_item_custom_layout_secondary="@layout/behind_swiped_vertical_list_secondary"
        android:layout_height="match_parent" />`

Main Activity

`private fun setupRecyclerView(orders: List<OrderSingle>) {
    if (swipeRefresh.isRefreshing) {
        swipeRefresh.isRefreshing = false
    }
    if(currentPage == 1) {
        mainOrdersList = orders.toMutableList()
        layoutManager = LinearLayoutManager(this)
        rv_orders.layoutManager = layoutManager
        adapter = OrdersAdapter(this, this, mainOrdersList, mainOrdersList, status!!)
        rv_orders.adapter = adapter
        adapter.notifyDataSetChanged()
        rv_orders.orientation = DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
        rv_orders.behindSwipedItemLayoutId = R.layout.behind_swiped_vertical_list
        rv_orders.behindSwipedItemSecondaryLayoutId = R.layout.behind_swiped_vertical_list_secondary
        rv_orders.reduceItemAlphaOnSwiping = true
        rv_orders.scrollListener = onListScrollListener
        rv_orders.dragListener = onItemDragListener
        rv_orders.swipeListener = onItemSwipeListener
        rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.UP)
        rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.DOWN)
        rv_orders.isNestedScrollingEnabled = true
        rv_orders.setHasFixedSize(true)
        when (status) {
            "NEW" -> {
                rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
            }
            "PENDING" -> {
                rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.LEFT)
            }
            else -> {
                rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.LEFT)
                rv_orders.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
            }
        }
    } else {
        mainOrdersList.addAll(orders)
        Log.e("More loading","SIZE " + mainOrdersList.size)
        Log.e("More loading","SIZE 2 " + orders.size)
        adapter.notifyDataSetChanged()
    }
}`

Adapter

`class OrdersAdapter(
private var context: Context,
private var listener: OnItemClickListener,
private var data: List,
private var filteredData: List,
private var statusText: String
) : DragDropSwipeAdapter<OrderSingle, OrdersAdapter.MyViewHolder>(filteredData), Filterable {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val view =
        LayoutInflater.from(parent.context).inflate(R.layout.single_item_order, parent, false)
    return MyViewHolder(view)
}

override fun getItemCount(): Int {
    Log.e("COUNT", filteredData.size.toString())
    return filteredData.size
}


class MyViewHolder(view: View) : DragDropSwipeAdapter.ViewHolder(view) {
    val singleOrderItem: LinearLayout = view.findViewById(R.id.singleOrderItem)
    val orderId: TextView = view.findViewById(R.id.tv_orderId_SO)
    val status: TextView = view.findViewById(R.id.tv_status_SO)
    val name: TextView = view.findViewById(R.id.tv_name_SO)
    private val address: TextView = view.findViewById(R.id.tv_address_SO)
    val dragIcon: ImageView = view.findViewById(R.id.drag_icon)


    fun bindTo(item: OrderSingle, context: Context, statusText: String) {
        try {
            orderId.text = "ORDER ID: ${item.orderId}"

            status.text = item.orderStatus
            if(item.orderStatus == "PICKUP") {
                status.text = "REMAINING"
            }
            when (statusText) {
                "PENDING", "NEW" -> {
                    status.setTextColor(ContextCompat.getColor(context, R.color.pending))
                }
                "DELIVERED" -> {
                    status.setTextColor(ContextCompat.getColor(context, R.color.delivered))
                }
                else -> {
                    status.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary))
                }
            }
            name.text = item.shipTo
            address.text = item.shippingAddress
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

override fun getFilter(): Filter {
    return object : Filter() {
        override fun performFiltering(charSequence: CharSequence): FilterResults? {
            val charString = charSequence.toString()
            if (charString.isEmpty()) {
                filteredData = data
            } else {
                val filteredList: MutableList<OrderSingle> = ArrayList()
                for (row in data) { // name match condition. this might differ depending on your requirement
                    if (
                        row.orderId.toLowerCase(Locale.ROOT).contains(charString.toLowerCase(Locale.ROOT)) ||
                        row.shipTo.toLowerCase(Locale.ROOT).contains(charString.toLowerCase(Locale.ROOT)) ||
                        row.shippingAddress.toLowerCase(Locale.ROOT).contains(charString.toLowerCase(Locale.ROOT)) ||
                        row.orderStatus.toLowerCase(Locale.ROOT).contains(charString.toLowerCase(Locale.ROOT)) ||
                        row.deliveryType.toLowerCase(Locale.ROOT).contains(charString.toLowerCase(Locale.ROOT))
                            ) {
                        filteredList.add(row)
                    }
                }
                filteredData = filteredList
            }
            val filterResults = FilterResults()
            filterResults.values = filteredData
            return filterResults
        }

        override fun publishResults(
            charSequence: CharSequence?,
            filterResults: FilterResults
        ) {
            filteredData = filterResults.values as List<OrderSingle>
            notifyDataSetChanged()
        }
    }
}



override fun getViewHolder(itemView: View): MyViewHolder {
    return MyViewHolder(itemView)
}


override fun getViewToTouchToStartDraggingItem(
    item: OrderSingle,
    viewHolder: MyViewHolder,
    position: Int
) = viewHolder.dragIcon

override fun onBindViewHolder(item: OrderSingle, viewHolder: MyViewHolder, position: Int) {
    viewHolder.bindTo(item, context, statusText)
    viewHolder.singleOrderItem.setOnClickListener {
        if(statusText != "NEW")
            listener.onItemClick(filteredData[position].orderId)
    }
}

}`

Whenever the list pull pulled from top to bottom, new data set is called and that in turn calls 'setupRecyclerView' and provides it with new data list and as soon as I refresh the list, both the behindSwipedItemLayout disappears and I get empty background when swiped.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.