Faites glisser et faites glisser dans RecyclerView. Partie 2: glisser-déposer des contrôleurs, des grilles et des animations personnalisées

Faites glisser et faites glisser dans RecyclerView. Partie 2: glisser-déposer des contrôleurs, des grilles et des animations personnalisées


Dans la première partie, nous avons examiné ItemTouchHelper et l'implémentation de ItemTouchHelper.Callback , qui ajoute les fonctions de base de glisser-déposer et de glisser- déplacer pour ignorer RecyclerView . Dans cet article, nous continuerons ce qui a été fait dans le précédent en ajoutant la prise en charge de l'organisation des éléments sous forme de grille, des contrôleurs de glisser-déposer, la sélection d'un élément de liste et des animations de balayage personnalisées.


Maillage d'éléments


Contrôleurs de glisser-déposer


Lors de la création d'une liste qui prend en charge le glisser-déposer , ils implémentent généralement la possibilité de glisser-déposer des éléments au toucher. Cela contribue à la clarté et à la facilité d'utilisation de la liste en "mode édition", et est également recommandé par les directives matérielles. Ajouter des contrôleurs de glisser-déposer à notre exemple est incroyablement facile.


Contrôleurs de glisser-déposer


Tout d'abord, mettez à jour la layout élément ( item_main.xml ).


 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item" android:layout_width="match_parent" android:layout_height="?listPreferredItemHeight" android:clickable="true" android:focusable="true" android:foreground="?selectableItemBackground"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="16dp" android:textAppearance="?android:attr/textAppearanceMedium" /> <ImageView android:id="@+id/handle" android:layout_width="?listPreferredItemHeight" android:layout_height="match_parent" android:layout_gravity="center_vertical|right" android:scaleType="center" android:src="@drawable/ic_reorder_grey_500_24dp" /> </FrameLayout> 

L'image utilisée pour le contrôleur de glissement peut être trouvée dans les icônes Material Design et ajoutée au projet à l'aide du plugin générateur d'icônes pratique dans Android Studio.


Comme brièvement mentionné dans un article précédent, vous pouvez utiliser ItemTouchHelper.startDrag(ViewHolder) pour démarrer par programme un glisser-déposer. Donc, tout ce que nous devons faire est de mettre à jour le ViewHolder en ajoutant un contrôleur de glisser et configurer un gestionnaire tactile simple qui appellera startDrag() .


Nous aurons besoin d'une interface pour transmettre les événements le long de la chaîne:


 public interface OnStartDragListener { /** * Called when a view is requesting a start of a drag. * * @param viewHolder The holder of the view to drag. */ void onStartDrag(RecyclerView.ViewHolder viewHolder); } 

