Qu'est-ce que developer.android.com ne dit rien sur RecyclerView?

La question du cycle de vie d'une application Android ou d'un fragment d'une application Android est extrêmement importante pour un Android pratiquant (développeur Android). Pourquoi? Parce que l'ordre d'exécution des rappels de toutes les méthodes liées à l'état du cycle de vie ( onCreate () , onStart () , etc.) est codé en dur et son application incorrecte entraînera l'inopérabilité de l'application. Qu'est-ce que le cycle de vie a à voir avec cela? - demandera l'habretchit attentif. Après tout, le titre, semble-t-il, ne parle pas de lui? Je réponds: il y a quelque chose en commun entre le cycle de vie de l'activité et le travail de RecyclerView - il y a un ORDRE DUR d' exécution des méthodes de rappel lors de l'utilisation de ce widget, et, par conséquent, la nécessité de L' APPLIQUER CORRECTEMENT .

Si cela n'est pas fait, les listes peuvent se comporter de manière très mystérieuse.

Adaptateur minimum pour RecyclerView


Par exemple. Il existe un tel adaptateur de liste avec un rembourrage minimum standard:

Listing 1


public class RvCustomAdapter extends RecyclerView.Adapter<RvCustomAdapter.CustomViewHolder> { private final Frag1 frag; private final LayoutInflater lInflater; private ArrayList<JSONDataSet> dataSet; ... ... ... public RvCustomAdapter(final Frag1 fragment) { this.frag = fragment; this.lInflater = (LayoutInflater) fragment.getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.dataSet = new ArrayList<>(); } ... ... ... @Override public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //    View view = lInflater.inflate(R.layout.recycler_view_data_item, parent, false); /** *      *  (size, margins, paddings  .) */ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); params.height = RecyclerView.LayoutParams.WRAP_CONTENT; view.setLayoutParams(params); return new CustomViewHolder(view); } //    view ( layout manager-) @Override public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) { holder.showData(position); } @Override public int getItemCount() { return dataSet.size(); } /** *  view holder-      *     *        */ class CustomViewHolder extends RecyclerView.ViewHolder { ... ... ... @BindView(R.id.ll_Data) LinearLayout ll_Data; @BindView(R.id.cb_Data) CheckBox cb_Data; ... ... ... private JSONDataSet cur; CustomViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } /** * ,      *       . */ ... ... ... } 

Dans le code de la méthode onBindViewHolder () de l'adaptateur de notre liste, dont les éléments contiennent une case à cocher ( CheckBox ), il existe un appel à la méthode du gestionnaire ( holder 'a), dans laquelle les données sont lues à partir de la collection connectée à l'adaptateur et en fonction de celle-ci est définie - case à cocher - l'état, ainsi que les éléments nécessaires ( Listener ) sont connectés aux différents éléments de l'interface:

Listing 2


  void showData(final int position) { cur = dataSet.get(position); cb_Data.setChecked(cur.isChecked()); ... ... ... cb_Data.setOnCheckedChangeListener(cb_DataOnCheckedChangeListener); ll_Data.setOnClickListener(ll_DataOnClickListener); } private OnClickListener ll_DataOnClickListener = new OnClickListener() { @Override public void onClick(View view) { cur.setChecked(!cur.isChecked()); cb_Data.setChecked(cur.isChecked()); } }; private OnCheckedChangeListener cb_DataOnCheckedChangeListener = new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { cur.setChecked(checked); compoundButton.setChecked(checked); setItemsColor(checked); if (checked) { if (...) { (frag).addSelectedItemsCounter(cur); } else { cur.setChecked(!checked); compoundButton.setChecked(!checked); setItemsColor(!checked); if (...) { createPrimaryDialog(); } else { createSecondaryDialog(); } } } else { (frag).remSelectedItemsCounter(cur); } } }; 

Lorsque l'indicateur est défini et qu'une certaine condition est remplie, les écouteurs modifient les données de la collection et lorsqu'elles ne sont pas exécutées, elles affichent l'une ou l'autre boîte de dialogue.

Il s'avère quelque chose comme ceci:


Dans Fig-1 - la liste générée. Dans Fig-2 - L'élément de liste marqué. Dans Fig-3 - un dialogue informant d'une violation de la condition lors du marquage de l'élément suivant.

Pour obtenir le résultat avec le gestionnaire de disposition de liste de la figure 1 ( LayoutManager ), l'ordre suivant d'appeler les fonctions nécessaires est effectué:

Algorithme 1


  1. Rv_Adapter.getItemCount () - vérifie le nombre d'éléments dans la collection;
  2. Rv_Adapter.onAttachedToRecyclerView () - l'adaptateur se connecte au widget;
  3. Bien que l'espace de liste ne soit pas rempli avec les éléments de liste, les étapes suivantes de l'algorithme 2 sont effectuées pour la liste:

