I have recyclerVIew in which I implement ItmeTouchHelper to drag and drop the items of RecyclerVIew but the positions of items are not saved
var itemTouch = ItemTouchHelper(object :
ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN,0){
override fun onMove(recyclerView: RecyclerView?, source: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean {
var source = source?.adapterPosition
var target = target?.adapterPosition
Collections.swap(database.DairyData(),source!!,target!!)
adapter.notifyItemMoved(source,target)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {
}
})
itemTouch.attachToRecyclerView(Dairy_recyclerView)
Related
I am building an app to be able to drag an item from one recycle view to another and I still have to keep the option to re-order inside a single recycler view.
So I have defined a Reorder Callback already
class ReorderHelperCallback(val adapter : ItemTouchHelperAdapter): ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
return makeMovementFlags( dragFlags, 0)//swipeFlags )
}
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
adapter.onItemMove(source.getAdapterPosition(),
target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//Not use for Drag N Drop
}
}
also an interface:
interface OnStartDragListener {
fun onStartDrag(viewHolder: RecyclerView.ViewHolder?)
}
and a touchhelper:
interface ItemTouchHelperAdapter {
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
fun onItemDismiss(position: Int)
}
to allow the reorder to work, I had to update the Recycler view adapter as below:
class Adapter(
private var context: Context?,
val dragStartListener : OnStartDragListener
): RecyclerView.Adapter<Adapter.ViewHolder>(), ItemTouchHelperAdapter {
var arrayItems : ArrayList<Data?> = ArrayList()
fun setData(array : MutableList<Data?>){
array.toCollection(arrayItems)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return arrayItems.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter.ViewHolder {
val binding = DashboardTileLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding, dragStartListener)
}
override fun onBindViewHolder(holder: Adapter.ViewHolder, position: Int) {
holder.setData(arrayItems[position])
}
inner class ViewHolder(val binding: LayoutBinding,
val dragStartListener : OnStartDragListener? = null)
: RecyclerView.ViewHolder(binding.root) {
val tileLayout = binding.tileLayout
fun setData(data: Data?) {
....
tileLayout.setOnDragListener { view, dragEvent ->
when(dragEvent.action) {
ACTION_DRAG_STARTED -> {
dragStartListener?.onStartDrag(this)
true
}
else -> false
}
}
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
Collections.swap(arrayItems, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onItemDismiss(position: Int) {
TODO("Not yet implemented")
}
}
and the fragment which contain the rv, I have updated the adapter init:
list1adapter?.let { adapter ->
adapter.setData(list)
val callback: ItemTouchHelper.Callback = ReorderHelperCallback(adapter)
mItemTouchHelperSelected = ItemTouchHelper(callback)
mItemTouchHelperSelected?.attachToRecyclerView(selectedLayout)
}
---
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) {
viewHolder?.let {
mItemTouchHelperSelected?.startDrag(it)
}
}
But my fragment contain 2 recycler views. list1 is working fine to user drag and drop to re-order the item but now, I would like to also be able to move an item from my rv list1 to the list2 and vice versa
Any idea, how to make it Kotlin ? I tried an sample code, byt I am losing the re-ordering.
Thanks
I have found an issue when dragging an item that doesn't trigger a move on a GridLayoutManager with 2 columns.
When dragging one item from the first column to the top of the list (which scrolls the list upwards) and then back to its original position, the recyclerView creates an empty space next to the original position, pushing the item that was next to the dragged item to the next row.
I have coded the simplest app to show you what I mean. On the first screenshot I start dragging one item towards the top of the list. On the second screenshot I reach the top of the screen. Then I move it back to its position and you can already see how the items have not been laid down properly.
Has anyone encountered this as well? I have not found any solution to this issue.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myAdapter = MyAdapter()
with(findViewById<RecyclerView>(R.id.recyclerView)) {
adapter = myAdapter
layoutManager = GridLayoutManager(this#MainActivity, 2)
ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, DOWN or UP or START or END)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun canDropOver(recyclerView: RecyclerView, current: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
TODO("Not yet implemented")
}
}).attachToRecyclerView(this)
}
myAdapter.submitList(data)
}
data class ListItem(val id: String, val name: String)
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
class MyAdapter : ListAdapter<ListItem, MyViewHolder>(MyAdapterDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
class MyAdapterDiffCallback : DiffUtil.ItemCallback<ListItem>() {
override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ListItem, newItem: ListItem) = oldItem == newItem
}
}
I have created a image slider using ViewPager2, I need a functionality that when I swipe up the image should be removed. I already worked with RecyclerView. So I remember I can use ItemTouchHelper.SimpleCallback for swipe to remove functionality. But attachToRecyclerView method requires a RecyclerView not a ViewPager2 even though viewpager2 uses RecyclerView adapter.
Kotlin:
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}).attachToRecyclerView(binding.viewPager)
The RecyclerView of the ViewPager2 is not accessible by default, but you can enforce its accessibility using reflection.
In that you need to access the RecyclerView by reflecting its declared field name using getDeclaredField(), and for RecyclerView it is: mRecyclerView (you can check it in the ViewPager2 class)
Then use setAccessible() to make this field accessible in order to allow using it for the ItemTouchHelper.
Here is an extension function to return the ViewPager2 ReyclerView:
fun ViewPager2.getRecyclerView(): RecyclerView? {
try {
val field = ViewPager2::class.java.getDeclaredField("mRecyclerView")
field.isAccessible = true
return field.get(this) as RecyclerView
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
return null
}
And you can use it like:
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}).attachToRecyclerView(binding.viewPager.getRecyclerView())
Preview:
UPDATE:
Thanks to #SimpleAndroid answer, there is a nice way for obtaining the RecyclerView:
viewPager.children.find { it is RecyclerView }?.let {
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}).attachToRecyclerView(it as RecyclerView)
}
Here is another way to get RecyclerView from ViewPager2. I assume you have an adapter named is PictureItemAdapter:
class PictureItemAdapter(private val recyclerViewAttached: (RecyclerView) -> Unit) : RecyclerView.Adapter<PictureItemViewHolder>() {
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
recyclerViewAttached(recyclerView)
}
...
}
And you will receive the Recycler view from the recyclerViewAttached closure
val pictureItemAdapter = PictureItemAdapter{ recyclerView ->
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}).attachToRecyclerView(recyclerView)
}
binding.viewPager.adapter = pictureItemAdapter
Note: Both RecyclerViewAdapter and FragmentStateAdapter have a method onAttachedToRecyclerView.
I use RecyclerView's ItemTouchHelper to change the order(drag & drop),And I want to move only a part of the list.
I don't want to move after a certain index. How can I handle it with one RecyclerView?
If I explain a little more about what I want to make,
The active items are sorted up and the non-active items are sorted down. And only the activated items will shift the order.
Right now, when I drag an active item, it goes down to the area of the non-active item, but I want to prevent it from going down. How can I do it?
UPDATE
open class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) :
ItemTouchHelper.Callback() {
private var isLongPressDrag = true
fun setLongPressDragEnable(isLongPressDrag: Boolean) {
this.isLongPressDrag = isLongPressDrag
}
override fun isLongPressDragEnabled() = isLongPressDrag
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = 0
return makeMovementFlags(dragFlags, swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return mAdapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, i: Int) {
mAdapter.onItemDismiss(viewHolder.adapterPosition)
}
}
Use ItemTouchHelper.SimpleCallback and override getDragDirs and onMove methods:
ItemTouchHelper.SimpleCallback(
UP or DOWN,
0
) {
override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
val selectedPosition = viewHolder.adapterPosition
val isActive: Boolean = // retrieve your model from list and check its active state
return if (isActive) super.getDragDirs(recyclerView, viewHolder) else 0
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder,
): Boolean {
val toPosition = target.adapterPosition
val isActiveTarget: Boolean = // retrieve your target model from list and check its active state
if (!isActiveTarget) return false
val fromPosition = viewHolder.adapterPosition
moveItem(fromPosition, toPosition)
return true
}
}
Returning 0 on getDragDirs prevents inactive item selection, while returning false on onMove prevents item movement while dragging.
I've created drag and drop for my recycler view, but I want disable drag and drop option on pull of cells (it's a headers in my view). How to make them not available for drag and drop function?
Drag And drop helper
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
override fun isLongPressDragEnabled() = true
override fun isItemViewSwipeEnabled() = true
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return ItemTouchHelper.Callback.makeMovementFlags(dragFlags, swipeFlags)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
mAdapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
return true
}
}
Adapter for Recyclerview
interface ItemTouchHelperAdapter {
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
}
Code to pair adapter to rv
val drugAndDropHandler = SimpleItemTouchHelperCallback(adapter)
ItemTouchHelper(drugAndDropHandler).attachToRecyclerView(multiple_stores_list)
Just need to override getMovementFlags
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
if (viewHolder.adapterPosition in 0..NOT_DRAGABLE_ITEMS_MAX_POSITION) {
return 0
}
}