Android RecyclerView and ListView implement the elegant way of single-selection lists.

https://www.cnblogs.com/zhujiabin/p/7569224.html

I. Overview:

The source of demand for this article is relatively simple, but there is still something to be discovered for the elegance of the article .

Demand Source: Why such a hungry similar electricity supplier coupons selection interface
in fact, a common list to achieve a radio function, 
the effect is as: 
Write picture description here 
(Do not blame map slag, I roll up four or five times, companies The recorded GIF is so scum...)

Conventional method: 
Add a boolean isSelectedfield to Javabean
and set the selected state of "CheckBox" in the Adapter according to the value of this field. 
Every time a new coupon is selected, the isSelected field in the data source is changed 
and the notifyDataSetChanged()entire list is refreshed. 
This is very simple to implement, and the amount of code is small. The only downside is that the performance is lost, not the most elegant. 
So, as a programmer who pursues a more leisure time today, I am determined to share a wave of elegant solutions.

This article will list and analyze several schemes for single selection of lists in ListView and RecyclerView  , and recommend the scheme of directional refresh partial binding , because it is more efficient and elegant .


Two RecyclerView program overview:

RecyclerView is my favorite, so I will talk about it first.

1Conventional plan:

Please read the conventional plan at the speed of light, directly upload the code: 
Bean structure:

Copy code

public class TestBean extends SelectedBean {
    private String name;
    public TestBean(String name,boolean isSelected) {
        this.name = name;
        setSelected(isSelected);
    }
}

Copy code

There are a lot of single-selection requirements in my project, and I am too lazy to write isSelectedfields, so I made a parent class for subclasses to inherit.

Copy code

public class SelectedBean {
    private boolean isSelected;
    public boolean isSelected() {
        return isSelected;
    }
    public void setSelected(boolean selected) {
        isSelected = selected;
    }
}

Copy code

Acitivity and other methods of Adapter are the most common and will not be repeated. 
The Adapter is onBindViewHolder()as follows:

Copy code

        Log.d("TAG", "onBindViewHolder() called with: holder = [" + holder + "], position = [" + position + "]"); 
        holder.ivSelect.setSelected(mDatas.get(position). isSelected());//"CheckBox" 
        holder.tvCoupon.setText(mDatas.get(position).getName());//TextView 
        holder.ivSelect.setOnClickListener(new View.OnClickListener() { 
            @Override 
            public void onClick( View view) { 
                //Implement single selection, the first method is very simple, Lv Rv is common, because they all have notifyDataSetChanged() method 
                // each time you click, first set all selected to false, and set the current click The item is set to true, refresh the entire view 
                for (TestBean data: mDatas) { 
                    data.setSelected(false); 
                } 
                mDatas.get(position).setSelected(true);
                notifyDataSetChanged();


            }
        });

Copy code

ViewHolder:

Copy code

public static class CouponVH extends RecyclerView.ViewHolder {
        private ImageView ivSelect;
        private TextView tvCoupon;

        public CouponVH(View itemView) {
            super(itemView);
            ivSelect = (ImageView) itemView.findViewById(R.id.ivSelect);
            tvCoupon = (TextView) itemView.findViewById(R.id.tvCoupon);
        }
    }

Copy code

Program advantages:

Simple and rude

Disadvantages of the scheme:

In fact, there are only two items that need to be modified
an Item that is currently in the selected state->normal state and 
then the item clicked by the current finger- >selected state. 
However, if the common scheme is adopted, the entire visible items on the screen will be refreshed and they will be re-entered. the getView()/onBindViewHolder()method. 
In fact, a screen generally has up to 10+ items visible on it, and it doesn’t hurt to traverse it once. 
But we still have to have the heart to pursue elegance, so we continue to look down.

2 Use Rv's notifyItemChanged() to directional refresh:

This program can be read at medium speed ( 
1) This program needs to add a field in the Adapter:

  private int mSelectedPos = -1;//Realize single-selection method two, the variable saves the currently selected position

⑵ When setting the data set (constructor, setData() method, etc.:), initialize  mSelectedPos the value.

Copy code

  //Realize single selection method 2: When setting the data set, find the default selected pos 
        for (int i = 0; i <mDatas.size(); i++) { 
            if (mDatas.get(i).isSelected()) { 
                mSelectedPos = i; 
            } 
        }

