Search code examples
androidandroid-recyclerviewmergedrag-and-dropitemtouchhelper

MERGE ITEMS in Recycler View when dragged & dropped on one another, in ANDROID?


Using ItemTouchHelper class we can DRAG, DROP, & SWIPE items in the recycler view; but how to just merge two items when dragged & dropped on one another?

Is it possible to do using ItemTouchHelper (or) is there any other API for that?


Solution

  • You could override onMove() in your ItemTouchHelper, it is called when you drag item A over item B. It gets called with the parameters viewHolder: RecyclerView.ViewHolder and target: RecyclerView.ViewHolder where viewHolder is the viewHolder of item A, and target is item B.

    Have some variable of type ViewHolder, that you set to target in onMove, to always have a reference to the item below item A.

    override clearView() to detect when the item is dropped, update your model in the background, so itemA now is merged with target, then call notifyItemChanged(itemA.adapterPosition) and notifyItemRemoved(itemB.adapterPosition) to animate a "merge"

    class MainActivity : AppCompatActivity() {
    
        companion object{
            val fruit = arrayListOf("apple", "pear", "orange", "banana", "grape", "pineapple")
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val recyclerView1 = findViewById<RecyclerView>(R.id.testRecycler)
            val layoutManager = LinearLayoutManager(this)
            recyclerView1.layoutManager = layoutManager
    
            val adapter = FruitAdapter()
            recyclerView1.adapter = adapter
    
            val itemTouchHelper = ItemTouchHelper(
                object : ItemTouchHelper.SimpleCallback(
                    ItemTouchHelper.UP or ItemTouchHelper.DOWN,
                    0
                ) {
                    @SuppressLint("StaticFieldLeak")
                    var target: RecyclerView.ViewHolder? = null
                    @SuppressLint("StaticFieldLeak")
                    var moving: RecyclerView.ViewHolder? = null
                    override fun clearView(
                        recyclerView: RecyclerView,
                        viewHolder: RecyclerView.ViewHolder
                    ) {
                        if(target!=null && moving != null){
                            val targetPos = target!!.adapterPosition
                            val sourcePos = moving!!.adapterPosition
                            fruit[targetPos] += "\n\n" + fruit[sourcePos]
                            fruit.removeAt(sourcePos)
                            target = null
                            moving = null
                            adapter.notifyDataSetChanged()
                        }
                    }
    
                    override fun onMove(
                        recyclerView: RecyclerView,
                        viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder
                    ): Boolean {
                        this.target = target
                        this.moving = viewHolder
                        return true
                    }
    
                    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                        TODO("Not yet implemented")
                    }
                })
    
            itemTouchHelper.attachToRecyclerView(recyclerView1)
        }
    }
    
    class FruitAdapter: RecyclerView.Adapter<FruitAdapter.FruitViewHolder>() {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {
            val itemView = LayoutInflater.from(parent.context)
                .inflate(R.layout.item, parent, false)
            return FruitViewHolder(itemView)
        }
        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {
            holder.itemView.findViewById<TextView>(R.id.fruitNameTextView).text = MainActivity.fruit[position]
        }
    
        override fun getItemCount(): Int {
            return MainActivity.fruit.size
        }
    
        class FruitViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
    }
    

    item.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:padding="20px"
        android:layout_margin="20px"
        android:background="@color/teal_200"
        android:layout_height="wrap_content">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/fruitNameTextView"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:id="@+id/testRecycler"
            android:layout_height="match_parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>