Was sagt developer.android.com über RecyclerView?

Die Frage nach dem Lebenszyklus einer Android-Anwendung oder nach einem Fragment einer Android-Anwendung ist für einen praktizierenden Android (Android-Entwickler) äußerst wichtig. Warum? Da die Ausführungsreihenfolge von Rückrufen aller Methoden, die sich auf den Status des Lebenszyklus beziehen ( onCreate () , onStart () usw.), fest codiert ist und die falsche Anwendung zur Inoperabilität der Anwendung führt. Was hat der Lebenszyklus damit zu tun? - Der aufmerksame Habretchit wird fragen. Immerhin geht es im Titel anscheinend nicht um ihn? Ich antworte: Es gibt etwas Gemeinsamkeiten zwischen dem Lebenszyklus der Aktivität und der Arbeit von RecyclerView - es gibt eine HARTBESTELLUNG für die Ausführung von Rückrufmethoden, wenn dieses Widget verwendet wird, und daher die Notwendigkeit, es korrekt anzuwenden .

Wenn dies nicht getan wird, können sich Listen auf sehr mysteriöse Weise verhalten.

Mindestadapter für RecyclerView


Zum Beispiel. Es gibt einen solchen Listenadapter mit Standard- Mindestauffüllung :

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

Im Code der onBindViewHolder () -Methode des Adapters unserer Liste, dessen Elemente ein Kontrollkästchen ( CheckBox ) enthalten, wird die Methode des Inhabers ( Inhaber 'a) angesprochen , bei der Daten aus der mit dem Adapter verbundenen Sammlung gelesen werden und basierend darauf das Kontrollkästchen gesetzt wird - state sowie die notwendigen Elemente ( Listener ) sind mit verschiedenen Elementen der Schnittstelle verbunden:

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

Wenn das Flag gesetzt ist und eine bestimmte Bedingung erfüllt ist, ändern Listener die Daten in der Sammlung, und wenn sie nicht ausgeführt werden, zeigen sie entweder das eine oder das andere Dialogfeld an.

Es stellt sich so etwas heraus:


In Abb. 1 - die generierte Liste. In Abb. 2 - Das markierte Listenelement. In Abb. 3 - Ein Dialog, der über eine Verletzung der Bedingung beim Markieren des nächsten Elements informiert.

Um das Ergebnis mit dem Listenlayout-Manager in Abbildung 1 ( LayoutManager ) zu erhalten, wird die folgende Reihenfolge zum Aufrufen der erforderlichen Funktionen ausgeführt:

Algorithmus 1


  1. Rv_Adapter.getItemCount () - Überprüft die Anzahl der Elemente in der Auflistung.
  2. Rv_Adapter.onAttachedToRecyclerView () - Der Adapter stellt eine Verbindung zum Widget her.
  3. Während der Listenbereich nicht mit den Listenelementen gefüllt ist, werden die folgenden Schritte von Algorithmus 2 für die Liste ausgeführt:

Algorithmus 2


  1. Rv_Adapter.onCreateViewHolder () - Für jedes Element der Auflistung wird ein eigener Handler erstellt.
  2. CustomViewHolder () - Der Konstruktor des Handlers wird ausgeführt.
  3. Rv_Adapter.onBindViewHolder () - Für jede Instanz wird der View Builder gestartet.
  4. Rv_Adapter.onViewAttachedToWindow () - Die generierte Ansicht ist mit dem Fenster verbunden.

Alles ist ganz toll! Wenn nicht für "Aber". Eher ABER!

Das Problem


Wenn Sie durch eine lange Liste mit mindestens ein paar Dutzend Elementen scrollen, erhalten Sie eine Nachricht aus Abbildung 3 ohne weitere Aktionen.

Fehlerbehebung


Der Grund dafür ist, dass wir beim Schreiben des Adaptercodes beim Scrollen NICHT DIE LEISTUNGSBESTELLUNG DER hier und hier aufgeführten CALLBACK- FUNKTIONEN berücksichtigt haben . Und er ist so:

Algorithmus 3


  1. Wenn im Ausland die Sichtbarkeit jedes Listenelements für die zugeordnete Instanz des Handlers ausgeblendet wird, wird die Methode Rv_Adapter.onViewDetachedFromWindow () ausgeführt , mit der die ausgeblendete Ansicht vom Fenster getrennt wird.
  2. Wenn jedes neue Element der Liste ( itemView ) außerhalb des Sichtbarkeitsbereichs angezeigt wird , wird Algorithmus 2 für die zugeordnete Handlerinstanz ausgeführt.

Das ist aber noch nicht alles. Mit den Standardeinstellungen des Markup-Managers verbleibt jedes vom Fenster getrennte Listenelement nicht lange in der Warteschlange, um schnell darauf zugreifen zu können. Sobald zwei davon vorhanden sind, werden sie vom Manager in die Warteschlange der bereitgestellten Instanzen verschoben, die durch einen Aufruf der Methode Rv_Adapter.onViewRecycled () für jedes verwendete Listenelement gekennzeichnet ist und umgekehrt.

Daher sieht Algorithmus 3 tatsächlich so aus:

Algorithmus 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 { ... ... ... ... ... } 


Aus dem obigen Algorithmus 3 ' ist ersichtlich, dass, wenn Sie mehr als maximal durch die Liste scrollen, die Anzahl der Ansichtspositionen darin neu erstellt wird, für die die Methode Rv_Adapter.onBindViewHolder (Inhaber, VisiblePos) verwendet wird, die die Aktionen des Benutzers wiederholt.

Schlussfolgerung und Empfehlung


Um zu vermeiden, dass sich Operationen in der Methode onBindViewHolder (Inhaber, VisiblePos) wiederholen , wenn die Liste um die Anzahl der Positionen größer als max gescrollt wird, ist Folgendes erforderlich:

  1. Ergänzen Sie die Sammlungselemente durch ein Feld mit dem Zeichen, dass die zugehörige Ansicht in die Warteschlange der verwendeten Handler verdrängt wird, z. B. bool recycelt .
  2. Fügen Sie Anweisungen zum Setzen dieses Flags in die Methode onViewRecycled (Inhaber) ein , z. B. .... setRecycled (true) ;
  3. Fügen Sie in die Methode onBindViewHolder (Inhaber, visiblePos) eine Überprüfung dieses Zeichens ein, z. B. if (! Handler.cur.isRecycled ()) ...;
  4. Fügen Sie in die onViewAttachedToWindow (Inhaber) -Methode Anweisungen zum Entfernen dieses Symptoms ein, zum Beispiel .... setRecycled (false) ;

Zum Beispiel so:

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


All Articles