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:
(Do not blame map slag, I roll up four or five times, companies The recorded GIF is so scum...)
Conventional method:
Add a boolean isSelected
field 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:
public class TestBean extends SelectedBean { private String name; public TestBean(String name,boolean isSelected) { this.name = name; setSelected(isSelected); } }
There are a lot of single-selection requirements in my project, and I am too lazy to write isSelected
fields, so I made a parent class for subclasses to inherit.
public class SelectedBean { private boolean isSelected; public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } }
Acitivity and other methods of Adapter are the most common and will not be repeated.
The Adapter is onBindViewHolder()
as follows:
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(); } });
ViewHolder:
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); } }
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.
//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; } }
The code in onClick is as follows:
//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); }
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.
//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);
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:
//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); }
Need to rewrite the three-parameter onBindViewHolder()
method:
@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); } } }
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:
//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; }
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