Lo que quiero lograr:
Tiene un RecyclerView con GridLayoutManager que soporta arrastrar y soltar y que reorganiza los elementos mientras se arrastra.

Lado nota: la Primera vez que el desarrollo de cualquier cosa con la función de arrastrar y soltar.

Hay un montón de temas sobre cómo lograr esta característica con un ListView, por ejemplo: https://raw.githubusercontent.com/btownrippleman/FurthestProgress/master/FurthestProgress/src/com/anappforthat/android/languagelineup/DynamicListView.java

Sin embargo, los ejemplos son generalmente un montón de código, creación de mapas de bits de la arrastró a la vista y se siente como debería ser posible obtener el mismo resultado mediante View.startDrag(...) y RecyclerView con notifyItemAdded(), notifyItemMoved() y notifyItemRemoved(), ya que proporcionan reorganizar las animaciones.

Lo he jugado algunos y me encontré con 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 fragmento de código de tipo de trabajo, me sale el intercambio, pero muy inestable inestable y a veces, cuando es refrescante toda la cuadrícula se reorganiza de nuevo a la orden original o algo al azar. De todos modos el código de arriba es solo mi primer intento, lo que realmente estoy más interesado en saber es si hay algún tipo de norma/mejor práctica manera de hacer la operación de arrastrar y soltar con ReyclerView o si es la forma correcta de resolver es el mismo que se ha utilizado para ListViews por años?

5 Comentarios

  1. 108

    De hecho, hay una mejor manera de lograr esto. Usted puede utilizar algunas de las RecyclerView‘s «compañero» de las clases:

    ItemTouchHelper, que es

    una clase de utilidad para agregar pase a despedir y drag & drop de apoyo a RecyclerView.

    y su ItemTouchHelper.Devolución de llamada, 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 puedes consultar su documentación.

    • Muy interesante, va a explorar esta solución el lunes, pero parece prometedor.
    • He actualizado el código, pero la idea original era dar una visión general del método y dejar los detalles para la documentación
    • Hasta votado. Esperemos que el OP se acepta ahora la respuesta (que es completo).
    • Además: puede deshabilitar arrastre para el elemento específico basado en la viewHolder en getMovementFlag – simplemente llame makeFlag con ‘0’ en lugar de la dirección a las banderas.
    • Este marcado como respuesta, no utilice el cable de código, pero el post me llevan a una solución que funciona. Gracias!
    • Puedo obtener la Gota de eventos con esto ? Yo necesarios para validar la posición en el evento Drop
    • la solución que @VishalKhakhkhar ? He añadido obligatorio onSwiped() método
    • Por el derecho de cambiar el orden: en lugar de sólo de la Colección.swap() que debe hacer: si (fromPosition < toPosition) { for (int i = fromPosition; i < toPosition; i++) { Colecciones.swap(gridItems, i, i + 1); } } else { for (int i = fromPosition; i > toPosition; i–) { Colecciones.swap(gridItems, i, i – 1); } }
    • tener un problema con el desplazamiento, cuando el usuario llega a la final, y es también el arrastre, de desplazamiento y arrastre se mezcla. Cualquier sugerencia
    • tan f ing fácil) no hay dolor de cabeza
    • Es allí una manera de iniciar el arrastre/mover en corto de prensa en lugar de pulsación larga?
    • Es allí cualquier manera de detener arrastrar y soltar para la posición específica?
    • Lo que escribí no es el intercambio entre los 2 elementos. En lugar de esto se mueve un elemento de un lugar a otro. Además, no hay ninguna necesidad de un bucle. Usted podría simplemente llame a val item = items.removeAt(fromPosition) items.add(toPosition, item) recyclerView.adapter!!.notifyItemMoved(fromPosition, toPosition)
    • No creo que hay una manera, al menos no oficial. Preguntado acerca de esto aquí: stackoverflow.com/q/57286355/878126
    • Trate de mi muestra aquí: github.com/AndroidDeveloperLB/RecyclerViewDragAndDropTest
    • Gracias he resuelto aquí stackoverflow.com/a/54216384/4079010 el uso de getMovementFlags método

  2. 16

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

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

    No es una de las funciones de apoyo tahat hacer 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();
    }
    • Si es posible, a continuación, por favor comparta su mAdapter.getCapitolos() método de código en su adaptador.
    • es sólo un ArrayList con los datos que se leen de la base de datos en el principio.
    • Su respuesta es impresionante! el si (fromPosition < toPosition) es muy importante para que esto funcione como debe..Gracias!
  3. 2

    Aquí, he hecho un total de la muestra en Kotlin (aquí), y, si lo desea, puede habilitar el pase a despedir de ella . Aquí está el código completo de la misma:

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

    manifiesto

    <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)
    }
    }
  4. 0

    A cambio dos puntos de vista en cuadrícula de diseño de tratar de usar

    _adapter.notifyDataSetChanged();

    lugar de

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

    Este me ayudó a

Dejar respuesta

Please enter your comment!
Please enter your name here