What does developer.android.com say about RecyclerView?

The question of the life cycle ( life: cycle ) activity ( activity ) or fragment ( Fragment A ) android application is extremely important for the practitioner androidchika (developer android). 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 an activity and the work of RecyclerView - this is HARD ORDERexecuting callback methods when using this widget, and, therefore, the need to use it correctly .

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


publicclassRvCustomAdapterextendsRecyclerView.Adapter<RvCustomAdapter.CustomViewHolder> {
    privatefinal Frag1 frag;
    privatefinal LayoutInflater lInflater;
    private ArrayList<JSONDataSet> dataSet;
    ... ... ...
    publicRvCustomAdapter(final Frag1 fragment){
        this.frag = fragment;
        this.lInflater = (LayoutInflater) fragment.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.dataSet = new ArrayList<>();
    }
    ... ... ...
    @Overridepublic 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);
        returnnew CustomViewHolder(view);
    }
    // Заменяет контент отдельного view (вызывается layout manager-ом)@OverridepublicvoidonBindViewHolder(@NonNull CustomViewHolder holder,
                                   int position){
        holder.showData(position);
    }
    @OverridepublicintgetItemCount(){
        return dataSet.size();
    }
    /**
     *  Класс view holder-а с помощью которого мы получаем 
     *  ссылку на каждый элемент
     *  отдельного пункта списка и обрабатываем его нажатие
     */classCustomViewHolderextendsRecyclerView.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 to the various elements of the interface connecting the necessary listeners ( Listener ):

Listing 2


voidshowData(finalint 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() {
                @OverridepublicvoidonClick(View view){
                    cur.setChecked(!cur.isChecked());
                    cb_Data.setChecked(cur.isChecked());
                }
            };
        private OnCheckedChangeListener cb_DataOnCheckedChangeListener =
            new OnCheckedChangeListener() {
                @OverridepublicvoidonCheckedChanged(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 () - runs Builder form (for each instance of view );
  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


@OverridepublicvoidonViewRecycled(@NonNull CustomViewHolder holder){
        super.onViewRecycled(holder);
        holder.cur.setRecycled(true);
    }
    @OverridepublicvoidonBindViewHolder(@NonNull CustomViewHolder holder, int position){
        if (!holder.cur.isRecycled()){
            ... ... ...
        }
    }
    @OverridepublicvoidonViewAttachedToWindow(@NonNull CustomViewHolder holder){
        super.onViewAttachedToWindow(holder);
        holder.cur.setRecycled(false);
    }

Also popular now: