系列文章目录
基本用法
遇到问题汇总
附上完整代码
文章目录
- 系列文章目录
- 前言
- 一、基本使用
- 1.先在使用该控件xml文件中引入RecyclerView:
- 2.再编写Adapter适配器代码
- 3.再创建一个Item的xml文件
- 4.在界面中调用这个适配器
- 二、遇到问题汇总
- 1.checkbox多选问题
- 2.多选不能及时刷新
- 3.重影问题
- 4.处理数据问题
- 三、附上完整代码
- 1.fragment文件
- 2.Adapter 适配器文件
- 3.fragmnet的xml文件
- 4.item的xml文件
- 总结
前言
RecyclerVie是安卓控件里相对复杂难用的,同时也是用的比较多的。今天就介绍一下基本用法,及我在使用过程中出现问题,后边附上的有代码。
一、基本用法
1.先在使用该控件xml文件中引入RecyclerView:
代码如下(示例):
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
2.再编写Adapter适配器代码
代码如下(示例):
这三个函数是强制必须重写的
- onCreateViewHolder 用于得到我们自定义的ViewHolder,在ListView中,我们也会定义ViewHolder来承载视图中的元素.
- onBindViewHolder 指定位置的数据和视图绑定起来
- getItemCount 列表总共有多少条
public class FragmentRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(mContext).inflate(R.layout.fragment_adapter_item_layout, parent, false); return new NormalHolder(itemView); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { NormalHolder normalHolder = (NormalHolder) holder; normalHolder.textView.setText(mDatas.get(position)); normalHolder.checkBox.setTag(position); normalHolder.checkBox.setChecked(mCheckStates.get(position, false)); } @Override public int getItemCount() { return mDatas.size(); }
}
ListView的数据都是从外部传进来的,我们需要给Adapter添加上一个构造函数,将数据从外部传进来:
public FragmentRecyclerAdapter(Context context, ArrayList<String> datas) { mContext = context; mDatas = datas; }
3.再创建一个Item的xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:id="@+id/item_layout" android:orientation="horizontal"> <CheckBox android:id="@+id/check_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/checkbox_shape" android:button="@null" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="发动机数据"></TextView> </LinearLayout>
4.在界面中调用这个适配器
@Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // TODO Auto-generated method stub View view = inflater.inflate(R.layout.sjjk_fdj_fragment, container, false); generateDatas(); RecyclerView mRv = view.findViewById(R.id.recyclerview); //为item加条线 mRv.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); //创建布局管理器,我这里用的是线性布局 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRv.setLayoutManager(linearLayoutManager); FragmentRecyclerAdapter adapter = new FragmentRecyclerAdapter(getContext(), mDatas); mRv.setAdapter(adapter); return view; }
关于RecyclerView使用基本就这些了
下面说一下使用过程中我遇到的一些坑
二、遇到问题汇总
1.checkbox多选问题
在上面item文件中大家应该能看到我里面是一个checkbox后边加一个文本框
checkbox实现单选多选操作。表面上看上去只是改变checkbox那么简单,然而实际开发中,实现起来并不是那么简单(之前博客有说实现checkbox方法)。尤其当item比较多(比如屏幕最多只能显示12个item,但总共有30个item,也就是说item数量大于屏幕能够显示的item数)滑动屏幕的时候,由于适配器中getview()会重复使用被移除屏幕的item,所以会造成checkbox选择状态不正常的现象。比方说你只对第一个Item中的CheckBox做了选中操作,当列表向上滚动的时候,你会发现,下面的Item中居然也会有被选中的。如何解决呢。
我们可以建一个SparseBooleanArray ,把每一个item勾选情况用tag记录下来,这样就能解决了
代码如下
SparseBooleanArray mCheckStates = new SparseBooleanArray(); normalHolder.checkBox.setTag(position);int tag = (int) buttonView.getTag();
if (isChecked) {
mCheckStates.put(tag, true);
} else { mCheckStates.delete(tag);
}
2.多选不能及时刷新
checkbox有单选,最上面是一个全选功能
当然全选时候就是把每个都标记一下
for (int i = 0; i < mDatas.size(); i++) { mCheckStates.put(i, true); }
这时候发现并不能及时刷新,前面说过了 item条目多,不是一屏幕能显示全的。往下滑动时候发现下面是刷新过得,再往上滑动才刷新过来。即刚点过全选时候第一屏并不能刷新,是因为在选时候第一屏view已经绘制好了 需要调用notifyDataSetChanged()刷新。
往代码里直接加这行代码报错
Cannot call this method while RecyclerView is computing a layout or scrollin
应用直接闪退。
看有个博主分析的挺好的,拿来借用一下。现在场景是recyclerview的item 存在checkBox,checkBox的checkChangedListener中会修改数据源,同时调用notifyDataSetChanged函数。
当持有该adapter的类也修改了数据源,同时调用了notifyDataSetChanged函数,Adapter就会触发onBindViewHolder(),checkBox的check被修改,又触发了onCheckedChanged的监听,这时就会crash。crash的本意是RecyclerView正在layout或滚动,无法调用notifyDataSetChanged。
我们应该避免在RecyclerView正在computing的时候,调用notifyDataSetChanged函数。使用handler的消息队列机制,当前队列消息正在处理computing,所以,可以向队列post一个notifyDataSetChanged的任务。这样就能避免RecyclerView正在computing
代码如下
for (int i = 0; i < mDatas.size(); i++) { mCheckStates.delete(i); } new Handler().post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
问题解决。
3.重影问题
因为我是在平板上开发的,所以会横竖屏来回切换,发现来回切换时候item会重影。
看别人说想去掉这个阴影,方法也是很简单的,只需要调整控件的一个属性:overScrollMode
为 never
,overScrollMode
顾名思义就是滑动即将超出边界时的模式,可以调整的属性有三种:
- never: 完全去掉滑动边界的阴影效果
- always: 总是出现滑动边界的阴影效果
- ifContentScrolls:
- 如果recycleview里面的内容可以滑动,那么滑到边界后继续滑动会出现滑动边界的阴影效果
- 如果recycleview里面的内容不可以滑动,那么滑到边界后继续滑动不会出现滑动边界的阴影效
发现 这个并不能解决问题,还有的说把滚动条也去掉就行了
android:scrollbars="none"
实际上都没用,最终不得已,固定住屏幕方向 搞成竖屏不让来回切换这个问题自然也就不存在了。
4.处理数据问题
因为我这边项目需要把checkbox勾选的记录下来传到下个页面,于是就定义了一个list
linkedList.add(mDatas.get(tag));
check时候根据tag把这个值存起来,勾选哪个下个页面就显示什么
取消全选时候 我是直接把list清除了,后来发现不行。因为我这里是activity里面嵌套fragment
fragment里面装的recyclerview。activity里面有四个fragment,每个fragment里面都是recyclerview
因为要处理逻辑一样用的适配器是同一个。这样直接清除,一个fragment全选取消了 其他fragment数据也没了。
于是便这样写了
这样取消全选时候把每个item都标记了 同时后边TextView数值也删除,理论上没毛病。
可是却发现有脏数据,勾选全选然后下个页面展示全部内容取消全选明明删除了还有数据显示,打印日志发现确实每一个都删除了 但是有刷新,绘制时候不止第一个item 下面也会绘制,正好检测到check了 所以第一屏除了第一个item剩下的也都一个个add进去了
后来改成这样,问题解决
即把所有数据源都删除,Java 集合类中的 List.removeAll() 方法用于从列表中移除指定 collection 中包含的所有元素。包括重复add进去的也会删除。
最终在activity里面这样处理的
因为不想太麻烦了 直接把list定义成了static 然后在activity里面clear一下。
遇到问题暂时就这些,下面附上完整代码
三、附上完整代码
activity里面主要就是取一下值,代码就是上面一部分 主要是fragment里面调用适配器 适配器代码
及fragment的xml文件 item的xml文件。
1.Fragment文件
package com.example.recanapp.sjjk.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.example.recanapp.R; import java.util.ArrayList; public class FdjFragment extends Fragment { private ArrayList<String> mDatas = new ArrayList<>(); @Override @Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // TODO Auto-generated method stub View view = inflater.inflate(R.layout.sjjk_fdj_fragment, container, false); generateDatas(); RecyclerView mRv = view.findViewById(R.id.recyclerview); mRv.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); //线性布局 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRv.setLayoutManager(linearLayoutManager); FragmentRecyclerAdapter adapter = new FragmentRecyclerAdapter(getContext(), mDatas); mRv.setAdapter(adapter); return view; } private void generateDatas() { mDatas.add("全选"); mDatas.add("发动机转速"); mDatas.add("发动机实际扭矩输出百分比"); mDatas.add("百分比负荷"); 因怕泄露公司资料,这里就不过多显示了 mDatas.add("加速踏板换抵挡开关"); mDatas.add("道路速度限制状态"); } }
2.Adapter 适配器文件
package com.example.recanapp.sjjk.fragment; import android.content.Context; import android.os.Handler; import android.util.Log; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.recanapp.R; import java.util.ArrayList; import java.util.LinkedList; public class FragmentRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context mContext; private ArrayList<String> mDatas; SparseBooleanArray mCheckStates = new SparseBooleanArray(); public static ArrayList<String> linkedList = new ArrayList<>(); public FragmentRecyclerAdapter(Context context, ArrayList<String> datas) { mContext = context; mDatas = datas; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(mContext).inflate(R.layout.fragment_adapter_item_layout, parent, false); return new NormalHolder(itemView); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { NormalHolder normalHolder = (NormalHolder) holder; normalHolder.textView.setText(mDatas.get(position)); normalHolder.checkBox.setTag(position); normalHolder.checkBox.setChecked(mCheckStates.get(position, false)); } @Override public int getItemCount() { return mDatas.size(); } public class NormalHolder extends RecyclerView.ViewHolder { public LinearLayout linearLayout; public CheckBox checkBox; public TextView textView; public NormalHolder(View itemView) { super(itemView); linearLayout = itemView.findViewById(R.id.item_layout); textView = itemView.findViewById(R.id.text); checkBox = itemView.findViewById(R.id.check_image); checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { int tag = (int) buttonView.getTag(); Log.i("RecyclerAdapter", "NormalHolder" + tag); if (isChecked) { linearLayout.setBackgroundResource(R.color.Copper); if (tag == 0) { for (int i = 0; i < mDatas.size(); i++) { mCheckStates.put(i, true); } new Handler().post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); linkedList.addAll(mDatas); } mCheckStates.put(tag, true); linkedList.add(mDatas.get(tag)); } else { linearLayout.setBackgroundResource(R.color.white); if (tag == 0) { for (int i = 0; i < mDatas.size(); i++) { mCheckStates.delete(i); } new Handler().post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); linkedList.removeAll(mDatas); } mCheckStates.delete(tag); linkedList.remove(mDatas.get(tag)); } Log.i("RecyclerAdapter", "NormalHolder" + linkedList.toString()); } }); } } }
3.fragment的xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/sjjk_fdj_fragment" android:orientation="vertical" tools:context="com.example.recanapp.sjjk.fragment.FdjFragment"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </LinearLayout>
4.item的xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:id="@+id/item_layout" android:orientation="horizontal"> <CheckBox android:id="@+id/check_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/checkbox_shape" android:button="@null" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="发动机数据"></TextView> </LinearLayout>
总结
以上就是今天要讲的内容,本文介绍了recyclerview基本使用,及在使用过程中作者遇到的一些问题(多选问题、不能及时刷新问题、重影问题、传值问题),希望能给大家一个参考。