recyclerview recycler item example drop and android drag-and-drop android-recyclerview gridlayoutmanager

android - example - recycler itemtouchhelper



Arrastre y suelte elementos en RecyclerView con GridLayoutManager (5)

Lo que quiero lograr: tener un RecyclerView con GridLayoutManager que admita arrastrar y soltar y que reorganice los elementos mientras arrastra.

Nota al margen: primera vez que desarrolla algo con arrastrar y soltar.

Hay muchos temas sobre cómo lograr esta función usando ListView, por ejemplo: https://raw.githubusercontent.com/btownrippleman/FurthestProgress/master/FurthestProgress/src/com/anappforthat/android/languagelineup/DynamicListView.java

Sin embargo, los ejemplos suelen tener mucho código, creando mapas de bits de la vista arrastrada y parece que debería ser posible lograr el mismo resultado usando View.startDrag(...) y RecyclerView con notifyItemAdded() , notifyItemMoved() y notifyItemRemoved() ya que proporcionan animaciones de reorganización.

Así que jugué con algunos y se me ocurrió esto:

final CardAdapter adapter = new CardAdapter(list); adapter.setHasStableIds(true); adapter.setListener(new CardAdapter.OnLongClickListener() { @Override public void onLongClick(View view) { ClipData data = ClipData.newPlainText("",""); View.DragShadowBuilder builder = new View.DragShadowBuilder(view); final int pos = mRecyclerView.getChildAdapterPosition(view); final Goal item = list.remove(pos); mRecyclerView.setOnDragListener(new View.OnDragListener() { int prevPos = pos; @Override public boolean onDrag(View view, DragEvent dragEvent) { final int action = dragEvent.getAction(); switch(action) { case DragEvent.ACTION_DRAG_LOCATION: View onTopOf = mRecyclerView.findChildViewUnder(dragEvent.getX(), dragEvent.getY()); int i = mRecyclerView.getChildAdapterPosition(onTopOf); list.add(i, list.remove(prevPos)); adapter.notifyItemMoved(prevPos, i); prevPos = i; break; case DragEvent.ACTION_DROP: View underView = mRecyclerView.findChildViewUnder(dragEvent.getX(), dragEvent.getY()); int underPos = mRecyclerView.getChildAdapterPosition(underView); list.add(underPos, item); adapter.notifyItemInserted(underPos); adapter.notifyDataSetChanged(); break; } return true; } }); view.startDrag(data, builder, view, 0); } }); mRecyclerView.setAdapter(adapter);

Este tipo de código de trabajo, obtengo el intercambio, pero muy inestable / tembloroso y, a veces, cuando se actualiza, toda la cuadrícula se reorganiza al orden original o algo aleatorio. De todos modos, el código anterior es solo mi primer intento rápido, lo que realmente me interesa más saber es si hay alguna forma estándar / mejor práctica de arrastrar y soltar con ReyclerView o si la forma correcta de resolverlo sigue siendo la misma. ha sido utilizado para ListViews durante años?


Aquí, hice una muestra completa en Kotlin ( here ), y, si lo desea, puede habilitar el deslizamiento para descartarlo. Aquí está el código completo:

build.gradle

_adapter.notifyDataSetChanged();

grid_item.xml

_adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());

activity_main.xml

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''

manifiesto

<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"/>

MainActivity.kt

<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"/>


En realidad, hay una mejor manera de lograr esto. Puede usar algunas de las clases "complementarias" de RecyclerView :

ItemTouchHelper , que es

una clase de utilidad para agregar deslizar para descartar y arrastrar y soltar el soporte para RecyclerView.

y su ItemTouchHelper.Callback , que es

El contrato entre ItemTouchHelper y su aplicación

// 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); } };

Para más detalles consulte su documentación.



Esta es mi solución con el reordenamiento de la base de datos:

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);

Hay funciones de soporte que hacen uso de 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(); }


Para intercambiar dos vistas en el diseño de cuadrícula, intente usar

<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>

en lugar de

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) } }

Este me ayudo