Ce que je veux réaliser: Avoir un RecyclerView avec GridLayoutManager qui prend en charge le glisser-déposer et qui réorganise les éléments tout en les faisant glisser.
Note latérale: La première fois que vous développez quelque chose avec glisser-déposer.
Il existe de nombreux sujets sur la réalisation de cette fonctionnalité à l'aide d'un ListView, par exemple: https://raw.githubusercontent.com/btownrippleman/FurthestProgress/master/FurthestProgress/src/com/anappforthat/Android/languagelineup /DynamicListView.Java
Cependant, les exemples utilisent généralement beaucoup de code, créant des bitmaps de la vue déplacée et il semble qu'il devrait être possible d'obtenir le même résultat avec View.startDrag(...)
et RecyclerView avec notifyItemAdded()
, notifyItemMoved()
et notifyItemRemoved()
car ils fournissent des animations de réorganisation.
Alors j'ai joué autour de certains et est venu avec ceci:
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);
Ce morceau de code fonctionne bien, mais je suis échangé, mais très instable/tremblant et parfois, quand il rafraîchit, la grille entière est réarrangée dans son ordre original ou aléatoire. Quoi qu'il en soit, le code ci-dessus n'est que ma première tentative rapide. Ce qui m'intéresse davantage, c'est de savoir s'il existe une méthode standard/la meilleure pratique permettant d'effectuer le glisser-déposer avec ReyclerView ou si la manière correcte de la résoudre reste la même. été utilisé pour ListViews depuis des années?
Il existe en fait un meilleur moyen d'y parvenir. Vous pouvez utiliser certaines des classes "compagnons" de RecyclerView
:
ItemTouchHelper
, qui est
une classe d’utilitaires à ajouter pour balayer et déplacer un support vers RecyclerView.
et son ItemTouchHelper.Callback
, qui est
le contrat entre ItemTouchHelper et votre 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);
}
};
Pour plus de détails, consultez leur documentation.
Voici ma solution avec la réorganisation de bases de données:
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);
Il existe une fonction de support utilisant 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();
}
Ici, j’ai fait un échantillon complet dans Kotlin ( ici ) et, si vous le souhaitez, vous pouvez activer le balayage. -à-rejeter sur elle. Voici le code complet de celui-ci:
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"/>
manifeste
<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)
}
}
Pour échanger deux vues dans une grille, essayez d’utiliser
_adapter.notifyDataSetChanged();
au lieu de
_adapter.notifyItemMoved(viewHolder.getAdapterPosition(),
target.getAdapterPosition());
Celui-ci m'a aidé