当前位置:网站首页>Research on recyclerview details - Discussion and repair of recyclerview click dislocation
Research on recyclerview details - Discussion and repair of recyclerview click dislocation
2022-04-23 14:08:00 【Senzhiqianshou】
First describe the problem . for instance , I have a list , The order of entries should be... From top to bottom 0,1,2…; Then when I delete the second entry , The original third item should become the current second item , Then I click on the second item now , The position you should get is 1, But the actual position is 2:
It's still painful to have this problem , Because other functions are good , Only this position is misplaced . To solve this problem , We have to figure out another problem :ViewHolder Of getPosition、getAdapterPosition and getLayoutPosition Have you figured out ?
Why did I ask this question , Let's review our writing ( Refer to my other article RecyclerView Advanced use ( One )- Simple implementation of sideslip deletion ):
We are customizing CallBack Inside onSwiped It's written like this :
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
recyclerItemList.remove(viewHolder.getAdapterPosition());
recyclerView.getAdapter().notifyItemRemoved(viewHolder.getAdapterPosition());
}
When we were taking positions , It's using getAdapterPosition. Actually get position There are four ways :
/** * @deprecated This method is deprecated because its meaning is ambiguous due to the async * handling of adapter updates. You should use {@link #getLayoutPosition()} or * {@link #getAdapterPosition()} depending on your use case. * * @see #getLayoutPosition() * @see #getAdapterPosition() */
@Deprecated
public final int getPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
/** * Returns the position of the ViewHolder in terms of the latest layout pass. * <p> * This position is mostly used by RecyclerView components to be consistent while * RecyclerView lazily processes adapter updates. * <p> * For performance and animation reasons, RecyclerView batches all adapter updates until the * next layout pass. This may cause mismatches between the Adapter position of the item and * the position it had in the latest layout calculations. * <p> * LayoutManagers should always call this method while doing calculations based on item * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position * of the item. * <p> * If LayoutManager needs to call an external method that requires the adapter position of * the item, it can use {@link #getAdapterPosition()} or * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. * * @return Returns the adapter position of the ViewHolder in the latest layout pass. * @see #getAdapterPosition() */
public final int getLayoutPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
/** * Returns the Adapter position of the item represented by this ViewHolder. * <p> * Note that this might be different than the {@link #getLayoutPosition()} if there are * pending adapter updates but a new layout pass has not happened yet. * <p> * RecyclerView does not handle any adapter updates until the next layout traversal. This * may create temporary inconsistencies between what user sees on the screen and what * adapter contents have. This inconsistency is not important since it will be less than * 16ms but it might be a problem if you want to use ViewHolder position to access the * adapter. Sometimes, you may need to get the exact adapter position to do * some actions in response to user events. In that case, you should use this method which * will calculate the Adapter position of the ViewHolder. * <p> * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the * next layout pass, the return value of this method will be {@link #NO_POSITION}. * * @return The adapter position of the item if it still exists in the adapter. * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last * layout pass or the ViewHolder has already been recycled. */
public final int getAdapterPosition() {
if (mOwnerRecyclerView == null) {
return NO_POSITION;
}
return mOwnerRecyclerView.getAdapterPositionFor(this);
}
/** * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders * to perform animations. * <p> * If a ViewHolder was laid out in the previous onLayout call, old position will keep its * adapter index in the previous layout. * * @return The previous adapter index of the Item represented by this ViewHolder or * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is * complete). */
public final int getOldPosition() {
return mOldPosition;
}
Altogether getPosition、getLayoutPosition、getAdapterPosition and getOldPosition. therefore , Why are we taking position Why did you take it when you were getAdapterPosition Well , There is no explanation in the online articles , It's all a generalization . But now the problem is , We're going to study .
First of all, from the notes ,getOldPosition It's related to animation , We won't discuss it here . Then there are only three left .
getPosition Used **@Deprecated** annotation , It shows that it is an abandoned method . Why abandon it .
Translated into English :* This method is deprecated , Because its meaning is unclear due to the asynchronous processing of adapter updates .* Let's switch to getLayoutPosition perhaps getAdapterPosition. From the source code ,getPosition and getLayoutPosition The code for is the same , We can argue that getLayoutPosition It's the old getPosition. Then there is nothing left getLayoutPosition and getAdapterPosition A comparison of . And our problem is that we use getAdapterPosition.
getLayoutPosition Comment means to return ViewHolder Position in the latest layout process .
getAdapterPosition The comment means to return this ViewHolder It means item The location of the adapter .
Just to see if it means more awkward . I found it here StackFlow A summary of an engineer on the :
I briefly summarized :
1、 In general , The two are the same .
2、getAdapterPosition Name , Get the location on the adapter . because RecyclerView Than ListView Evolved local refresh function , So in ListView The unification and renewal of the times getPosition The way is going to change .RecylerView There are essentially two lists , A storage data source , A storage view ViewHolder. Through the observer model , Data source driven view updates . therefore , You can simply say getAdapterPosition Is the location on the acquired data source .
3、getLayoutPostion Is to get the view position of the list drawn last time . Especially when using local refresh ( As long as it's not notifyDataSetChanged) During these operations , It is recommended to use this method to get the location . comparison getAdapterPosition, Can get the changed position immediately .
With the above summary , Then we have a way to solve this problem , Change the code to :
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
recyclerItemList.remove(viewHolder.getLayoutPosition());
recyclerView.getAdapter().notifyItemRemoved(viewHolder.getLayoutPosition());
}
Then try again , It turns out it's not going to work .
What's the problem ?
Let's take a look at the place where you set the item click event :
public class SimpleRecyclerListAdapter extends RecyclerView.Adapter<SimpleRecyclerViewHolder> {
private List<RecyclerItem> recyclerItemList;
private OnItemCLickListener onItemCLickListener;
public void setOnItemCLickListener(OnItemCLickListener onItemCLickListener) {
this.onItemCLickListener = onItemCLickListener;
}
public SimpleRecyclerListAdapter(List<RecyclerItem> recyclerItemList) {
this.recyclerItemList = recyclerItemList;
}
@NonNull
@Override
public SimpleRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(getItemLayoutRes(), parent, false);
return new SimpleRecyclerViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull SimpleRecyclerViewHolder holder, int position) {
RecyclerItem item = recyclerItemList.get(position);
holder.iconView.setImageResource(item.getIcon());
holder.textView.setText(item.getText());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemCLickListener != null) {
onItemCLickListener.onItemClick(item, holder, position);
}
}
});
}
@Override
public int getItemCount() {
return recyclerItemList == null ? 0 : recyclerItemList.size();
}
public interface OnItemCLickListener {
void onItemClick(RecyclerItem recyclerItem, SimpleRecyclerViewHolder holder, int position);
}
protected @LayoutRes int getItemLayoutRes(){
return R.layout.item_simple_list;
}
}
The rest of the code can be ignored , Just look onBindViewHolder It set up onClickListener The place of . It's from here position Directly from the outside position Got . But in fact, external position still adapterPosition, Why do you say that :
/** * Called by RecyclerView to display the data at the specified position. This method * should update the contents of the {@link ViewHolder#itemView} to reflect the item at * the given position. * <p> * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method * again if the position of the item changes in the data set unless the item itself is * invalidated or the new position cannot be determined. For this reason, you should only * use the <code>position</code> parameter while acquiring the related data item inside * this method and should not keep a copy of it. If you need the position of an item later * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will * have the updated adapter position. * <p> * Partial bind vs full bind: * <p> * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, * the ViewHolder is currently bound to old data and Adapter may run an efficient partial * update using the payload info. If the payload is empty, Adapter must run a full bind. * Adapter should not assume that the payload passed in notify methods will be received by * onBindViewHolder(). For example when the view is not attached to the screen, the * payload in notifyItemChange() will be simply dropped. * * @param holder The ViewHolder which should be updated to represent the contents of the * item at the given position in the data set. * @param position The position of the item within the adapter's data set. * @param payloads A non-null list of merged payloads. Can be empty list if requires full * update. */
public void onBindViewHolder(@NonNull VH holder, int position,
@NonNull List<Object> payloads) {
onBindViewHolder(holder, position);
}
See the comments , Say this postion within the adapter’s data set. therefore , If we click on the event, we can't transmit this position. Change it to the following way :
@Override
public void onBindViewHolder(@NonNull SimpleRecyclerViewHolder holder, int position) {
RecyclerItem item = recyclerItemList.get(holder.getLayoutPosition());
holder.iconView.setImageResource(item.getIcon());
holder.textView.setText(item.getText());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemCLickListener != null) {
onItemCLickListener.onItemClick(item, holder, holder.getLayoutPosition());
}
}
});
}
Retest :
Perfect solution .
In addition to the above solution of comparing source flow , Is there any other solution ?
The answer is yes , And there are two :
1、 Do not use getLayoutPosition, Still use getAdapterPosition. So our onSwiped The method needs to be modified to :
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//1、 Delete source data
int pos = viewHolder.getAdapterPosition();
RecyclerItem item = recyclerItemList.get(pos);
recyclerItemList.remove(item);
//2、 Notify view refresh
recyclerView.getAdapter().notifyItemRemoved(viewHolder.getAdapterPosition());
//3、 correct position, Prevent when clicking ,position Disorder
recyclerView.getAdapter().notifyItemRangeChanged(0, recyclerItemList.size());
}
Added a third step , Called notifyItemRangeChanged, Let's look at this method :
/** * Notify any registered observers that the <code>itemCount</code> items starting at * position <code>positionStart</code> have changed. * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>. * * <p>This is an item change event, not a structural change event. It indicates that * any reflection of the data in the given position range is out of date and should * be updated. The items in the given range retain the same identity.</p> * * @param positionStart Position of the first item that has changed * @param itemCount Number of items that have changed * * @see #notifyItemChanged(int) */
public final void notifyItemRangeChanged(int positionStart, int itemCount) {
mObservable.notifyItemRangeChanged(positionStart, itemCount);
}
Notify all registered observed entries to update the location . The first parameter is the starting position we need to update , Here we start with the first update ( In fact, you can calculate , Start from where you click ). the second , Number of entries to be updated , Since we started with the first update , Then the number is all entries , Disguised as full update .
Now that I think of all the updates , Then it's natural to think of a third solution .
2、 use notifyDataSetChanged Full update , Change the code to :
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
recyclerItemList.remove(viewHolder.getAdapterPosition());
recyclerView.getAdapter().notifyDataSetChanged();
}
Actually notifyDataSetChanged Can be seen as notifyItemRemoved(notifyItemInserted) and notifyItemRangeChanged Set . The least amount of code , But it's a full update , And period notifyItemRemoved(notifyItemInserted) Animation .
Source code address :GitHub
more RecyclerView Discussion articles , You can follow my other blogs .
版权声明
本文为[Senzhiqianshou]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204231405037310.html
边栏推荐
猜你喜欢
微信小程序获取登录用户信息、openid和access_token
Promtail + Loki + Grafana 日志监控系统搭建
浅谈基于openssl的多级证书,Multi-level CA的签发和管理,以及双向认证
mysql新表,自增id长达20位,原因竟是......
sql中出现一个变态问题
Detailed tutorial on the use of setinterval timing function of wechat applet
RobotFramework 之 文件上传和下载
帆软实现分页时第一行和最后两行冻结方式
星界边境文本自动翻译机使用说明
Jmeter安装教程以及我遇到的问题的解决办法
随机推荐
As a junior college student, I studied hard in closed doors for 56 days, won Ali offer with tears, five rounds of interviews and six hours of soul torture
How QT designer adds resource files
PATH环境变量
室内外地图切换(室内基于ibeacons三点定位)
帆软实现一个单选按钮,可以统一设置其他单选按钮的选择状态
微信小程序的订阅号开发(消息推送)
微信小程序setInterval定时函数使用详细教程
Wechat applet communicates with low-power Bluetooth - receives data sent by hardware (IV)
leetcode--380. O (1) time insertion, deletion and acquisition of random elements
Intégration de Clusters CDH Phoenix basée sur la gestion cm
CentOS mysql多实例部署
Chapter I review of e-commerce spike products
微信小程序基于udp协议与esp8266进行通信
Call wechat customer service applet
Mock测试
Promtail + Loki + Grafana 日志监控系统搭建
Can global variables be defined in header files
win10自带Groove音乐不能播放CUE和APE文件的一种曲线救国办法,自己创建aimppack插件包,AIMP安装DSP插件
帆软中单元格中隔行变色以及数量大于100字体变大变红设置
mysql通过binlog文件恢复数据