Algorithme 2


  1. Rv_Adapter.onCreateViewHolder () - pour chaque élément de la collection, son propre gestionnaire est créé;
  2. CustomViewHolder () - le constructeur du gestionnaire est exécuté;
  3. Rv_Adapter.onBindViewHolder () - pour chaque instance, le générateur de vues est démarré;
  4. Rv_Adapter.onViewAttachedToWindow () - la vue générée est connectée à la fenêtre;

Tout est super! Sinon pour "Mais". Au contraire, MAIS!

Le problème


Lorsque vous faites défiler une longue liste contenant au moins une douzaine d'éléments, nous recevrons un message de la figure 3 sans aucune autre action.

Dépannage


La raison en est que lors de l'écriture du code de l'adaptateur, NOUS N'AVONS PAS PRIS EN COMPTE L'ORDRE DE PERFORMANCE DES FONCTIONS DU TABLEAU DE TABLEAUX énumérées ici et ici lors du défilement. Et il est comme ça:

Algorithme 3


  1. Lorsque vous masquez à l'étranger la visibilité de chaque élément de liste pour l'instance associée du gestionnaire, la méthode Rv_Adapter.onViewDetachedFromWindow () est exécutée , ce qui déconnecte la vue masquée de la fenêtre;
  2. Lorsque chaque nouvel élément de la liste ( itemView ) apparaît de l'extérieur du champ de visibilité, l'algorithme 2 est exécuté pour l'instance de gestionnaire associée;

Mais ce n'est pas tout. Avec les paramètres «par défaut» du gestionnaire de balisage, chaque élément de liste déconnecté de la fenêtre ne reste pas longtemps dans la file d'attente pour un accès rapide. Dès qu'il y en a 2, ils sont déplacés par le gestionnaire dans la file d'attente des instances supprimées, qui est marquée par un appel à la méthode Rv_Adapter.onViewRecycled () pour chaque élément de liste utilisé et vice versa.

Par conséquent, l'algorithme 3 ressemble en fait à ceci:

Algorithme 3 '


 //   :  - true,  - false: bool direction; if(direction){ /** *          *      * (  directDetachedViews) */ Rv_Adapter.onViewDetachedFromWindow(holder); /** *        *   ,  max */ if(directDetachedViews.size() > max) { /** *       (holder) *      *      * (  directRecycleredHolders) */ Rv_Adapter.onViewRecycled(holder); } /** *     * (visiblePos)   ,  */ if(visiblePos < Rv_Adapter.getItemCount()) { /** *       *    (  reverseDetachedViews) *   (itemView),   *   (  visiblePos), */ if(reverseDetachedViews.content(itemView)){ /** *        *  (  reverseRecycleredHolders) *   holder,    *  ,  visiblePos,    */ Rv_Adapter.onCreateViewHolder(itemView) -> { holder = CustomViewHolder(itemView); }; } else { /** *  -       *   (reverseRecycleredHolders) */ holder = reverseRecycleredHolders.getHolder(visiblePos); } /** *        *   */ Rv_Adapter.onBindViewHolder(holder, visiblePos); } else { /** *  -      *     (reverseDetachedViews) */ holder = reverseDetachedViews.getHolder(visiblePos) } //     Rv_Adapter.onViewAttachedToWindow(holder); } else { ... ... ... ... ... } 


D'après l' algorithme 3 ci-dessus , on voit que si vous faites défiler la liste de plus de max, le nombre de positions d'affichage dans celle-ci sera à nouveau créé, pour lequel la méthode Rv_Adapter.onBindViewHolder (holder, visiblePos) sera utilisée, ce qui répétera les actions de l'utilisateur.

Conclusion et recommandation


Afin d'éviter de répéter les opérations dans la méthode onBindViewHolder (holder, visiblePos) lors du défilement de la liste par le nombre de positions supérieur à max est nécessaire:

  1. Complétez les éléments de collection avec un champ avec un signe d'éviction de la vue associée dans la file d'attente des gestionnaires utilisés, par exemple, bool recyclé ;
  2. Insérez des instructions sur la façon de définir cet indicateur dans la méthode onViewRecycled (support) , par exemple .... setRecycled (true) ;
  3. Insérez dans la méthode onBindViewHolder (holder, visiblePos) une vérification de ce signe, par exemple if (! Handler.cur.isRecycled ()) ...;
  4. Insérez dans la méthode onViewAttachedToWindow (support) les instructions pour supprimer ce symptôme, par exemple .... setRecycled (false) ;

Par exemple , comme ceci:

Listing 3


  @Override public void onViewRecycled(@NonNull CustomViewHolder holder) { super.onViewRecycled(holder); holder.cur.setRecycled(true); } @Override public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) { if (!holder.cur.isRecycled()){ ... ... ... } } @Override public void onViewAttachedToWindow(@NonNull CustomViewHolder holder) { super.onViewAttachedToWindow(holder); holder.cur.setRecycled(false); } 

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


All Articles