解决安卓中RecyclerView当一项被点击之后,后面每间隔相同的一段都会有其它项被点击

问题描述

  安卓开发会有很多很多莫名其妙的坑。笔者在使用 RecyclerView 的过程中,发现一个很奇怪的事情。当 RecyclerView 有一项被点击之后,后面每间隔相同的一段都会有其它项被点击。经过笔者不断查看日志,这才发现问题所在。

笔者报错时的运行环境:

  • Android Studio Flamingo | 2022.2.1

  • Android SDK 33

  • Gradle 8.0.1

  • JDK 17

  • RecyclerView 1.2.1

缘由

  原来是因为 RecyclerView 为了提高效率,于是使用缓存。然后后来在若干项之后,并没有创建新的视图 View,而直接循环复用了原来的视图,因此导致视图脏读,从而发生以上的问题。

  具体来说,RecyclerView 要求适配器 RecyclerView.Adapter<?> 至少提供两个方法:onCreateViewHolder、onBindViewHolder。第一个方法用于创建视图,第二个方法用于初始化视图数据。问题在于,RecyclerView 在若干项之后,就不再调用 onCreateViewHolder,而是选择直接循环复用缓存中的视图数据,然后调用 onBindViewHolder 来进行初始化。

解决办法 1:提高缓存容量

  很遗憾,安卓没有提供关闭缓存的 API。安卓很多组件都有一些错误设计,这种现象太普遍了,笔者早已习惯了。现实就是要在不良的环境下解决各种疑难杂症。

  RecyclerView 支持设置缓存容量。因此,只需要将缓存容量设置为与数据项的数量一样即可。

recyclerView.setItemViewCacheSize(/* RecyclerView 列表数据项的数量 */);

  不过,这种方法非常笨拙,有一些隐患。首先是性能的问题,这毋庸置疑。RecyclerView 复用视图本来就是为了提高性能。另一个问题是,如果向 RecyclerView 增加了一些项,此时需要同步变更缓存容量。如果删除了一些项,则缓存可能还会发生混乱。这是一个安全问题。

解决办法 2:每次在初始化视图数据之前重置视图数据

  很遗憾,安卓同样没有提供一键重置视图 View 数据的 API,所以需要自己手动自定义代码重置。具体重置哪些数据呢?上次变更了更些数据,这次初始化视图数据之前(调用 onBindViewHolder 方法刚开始时)就要重置哪些数据。这种说法看起来很奇怪,为什么后面已经要初始化了,前面还要重置呢?这很好理解。因为一般只有不同视图数据不同的部分才需要初始化的,相同的数据是直接使用布局文件的默认值而不进行初始化。

  比如,如果上次添加了鼠标点击按钮变色事件,那么这次就需要先把按钮颜色恢复至默认值。因为后面初始化数据的时候并不会再设定按钮的背景颜色。总之,上次变更了更些数据,这次初始化视图数据之前就要重置哪些数据。

  不过,这种方法也比较笨拙,首先它耦合度很高,需要记住上次视图变化的每个细节。另外,这种方法也有安全问题。因为每次都重置视图数据,所以当滑动到下面的项时,前面视图的数据就被清空了。比方说,如果上次使得按钮变色,当滑动到下面的项,然后再滑回来时,前面那个按钮的颜色也会恢复原状。

解决办法 3:优化设计,不在视图中储存数据

  已经知道,前面的 解决办法 1解决办法 2 都各自有一些问题。这些问题的症结在于,RecyclerView 在设计的时候认为开发者不会在视图 View 中储存任何数据。因此,读者在开发的过程中需要区分,共性数据与个性化数据,视图数据空间与数据库数据空间。如果把个性化数据储存在视图数据空间,就会导致 bug。所以,最好的办法是不要在视图 View 中储存数据。

  比如上面举例的点击按钮变色问题,不应该让视图 View 来储存这种颜色变化,而应该使用其它单独的数据结构来储存。这样设计起来就没有安全隐患。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这里是一段示例代码,供您参考: ```xml <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" android:clipToPadding="false" android:paddingStart="16dp" android:paddingEnd="16dp" android:overScrollMode="never" app:layoutManager="LinearLayoutManager" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <!-- 右侧拖动条 --> <ImageView android:id="@+id/scrollBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/scroll_bar" android:layout_alignParentRight="true" android:layout_centerVertical="true" /> ``` ```java // RecyclerView Adapter public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<String> mData; public MyAdapter(List<String> data) { mData = data; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_layout, parent, false); return new ViewHolder(itemView); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.tvTitle.setText(mData.get(position)); } @Override public int getItemCount() { return mData.size(); } static class ViewHolder extends RecyclerView.ViewHolder { TextView tvTitle; ViewHolder(@NonNull View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.tvTitle); } } } // RecyclerView Item Layout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textColor="@color/black" android:layout_centerInParent="true" /> </RelativeLayout> ``` 这段代码RecyclerView 使用一个 LinearLayoutManager 来管理布局,设置 paddingStart 和 paddingEnd 来控制列表与边缘的间距。右侧拖动条使用一个 ImageView 来实现,设置 layout_alignParentRight 和 layout_centerVertical 控制位置。RecyclerView 的每一项使用一个 RelativeLayout 实现,其的 TextView 设置 layout_centerInParent 控制文本居。这样就可以实现每一项与标题心居对齐,右侧拖动条使用图片的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值