Copy code

The code in onClick is as follows:

Copy code

               //Implement the second method of single selection: notifyItemChanged() refreshes two views in a directional direction 
                //If the checked item is not already checked item 
                if (mSelectedPos!=position){ 
                    // first cancel the check state of the previous item 
                    mDatas. get(mSelectedPos).setSelected(false); 
                    notifyItemChanged(mSelectedPos); 
                    //Set the check state of the new Item 
                    mSelectedPos = position; 
                    mDatas.get(mSelectedPos).setSelected(true); 
                    notifyItemChanged(mSelectedPos); 
                }

Copy code

Since this program is called notifyItemChanged(), it will be accompanied by a "white light flashing" animation.

Program advantages:

The program, the more elegant, and will not re-take a screen visible to the Item getView()/onBindViewHolder()method, 
but will still be re-take the need to modify two Item of the getView()/onBindViewHolder()method,

Disadvantages of the scheme:

What we actually need to modify is the value of "CheckBox" inside. 
According to the posture learned in DiffUtil, the term should be "Partial bind", 
(Amway time, I have never heard the stamp of DiffUtil and Partial bind ->: [Android 】Detailed explanation of the new tools brought by 7.0: DiffUtil
What we need is only partial binding .

A suspicious point: 
Use Method 2 When you select another Item for the first time, when you switch the selected state, 
check the log. It is not only the new and old Item onBindViewHolder()method, but also the two Item methods that are not in the screen range at all onBindViewHolder()
such as In this example, there are items 0-3 on the screen, and item1 is checked by default. After I select item0, the log shows that the onBindViewHolder()method of postion 4, 5, 0, 1 is executed in sequence
But when you switch other items again, it will meet expectations: only the two items that need to be modified are getView()/onBindViewHolder()used. 
The reason is unknown. If some friends know, please let me know, thank you.

3 Rv realizes partial binding (recommended):

Use the RecyclerView  findViewHolderForLayoutPosition()method to get the ViewHolder of a certain postion. According to the comment of this method in the source code, it may return null. So we need to pay attention to null (empty is not visible on the screen). 
Unlike method 2, only the code in onClick, the core is still using mSelectedPos fields to do things.

Copy code

   //Realize single selection method three: Another directional refresh method of RecyclerView: there will be no white light flashing animation nor repeat onBindVIewHolder 
    CouponVH couponVH = (CouponVH) mRv.findViewHolderForLayoutPosition(mSelectedPos); 
    if (couponVH != null) {/ /Still on the screen 
        couponVH.ivSelect.setSelected(false); 
    }else { 
        //add by 2016 11 22 for In some extreme cases, the holder is cached in the Recycler's cacheView, 
        // ViewHolder is not available at this time, but neither The onBindViewHolder method will be called back. So add an exception handling 
        notifyItemChanged(mSelectedPos); 
    } 
    mDatas.get(mSelectedPos).setSelected(false);//You need to change the data regardless of whether it is on the screen or not 
    //Set the check state of the new Item 
    mSelectedPos = position; 
    mDatas.get (mSelectedPos).setSelected(true); 
    holder.ivSelect.setSelected(true);

Copy code

Program advantages:

Refresh two items in a targeted manner, only modify the necessary parts, and will not re-go onBindViewHolder(), which belongs to manual partial binding. The amount of code is also moderate, not much.

Disadvantages of the scheme:

No white light flash animation? ? ? (If this is a disadvantage)

4 Rv uses payloads to achieve partial binding (not recommended):

This plan is a pioneering thinking, based on plan 2, using payloads and notifyItemChanged(int position, Object payload)doing things. 
I don’t know what payloads are, and if I don’t understand this solution, I want to Amway again: (Poke ->: [Android] Explain the new tools brought by 7.0: DiffUtil ) The 
onClick code is as follows:

Copy code

                //Realize single selection method four: 
                if (mSelectedPos != position) { 
                    //First cancel the check state of the previous item 
                    mDatas.get(mSelectedPos).setSelected(false); 
                    //Transfer a payload 
                    Bundle payloadOld = new Bundle( ); 
                    payloadOld.putBoolean("KEY_BOOLEAN", false); 
                    notifyItemChanged(mSelectedPos, payloadOld); 
                    //Set the check state of the new Item 
                    mSelectedPos = position; 
                    mDatas.get(mSelectedPos).setSelected(true); 
                    Bundle payloadNew = new Bundle (); 
                    payloadNew.putBoolean("KEY_BOOLEAN", true); 
                    notifyItemChanged(mSelectedPos, payloadNew); 
                }

Copy code

Need to rewrite the three-parameter onBindViewHolder() method:

Copy code

@Override
    public void onBindViewHolder(CouponVH holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle payload = (Bundle) payloads.get(0);
            if (payload.containsKey("KEY_BOOLEAN")) {
                boolean aBoolean = payload.getBoolean("KEY_BOOLEAN");
                holder.ivSelect.setSelected(aBoolean);
            }
        }
    }

Copy code

Program advantages:

Same method 3

Disadvantages of the scheme:

The amount of code is large, and the implementation effect is the same as that of method three, which is only used for pioneering thinking, so choose method three.


List of three ListView solutions:

To be honest, if you are still using ListView, it is not a problem from history, you need to think about it. 
But after all, there are still people using it, just like there are still people using Android 4.x, we also need to consider whether this part of people feels.

1 Conventional plan:

The conventional plan is the same as Rv, no code, refer to II.1:

Program advantages:

Same as 2.1

Disadvantages of the scheme:

Same as 2.1

2 Find the elegant way in ListView:

The idea of ​​this plan is the same as II.3. 
But ListView does not provide  findViewHolderForLayoutPosition() this method to obtain the cached ViewHolder through the postion. This is nonsense, because it was designed without forcing us to use the ViewHolder mode, so we can't get the ViewHolder, so we find another way to getChildAt() get the child View directly through the ViewGroup , and get the child View to get the ViewHolder, you can Mess up. Code:

Copy code

   //Realize single selection: Method 2: Directional refresh of Lv 
                //If the currently selected View is visible on the current screen, and not yourself, you need to directional refresh the state of the previous View 
                if (position != mSelectedPos) { 
                    int firstPos = mLv .getFirstVisiblePosition()-mLv.getHeaderViewsCount();//The HeaderView situation is considered here 
                    int lastPos = mLv.getLastVisiblePosition()-mLv.getHeaderViewsCount(); 
                    if (mSelectedPos >= firstPos && mSelectedPos <= lastPos) = { 
                        View lastSelectedView mLv.getChildAt(mSelectedPos-firstPos);//Remove the selected View 
                        CouponVH lastVh = (CouponVH) lastSelectedView.getTag(); 
                        lastVh.ivSelect.setSelected(false); 
                    }
                    //Regardless of whether it is visible on the screen, you need to change the previous data 
                    mDatas.get(mSelectedPos).setSelected(false); 

                    //Change the selected state of the 
                    currently clicked View couponVH.ivSelect.setSelected(true); 
                    mDatas.get (position).setSelected(true); 
                    mSelectedPos = position; 
                }

Copy code

Program advantages:

It is also a directional refresh + partial binding of two items and will not re-go getView().

Disadvantages of the scheme:

The amount of code seems to be slightly more.


Four summary:

Before writing this article, I also discussed with Guo Shen. Indeed, as he said, the times of getView and onBindViewHolder when refreshing are generally single digits (the number of ItemViews visible on the screen), so even if you use the most conventional method to achieve it, it will not hurt . According to Guo Shen, he wrote before, the reference is the implementation of gmail, and I have seen that the multiple selection function of gmail is done using the conventional solution. 
So, if the project time is urgent, it is okay to adopt the conventional plan. (I also often use the regular plan when I am in a hurry)

The solution in this article can also be used in scenarios such as list likes and drop-down filters
For example, when the list likes the list, if you go through onBindViewHolder() again, the picture nine-square grid control will need to re-set the data set. Some nine-square grids are not well written, and the views in there must be removed, and the rendering is rebuilt. At this time, it is excellent.

Its practical RecyclerView+DiffUtil can also achieve partial binding of directional refresh, please refer to my previous blog post, but it feels like a killer. 
After all, DiffUtil also takes time to calculate. It also traverses the entire new and old data sets when calculating, so this article does not provide this solution to avoid misleading.

The code for this article is no longer a separate project, it can be taken from my github Demos: 
https://github.com/mcxtzhang/Demos/tree/master/selectcoupondemo

Guess you like

Origin blog.csdn.net/az44yao/article/details/112610925