Définissez ensuite une ImageView pour le contrôleur de glissement dans le ItemViewHolder :


 public final ImageView handleView; public ItemViewHolder(View itemView) { super(itemView); // ... handleView = (ImageView) itemView.findViewById(R.id.handle); } 

et mettez à jour RecyclerListAdapter :


 private final OnStartDragListener mDragStartListener; public RecyclerListAdapter(OnStartDragListener dragStartListener) { mDragStartListener = dragStartListener; // ... } @Override public void onBindViewHolder(final ItemViewHolder holder, int position) { // ... holder.handleView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { mDragStartListener.onStartDrag(holder); } return false; } }); } 

RecyclerListAdapter devrait maintenant ressembler à ceci .


Tout ce qui reste à faire est d'ajouter le OnStartDragListener au fragment:


 public class RecyclerListFragment extends Fragment implements OnStartDragListener { // ... @Override public void onViewCreated(View view, Bundle icicle) { super.onViewCreated(view, icicle); RecyclerListAdapter a = new RecyclerListAdapter(this); // ... } @Override public void onStartDrag(RecyclerView.ViewHolder viewHolder) { mItemTouchHelper.startDrag(viewHolder); } } 

RecyclerListFragment devrait maintenant ressembler à ceci . Maintenant, lorsque vous lancez l'application, vous pouvez commencer à glisser-déposer en touchant le contrôleur.


Déplacement des éléments de liste


Mettre en surbrillance un élément de liste


Maintenant, dans notre exemple, il n'y a aucune indication visuelle de l'élément que vous faites glisser. Évidemment, cela ne devrait pas être le cas, mais c'est facile à corriger. À l'aide d' ItemTouchHelper vous pouvez utiliser des effets de surbrillance d'élément standard. Sur Lollipop et les versions ultérieures d'Android, le rétro-éclairage "se propage" sur l'élément dans le processus d'interaction avec lui; sur les versions antérieures, l'élément change simplement de couleur pour s'assombrir.


Pour implémenter cela dans notre exemple, ajoutez simplement un arrière-plan (propriété background ) à FrameLayout racine FrameLayout élément FrameLayout ou définissez-le dans le constructeur de RecyclerListAdapter.ItemViewHolder . Cela ressemblera à ceci:


Sélection des éléments


Ça a l'air cool, mais vous voudrez peut-être contrôler encore plus. Une façon de procéder consiste à autoriser ViewHolder gérer les changements d'état d'un élément. Pour cela, ItemTouchHelper.Callback propose deux autres méthodes:


  • onSelectedChanged(ViewHolder, int) est appelé à chaque fois que l'état d'un élément change pour glisser ( ACTION_STATE_DRAG ) ou glisser ( ACTION_STATE_SWIPE ). C'est un endroit idéal pour changer l'état du composant de view en actif .
  • clearView(RecyclerView, ViewHolder) est appelé lorsque le composant de view glisser-déplacer est terminé, ainsi que lorsque le balayage est terminé ( ACTION_STATE_IDLE ). Ici, l'état initial de votre composant de view est généralement restauré.

Maintenant, mettons tout cela ensemble.


Tout d'abord, créez une interface que ViewHolders implémentera:


 /** * Notifies a View Holder of relevant callbacks from * {@link ItemTouchHelper.Callback}. */ public interface ItemTouchHelperViewHolder { /** * Called when the {@link ItemTouchHelper} first registers an * item as being moved or swiped. * Implementations should update the item view to indicate * it's active state. */ void onItemSelected(); /** * Called when the {@link ItemTouchHelper} has completed the * move or swipe, and the active item state should be cleared. */ void onItemClear(); } 

Ensuite, dans SimpleItemTouchHelperCallback implémentez les méthodes appropriées:


 @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { // We only want the active item if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemSelected(); } } super.onSelectedChanged(viewHolder, actionState); } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemClear(); } } 

Maintenant, tout ce qui reste à faire est que RecyclerListAdapter.ItemViewHolder implémente ItemTouchHelperViewHolder :


 public class ItemViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder { // ... @Override public void onItemSelected() { itemView.setBackgroundColor(Color.LTGRAY); } @Override public void onItemClear() { itemView.setBackgroundColor(0); } } 

Dans cet exemple, nous ajoutons simplement un arrière-plan gris pendant que l'élément est actif, puis le supprimons. Si votre ItemTouchHelper et votre adaptateur sont étroitement connectés, vous pouvez facilement abandonner ce paramètre et changer l'état du composant de view directement dans ItemTouchHelper.Callback .


Filets


Si maintenant vous essayez d'utiliser le GridLayoutManager , vous verrez qu'il ne fonctionne pas correctement. La raison et la solution sont simples: nous devons dire à notre ItemTouchHelper que nous voulons prendre en charge le déplacement des éléments vers la gauche et la droite. Plus tôt dans SimpleItemTouchHelperCallback nous avons déjà spécifié:


 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; return makeMovementFlags(dragFlags, swipeFlags); } 

La seule modification nécessaire pour prendre en charge les grilles consiste à ajouter les indicateurs appropriés:


 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 

Cependant, le glissement vers la suppression n'est pas un comportement très naturel pour les éléments de grille, donc les swipeFlags plus susceptibles d'être swipeFlags :


 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; int swipeFlags = 0; return makeMovementFlags(dragFlags, swipeFlags); } 

Pour voir un exemple de GridLayoutManager fonctionnel, voir RecyclerGridFragment . Voici à quoi cela ressemble au démarrage:


Éléments de la grille


Animations de balayage personnalisées


ItemTouchHelper.Callback fournit un moyen très pratique de contrôler entièrement l'animation tout en faisant glisser ou en balayant. Puisque ItemTouchHelper est un RecyclerView.ItemDecoration , nous pouvons intervenir dans le processus de rendu d'un composant de view d'une manière similaire. Dans la partie suivante, nous examinerons cette question plus en détail, mais pour l'instant, regardons un exemple simple de remplacement de l'animation de balayage par défaut pour montrer une disparition linéaire.


 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { float width = (float) viewHolder.itemView.getWidth(); float alpha = 1.0f - Math.abs(dX) / width; viewHolder.itemView.setAlpha(alpha); viewHolder.itemView.setTranslationX(dX); } else { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } 

Les dY dX et dY sont le décalage actuel par rapport au composant de view sélectionné, où:


  • -1.0f est un balayage complet de droite à gauche (de ItemTouchHelper.END à ItemTouchHelper.START )
  • 1.0f est un balayage complet de gauche à droite (de ItemTouchHelper.START à ItemTouchHelper.END )

Il est important d'appeler super pour toute actionState que vous ne traitez pas afin de déclencher l'animation par défaut.


Dans la partie suivante, nous considérerons un exemple dans lequel nous contrôlerons le dessin d'un élément au moment du glisser-déposer.


Conclusion


En fait, configurer ItemTouchHelper est assez amusant. Afin de ne pas augmenter le volume de cet article, je l'ai divisé en plusieurs.


Code source


Voir le code complet de cette série d'articles sur les référentiels GitHub Android-ItemTouchHelper-Demo . Cet article couvre les validations de ef8f149 à d164fba .


← Faites glisser et faites glisser dans RecyclerView. Partie 1: ItemTouchHelper

Source: https://habr.com/ru/post/fr428419/


All Articles