📜 ⬆️ ⬇️

What does developer.android.com say about RecyclerView?

The question of the life cycle of an activity ( activity ) or a fragment ( fragment ) of an android application is extremely important for a practicing android player (android developer). Why? Because the order of executing callbacks of all methods related to the state of the life cycle ( onCreate () , onStart () , etc.) is rigidly defined and its incorrect use will lead to the application being disabled. What does the life cycle? - asks attentive habrochityvatel. After all, the title, it seems, is not about him? The answer is: there is something in common between the life cycle of the activity and the operation of the RecyclerView - it is the HARD ORDER of executing callback methods when using this widget, and, therefore, the need to IT IS RIGHT .

If this is not done, the lists can behave in a very mysterious way.

Minimum Adapter for RecyclerView


For example. There is such a list adapter with a standard minimum completion:

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

In the code of the onBindViewHolder () method of the adapter of our list, whose elements contain a check box ( CheckBox ), there is a call to the handler method ( holder 'a), in which data from the collection connected to the adapter is read and set on its basis by the check box - the state, as well as the necessary listeners are connected to various interface elements:
')

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

Listeners, when setting a flag and fulfilling a certain condition, change the data in the collection, and if it is not fulfilled, they display either one or another dialog box.

It turns out like this:


In Figure-1 - the list is generated. In Figure-2 - Checked list item. In Figure 3, a dialogue informs about the violation of the condition when the next element is marked.

To get the result with Pic-1 list layout manager ( LayoutManager ), the following order of the required functions is performed:

Algorithm 1


  1. Rv_Adapter.getItemCount () - checks the number of items in the collection;
  2. Rv_Adapter.onAttachedToRecyclerView () - the adapter is connected to the widget;
  3. Until the list items fill the screen space, the following steps of algorithm 2 are performed for the list:

Algorithm 2


  1. Rv_Adapter.onCreateViewHolder () - for each element of the collection creates its own processing;
  2. CustomViewHolder () - handler constructor is executed;
  3. Rv_Adapter.onBindViewHolder () —the view builder is launched for each instance;
  4. Rv_Adapter.onViewAttachedToWindow () - the generated view connects to the window;

Everything is great! If not for "But." Rather BUT!

Problem


When scrolling through a long list containing at least a couple of dozen positions, we will receive a message from Figure-3 without any other actions.

Troubleshooting


The reason is that when writing the adapter code, WE DIDN'T LEARN ORDER THE PERFORMANCE OF THE callback functions listed here and here when scrolling. And he is:

Algorithm 3


  1. When each item in the list is hidden outside the scope of the associated handler instance, the Rv_Adapter.onViewDetachedFromWindow () method is executed , which disables the hidden view from the window;
  2. When the appearance of each new item in the list ( itemView ), the handler instance associated with it ( handler ) is executed, Algorithm 2 is executed;

But that is not all. With the “default” settings of the layout manager, each disconnected from the window list item does not long remain in the queue for quick access. As soon as there are 2 of them there, they are moved by the manager to the queue of reclaimed instances, which is marked by a call to the Rv_Adapter.onViewRecycled () method for each recycled list item and vice versa.

Therefore, Algorithm 3 actually looks like this:

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


From the above Algorithm 3 ', it can be seen that if the list is scrolled by more than max, the number of positions will be re-created in it, for which the Rv_Adapter.onBindViewHolder (holder, visiblePos) method will be used, which will repeat the user's actions.

Conclusion and recommendation


In order to avoid repetition of operations in the onBindViewHolder method (holder, visiblePos) when scrolling a list by a number of positions greater than max, you need:

  1. Add items to the collection with a field with a sign of crowding out the associated view of the queue of recycled handlers, for example bool recycled ;
  2. Insert in the onViewRecycled (holder) method instructions for installing this feature, for example .... setRecycled (true) ;
  3. Insert into the onBindViewHolder method (holder, visiblePos) a check of this sign, for example if (! Handler.cur.isRecycled ()) ...;
  4. Insert into the onViewAttachedToWindow (holder) method instructions for removing this feature, for example .... setRecycled (false) ;

For example:

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


All Articles