Drag and drop items in RecyclerView with GridLayoutManager

后端 未结 4 490
终归单人心
终归单人心 2020-11-27 11:53

What I want to achieve: Have a RecyclerView with GridLayoutManager that supports drag\'n\'drop and that rearranges the items while dragging.

Side note: First time de

相关标签:
4条回答
  • 2020-11-27 12:32

    This is my solution with database reordering:

        ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                final int fromPosition = viewHolder.getAdapterPosition();
                final int toPosition = target.getAdapterPosition();
                if (fromPosition < toPosition) {
                    for (int i = fromPosition; i < toPosition; i++) {
                        Collections.swap(mAdapter.getCapitolos(), i, i + 1);
                    }
                } else {
                    for (int i = fromPosition; i > toPosition; i--) {
                        Collections.swap(mAdapter.getCapitolos(), i, i - 1);
                    }
                }
                mAdapter.notifyItemMoved(fromPosition, toPosition);
                return true;
            }
    
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
                MyViewHolder svH = (MyViewHolder ) viewHolder;
                int index = mAdapter.getCapitolos().indexOf(svH.currentItem);
                mAdapter.getCapitolos().remove(svH.currentItem);
                mAdapter.notifyItemRemoved(index);
                if (emptyView != null) {
                    if (mAdapter.getCapitolos().size() > 0) {
                    emptyView.setVisibility(TextView.GONE);
                    } else {
                    emptyView.setVisibility(TextView.VISIBLE);
                    }
                }
            }
    
            @Override
            public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                super.clearView(recyclerView, viewHolder);
                reorderData();
            }
        };
    
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        itemTouchHelper.attachToRecyclerView(recList);
    

    There is a support functions tahat make use of AsyncTask:

    private void reorderData() {
        AsyncTask<String, Void, Spanned> task = new AsyncTask<String, Void, Spanned>() {
            @Override
            protected Spanned doInBackground(String... strings) {
                dbService.deleteAllData();
                for (int i = mAdapter.getCapitolos().size() - 1; i >= 0; i--) {
                    Segnalibro s = mAdapter.getCapitolos().get(i);
                    dbService.saveData(s.getIdCapitolo(), s.getVersetto());
                }
                return null;
            }
    
            @Override
            protected void onPostExecute(Spanned spanned) {
            }
        };
        task.execute();
    }
    
    0 讨论(0)
  • 2020-11-27 12:38

    There is actually a better way to achieve this. You can use some of the RecyclerView's "companion" classes:

    ItemTouchHelper, which is

    a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

    and its ItemTouchHelper.Callback, which is

    the contract between ItemTouchHelper and your application

    // Create an `ItemTouchHelper` and attach it to the `RecyclerView`
    ItemTouchHelper ith = new ItemTouchHelper(_ithCallback);
    ith.attachToRecyclerView(rv);
    
    // Extend the Callback class
    ItemTouchHelper.Callback _ithCallback = new ItemTouchHelper.Callback() {
        //and in your imlpementaion of
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            // get the viewHolder's and target's positions in your adapter data, swap them
            Collections.swap(/*RecyclerView.Adapter's data collection*/, viewHolder.getAdapterPosition(), target.getAdapterPosition());
            // and notify the adapter that its dataset has changed
            _adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
            return true;
        }
    
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            //TODO    
        }
    
        //defines the enabled move directions in each state (idle, swiping, dragging). 
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
                    ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.START | ItemTouchHelper.END);
        }
    };
    

    For more details check their documentation.

    0 讨论(0)
  • 2020-11-27 12:41

    Here, I've made a full sample in Kotlin (here), and, if you wish, you can enable swipe-to-dismiss on it . Here's the entire code of it:

    build.gradle

    implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
    implementation 'androidx.core:core-ktx:1.2.0-alpha02'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
    implementation 'com.google.android.material:material:1.1.0-alpha08'
    implementation 'androidx.recyclerview:recyclerview:1.1.0-beta01'
    

    grid_item.xml

    <TextView
        android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent" android:layout_height="100dp" android:gravity="center"/>
    

    activity_main.xml

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView" tools:listitem="@layout/grid_item"  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:orientation="vertical" app:spanCount="3" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
    

    manifest

    <manifest package="com.sample.recyclerviewdraganddroptest" xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools">
    
        <application
            android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
            android:theme="@style/AppTheme.NoActionBar" tools:ignore="AllowBackup,GoogleAppIndexingWarning">
            <activity
                android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
    
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val items = ArrayList<Int>(100)
            for (i in 0 until 100)
                items.add(i)
            recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                    return object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.grid_item, parent, false)) {}
                }
    
                override fun getItemCount() = items.size
    
                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                    val data = items[position]
                    holder.itemView.setBackgroundColor(if (data % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                    holder.itemView.textView.text = "item $data"
                }
            }
            val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
                override fun isLongPressDragEnabled() = true
                override fun isItemViewSwipeEnabled() = false
    
                override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
                    val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
                    val swipeFlags = if (isItemViewSwipeEnabled) ItemTouchHelper.START or ItemTouchHelper.END else 0
                    return makeMovementFlags(dragFlags, swipeFlags)
                }
    
                override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                    if (viewHolder.itemViewType != target.itemViewType)
                        return false
                    val fromPosition = viewHolder.adapterPosition
                    val toPosition = target.adapterPosition
                    val item = items.removeAt(fromPosition)
                    items.add(toPosition, item)
                    recyclerView.adapter!!.notifyItemMoved(fromPosition, toPosition)
                    return true
                }
    
                override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                    val position = viewHolder.adapterPosition
                    items.remove(position)
                    recyclerView.adapter!!.notifyItemRemoved(position)
                }
    
            })
            itemTouchHelper.attachToRecyclerView(recyclerView)
        }
    
    }
    
    0 讨论(0)
  • 2020-11-27 12:41

    I found Advancedrecyclerview quite useful. Have a look!!!

    0 讨论(0)
提交回复
热议问题