关于 ListView
我们大家都应该是非常的熟悉了,在 Android 开发中是经常用到的,今天就再来回顾一下,ListView
的使用方法,和一些需要优化注意的地方,还有日常开发过程中的一些小技巧和经验。
ListView
是 Android 系统为我们提供的一种列表显示的一种控件,使用它可以用来显示我们常见的列表形式。继承自抽象类 AdapterView
。
类的关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aykGPhlJ-1657552024732)(https://ask.qcloudimg.com/http-save/yehe-5368985/cc8k688y5e.jpeg?imageView2/2/w/1620)]
这就是一种最简单的 ListView
的表现形式,黑色框就是 ListView
控件,其中由一个个的 item
组成(红色框内容),然后可以通过向下滑动来查看很多的条目。
ListView
仅是作为容器(列表),用于装载显示数据(就是上面的一个个的红色框的内容,也称为 item)。item 中的具体数据是由适配器(adapter)来提供的。
适配器(adapter):作为 View (不仅仅指的 ListView)和数据之间的桥梁或者中介,将数据映射到要展示的 View 中。这就是最简单适配器模式,也是适配器的主要作用!
当需要显示数据的时候,ListView 会从适配器(Adapter)中取出数据,然后来加载数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QGeDmZDV-1657552024733)(https://ask.qcloudimg.com/http-save/yehe-5368985/gd8n5jgqfh.jpeg?imageView2/2/w/1620)]
ListView 负责以列表的形式向我们展示 Adapter 提供的内容
前面讲了 ListView 负责把 Adapter 提供的内容一一的展现出来,每一条数据对应一个 item 。试想如果把所有的数据信息全部加载到 ListView 上显示,加入这些数据有 100 条。那么 ListView 就要创建 100 个视图。如果有更多的数据,那么 ListView 就会创建更多的视图。这种行为显然是不可取的,这样会消耗大量的内容。
解决方案:
为了节省内存的占用,ListView 是不会为每一条数据创建一个视图的,而是采用了 Recycler组件 的方式。回收和复用 View。
那么是如何来复用的呢?
我们都知道一个屏幕可见的内容就是那么大,所以用户一次能看到的 item 就是固定的那么几个。假如当屏幕一次可以显示 x 个 item 时(不用是完整的),那么 ListView 会创建 x+1 个视图;当第1个 item 离开屏幕的时候,此时这个 item 的 View 就会被回收,再入屏的 item 的 View 就会优先从该缓存中获取。
只有 item 完全离开屏幕后才会复用,这也是为什么 ListView 要创建比屏幕需要显示视图多 1 个的原因:缓冲显示视图。 第 1 个 item 离开屏幕是有一个过程的,会有 1 个 第一个 item 的下半部分 & 第 X+1 个 item 的上半部分同时在屏幕中显示的状态 这种情况是没法使用缓存的 View 的。只能继续用新创建的视图 View。
实例演示:
假如屏幕一次只能显示 5 个 item,那么 ListView 会创建 (5+1)个 item 视图;当第 1 个 item 完全离开屏幕后才会回收至缓存,从而复用。(用于显示第 7 个 item)。
演示图来自网络:
引入 ListView 和普通的 View 一样,直接在布局中添加 ListView
控件即可。
xml 中文件配置信息
<ListView
android:id="@+id/listView"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
LinearLayout>
AbsListView 常用属性和相关方法:
属性 | 说明 | 备注 |
---|---|---|
android:choiceMode | 列表的选择行为:默认:none 没有选择行为 | 选择方式:none:不显示任何选中项目 singleChoice:允许单选multipleChoiceModel:允许多选配合 getCheckedItemPosition 、getCheckedItemCount、等使用 |
android:drawSelectorOnTop | 如果该属性设置为 true,选中的列表项的选中颜色会 成为前景颜色(实验没有效果) | |
android:transcriptMode | 指定列表添加新的选项的时候,是否自动滑动到底部,显示新的选项。 | disabled:取消 transcriptMode 模式;默认的 normal:当接受到数据集合改变的通知,并且仅仅当最后一个选项已经显示在屏幕的时候,自动滑动到底部。alwaysScroll:无论当前列表显示什么选项,列表将会自动滑动到底部显示最新的选项。 |
ListView 提供的 xml 属性
XML 属性 | 说明 | 备注 |
---|---|---|
android:divider | 设置 List 列表项的分隔条(可用颜色分割,也可用图片 Drawable 分割) | 不设置列表之间的分割线,可设置属性为 @null |
android:dividerHeight | 用于设置分隔条的高度 | |
android:background 属性 | 设置列表的背景 | |
android:entries | 指定一个数组资源,Android 将根据该数组资源来生成 ListView | |
android:footerDividerEnabled | 如果设置成 false 则不在 footerView 之前绘制分隔条 | |
android:headerDividerEnabled | 如果设置成 false 则不再 headerView 之前绘制分隔条 |
使用 ListView 的话就离不开 Adapter 了。
Adapter 本身是一个接口,Adapter 接口及其子类的继承关系如下图:
其中 ListAdapter 为 AbsAdapter 提供列表项,SpinnerAdapter 为 AbsSpinner 提供列表项
ArrayAdapter 、SimpleAdapter 都是 Android API 给我们提供好的适配器,直接使用即可,不过模式都已经写死了。
特定:使用简单、用于将数组、List 形式的数据绑定到列表中作为数据源,支持泛型操作
步骤:
具体实现:
添加 ListView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"
>
ListView>
LinearLayout>
定义数据源
List<String> listData = new ArrayList<>();
for(int i=0;i<20;i++){
listData.add("item数据"+i);
}
创建 ArrayAdapter 适配器
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,android.R.layaout.simple_list_item_1,listData);
这里简单介绍一下 ArrayAdapter 的构造方法,ArrayAdapter 有好几个构造方法。
其中第一参数都是 Context 第二个参数就是要添加的 item 的布局 id 然后就是数据,数据可以使用数组也可以使用List。还有一点要注意的是,如果 List 里面存放的是一个普通对象而不是String 的话,则显示在 item 中的数据为这个对象调用 toString 后的结果。
将 ArrayAdapter 适配器绑定到 ListView 上
listView.setAdapter(arrayAdapter);
使用 ArrayAdapter 的缺点
ArrayAdapter 使用起来非常简单,也就导致了功能实现非常局限,每个列表项只能是 TextView。可用的 item 布局要足够简单!
相比 ArrayAdapter 来说,功能比较强大,可以将数据源的数据一一的绑定到 item 中的 view 中。
使用步骤:
具体实现
在 xml 中添加 ListView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"
>
ListView>
LinearLayout>
实现 item 布局,这里我自己随便写了一个布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_one"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_two"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_three"/>
<ImageView
android:contentDescription="@string/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_four"/>
LinearLayout>
创建数据源,使用 SimpleAdapter 的时候创建数据源很关键。 数据源的固定格式是 List> ,一般我们都这样写 List ,当然 List 里面存放的一条一条的数据就是对应 itme 中的数据。如果 item 中的布局有点复杂的话,item 中的每个控件又需要设置不同的值,那么 item 中的每个布局的内容就又对应 HashMap 中的值了。
// 比如上面的布局,有 4 个内容需要填充,则对应的数据源应该是
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name","小明");
hashMap.put("age",18);
hashMap.put("height",180);
hashMap.put("picture",R.drawable.icon);
然后多了个 item 就是设置多个这样的 hashMap 加入到 List 中构成数据源。
// 具体的实现方法:
List<HashMap<String,Object>> listData = new ArrayList();
String[] name = new String[]{"小明","小华","小赵","小王"};
String[] age = new int[]{15,16,17,18};
int[] height = new int[]{180,179,174,177};
int[] picture = new int[]{R.drawable.icon,R.drawable.c,R.drawable.a,R.drawable.aa,R.drawable.ww};
for(int i =0;i<name.length,i++){
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name",name[i]);
hashMap.put("age",age[i]);
hashMap.put("height",height[i]);
hashMap.put("picture",picture[i]);
listData.add(hashMap);
}
创建 SimpleAdapter SimpleAdapter 的创建是非常容易和固定的,因为它就只有一个构造方法
// 将 hashMap 的 key 组成一个字符串数组
String[] form = new String[]{"name","age","height","picture"};
// 将 item 布局中的 view 的 id 组成一个数组,要和 form 对应
int[] to = new int[]{R.id.tv_one,R.id.tv_two,R.id.tv_three,R.id.tv_four};
SimpleAdapter simpleAdapter = new SimpleAdapter(this,listData,R.layout.item_simple_adapter,form,to);
将 SimpleAdapter 绑定到 ListView 中
listView.setAdapter(simpleAdapter);
我们在实际开发过程中接触最多的就是 BaseAdapter
了。可以最大程度的定制我们自己的 item。
实现步骤
具体实现步骤
布局中添加 ListView(就不再写代码了,和上面一样
实现 item 布局(依然使用 SimpleAdapter 中的 item 布局就可以了)
创建数据源 class User{ private String name; private int age; private int height; private int picture(); get… set… 方法 } List listData = new ArrayList<>(); for(int i=0;i<20;i++){ User user = new User(); user.setName(“小明”+i); user.setAge(age); user.setHeight(height); user.setPicture(id); listData.add(user); }
创建自己的 Adapter // 继承 BaseAdapter 必须要实现它的 4 个方法 重点讲解一下 BaseAdapter 中的这四个方法
public class MyAdapter extends BaseAdapter {
// 返回适配器中所代表的数据集合的条数
// 会先执行这个方法(连续执行好几次),如果是 0 则后面的方法就不会执行了
@Override
public int getCount() {
return 0;
}
// 返回数据集合中指定索引 position 对应的数据项
// 手动调用才会执行
@Override
public Object getItem(int i) {
return null;
}
// 返回列表中与指定索引对应的行 id
// 手动调用才会执行
@Override
public long getItemId(int i) {
return 0;
}
// 返回指定索引对应的数据的视图,会多次调用
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
return null;
}
}
getView()
方法,返回我们任意想要的布局类型。getCount()
获取 ListView 的长度(item 的个数)getView()
,根据 ListView 的长度逐一绘制 ListView 的每一行getItem()
getItemId()
来获取 Adapter 中的数据重点看一下 getView
实现方式一:
直接返回索引对应的数据的视图
@Override
public View getView(int position,View convertView,ViewGroup parent){
View item = mInflater.inflater(R.layout.item,null);
TextView tv = item.findViewById(R.id.tv);
ImageView iv = item.findViewById(R.id.iv);
Button bt = item.findViewById(R.id.bt);
tv.setTextView("");
img.setImageResource("");
....各种设置
return item;
}
这是最直接的一种方式,目标很明确就是返回对应的视图。同样缺点也很明确,没有利用 ListView 对 item 的复用机制,假如有 1000 个 item 就要绘制 1000 个 view。然后再进行 findViewById 会十分消耗资源。
实现方式二:使用 convertView 作为 View 缓存
将 convertView 作为 getView 的输入参数、返回参数
借助 ListView 的缓存机制,实现 view 的复用。
@Override
public View getView(int position,View convertView,ViewGroup parent){
// 检测有无可重复使用的 View,如果没有就创建新的
// ListView 的缓存原理前面已经介绍了,从页面消失进入缓存区的 View 就会传递过来
if(convertView == null){
convertView = mInflater.inflater(R.layout.item,null);
}
TextView tv = convertView.findViewById(R.id.tv);
ImageView iv = convertView.findViewById(R.id.iv);
Button bt = convertView.findViewById(R.id.bt);
tv.setTextView("");
img.setImageResource("");
....各种设置
return convertView;
}
// 优点:减少了 View 的重新绘制,实现了 view 复用机制
// 缺点:每次都要 findViewById 寻找组件
实现方式三:在方式二的基础上,进行优化,引入 ViewHolder 减少 findViewById
class ViewHolder{
TextView tv;
ImageView iv;
Button bt;
}
@Override
public View getView(int position,View convertView,ViewGroup parent){
ViewHolder viewHolder;
if(convertView == null){
convertView = mInflater.inflater(R.layout.item,null);
viewHolder = new ViewHolder();
viewHolder.tv = convertView.findViewById(R.id.tv);
viewHolder.iv = convertView.findViewById(R.id.iv);
viewHolder.bt = convertView.findViewById(R.id.bt);
// 将 viewHolder 绑定到 convertView 中实现复用
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder)convertView.getTag();
}
viewHolder.iv.set.....
.....各种设置
return convertView;
}
// 优点:重用 View 的时候不用再次重复使用 findViewById 了。是 ListView 的最佳方案
Adapter 优化总结:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlUAbz2m-1657552024734)(https://ask.qcloudimg.com/http-save/yehe-5368985/1jfyprod1p.jpeg?imageView2/2/w/1620)]
getView 内部应做尽可能少的业务逻辑处理。因为 getView 调用很频繁。
关于可见和不可见的逻辑可以提前在数据源里面填充好。
getView 中不要出现大量的对象
最好把创建对象放到 ViewHolder 中
加载图片,滑动的时候不要加载图片,会造成 ListView 卡顿,需要在监听器里面判断 ListView 的状态。
listView.setOnScrollListener(new OnScrollListenr){
@Override
public void onScrollStateChanged(AbsListView lsitView,int scrollState){
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING){
imageLoader.stopProcessingQueue();
}else{
// 加载图片
}
}
}
https://zhuanlan.zhihu.com/p/23339185
话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。那么今天开始我们来重点学习一下RecyclerView控件,本系列文章会包括到以下三个部分:
那么今天我们首先来看第一部分:RecyclerView控件的基本使用,进阶,动画相关知识点。本次讲解所有用的Demo例子已经全部更新到下面的项目中了,欢迎大家star和fork。
FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android
通过使用RecyclerView控件,我们可以在APP中创建带有Material Design风格的复杂列表。RecyclerView控件和ListView的原理有很多相似的地方,都是维护少量的View来进行显示大量的数据,不过RecyclerView控件比ListView更加高级并且更加灵活。当我们的数据因为用户事件或者网络事件发生改变的时候也能很好的进行显示。和ListView不同的是,RecyclerView不用在负责Item的显示相关的功能,在这边所有有关布局,绘制,数据绑定等都被分拆成不同的类进行管理,下面我这边会一个个的进行讲解。同时RecyclerView控件提供了以下两种方法来进行简化和处理大数量集合:
你也可以自定义LayoutManager或者设置添加/删除的动画,整体的RecyclerView结构图如下:
为了使用RecyclerView控件,我们需要创建一个Adapter和一个LayoutManager:
Adapter:继承自RecyclerView.Adapetr类,主要用来将数据和布局item进行绑定。
LayoutManager:布局管理器,设置每一项view在RecyclerView中的位置布局以及控件item view的显
示或者隐藏.当View重用或者回收的时候,LayoutManger都会向Adapter来请求新的数据来进行替换原来数据的内容。这种回收重用的机制可以提供性能,避免创建很多的view或者是频繁的调用findViewById方法。这种机制和ListView还是很相似的。
RecyclerView提供了三种内置的LayoutManager:
当然除了上面的三种内部布局之外,我们还可以继承RecyclerView.LayoutManager来实现一个自定义的LayoutManager。
Animations(动画)效果:
RecyclerView对于Item的添加和删除是默认开启动画的。我们当然也可以通过RecyclerView.ItemAnimator类定制动画,然后通过RecyclerView.setItemAnimator()方法来进行使用。
RecyclerView相关类:
类名 | 说明 |
---|---|
RecyclerView.Adapter | 可以托管数据集合,为每一项Item创建视图并且绑定数据 |
RecyclerView.ViewHolder | 承载Item视图的子布局 |
RecyclerView.LayoutManager | 负责Item视图的布局的显示管理 |
RecyclerView.ItemDecoration | 给每一项Item视图添加子View,例如可以进行画分隔线之类 |
RecyclerView.ItemAnimator | 负责处理数据添加或者删除时候的动画效果 |
我这边实例采用Android Studio 1.3.2。
1.添加库依赖:
dependencies {
…….
compile'com.android.support:recyclerview-v7:23.1.1'
}
2.新建布局,引入RecyclerView控件:
3.在Activity中获取RecyclerView控件然后进行设置LayoutManger以及Adapter即可,和ListView的写法有点类似:
/**
* 当前类注释:RecyclerView使用实例测试demo
* 项目名:FastDev4Android
* 包名:com.chinaztt.fda.test
* 作者:江清清 on 15/11/17 15:10
* 邮箱:[email protected]
* QQ: 781931404
* 公司:江苏中天科技软件技术有限公司
*/
public class RecyclerViewTestActivity extends BaseActivity {
private LinearLayout top_bar_linear_back;
private TextView top_bar_title;
private RecyclerView recyclerView_one;
private RecyclerView.Adapter mAdapter;
private LinearLayoutManager mLayoutManager;
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recyclerview_test_layout);
top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
top_bar_title.setText("RecyclerView使用实例");
//开始设置RecyclerView
recyclerView_one=(RecyclerView)this.findViewById(R.id.recyclerView_one);
//设置固定大小
recyclerView_one.setHasFixedSize(true);
//创建线性布局
mLayoutManager = newLinearLayoutManager(this);
//垂直方向
mLayoutManager.setOrientation(OrientationHelper.VERTICAL);
//给RecyclerView设置布局管理器
recyclerView_one.setLayoutManager(mLayoutManager);
//创建适配器,并且设置
mAdapter = newTestRecyclerAdapter(this);
recyclerView_one.setAdapter(mAdapter);
}
class CustomOnClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
RecyclerViewTestActivity.this.finish();
}
}
}
4.自定义一个适配器来进行创建item view以及绑定数据
/**
* 当前类注释:RecyclerView 数据自定义Adapter
* 项目名:FastDev4Android
* 包名:com.chinaztt.fda.adapter.base
* 作者:江清清 on 15/11/18 22:29
* 邮箱:[email protected]
* QQ: 781931404
* 公司:江苏中天科技软件技术有限公司
*/
public class TestRecyclerAdapter extends RecyclerView.Adapter{
private LayoutInflater mInflater;
private String[] mTitles=null;
public TestRecyclerAdapter(Context context){
this.mInflater=LayoutInflater.from(context);
this.mTitles=new String[20];
for (int i=0;i<20;i++){
int index=i+1;
mTitles[i]="item"+index;
}
}
/**
* item显示类型
* @param parent
* @param viewType
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
//view.setBackgroundColor(Color.RED);
ViewHolder viewHolder=new ViewHolder(view);
return viewHolder;
}
/**
* 数据的绑定显示
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.item_tv.setText(mTitles[position]);
}
@Override
public int getItemCount() {
return mTitles.length;
}
//自定义的ViewHolder,持有每个Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView item_tv;
public ViewHolder(View view){
super(view);
item_tv = (TextView)view.findViewById(R.id.item_tv);
}
}
这个自定义Adapter和我们在使用Listview时候的Adapter相比还是有点不太一样的,首先这边我们需要继承RecyclerView.Adaper类,然后实现两个重要的方法onBindViewHodler()以及onCreateViewHolder(),这边我们看出来区别,使用RecyclerView控件我们就可以把Item View视图创建和数据绑定这两步进行分来进行管理,用法就更加方便而且灵活了,并且我们可以定制打造千变万化的布局。同时这边我们还需要创建一个ViewHolder类,该类必须继承自RecyclerView.ViewHolder类,现在Google也要求我们必须要实现ViewHolder来承载Item的视图。
该Demo运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9OMBUT1-1657552024735)(https://box.kancloud.cn/2016-01-18_569c8ebb1ab5c.jpg)]
上面的例子我们这边比较简单使用LinearLayoutManager来实现了,其中布局是采用垂直布局的,当然我们还可以设置线性布局的方向为横向,只要如下设置即可:
mLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
运行效果如下:
那么另外两种内置的布局如下:
1.GridLayoutManger:使用如下设置:
GridLayoutManager girdLayoutManager=new GridLayoutManager(this,4);
recyclerView_one.setLayoutManager(girdLayoutManager);
运行效果如下:
2.GridLayoutManger:使用如下设置:
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager(2,OrientationHelper.VERTICAL);
recyclerView_one.setLayoutManager(staggeredGridLayoutManager);
大家肯定观察到上面的显示效果还是比较丑,例如就没有分隔线这个效果,下面我们一起来实现以下分隔线的效果。还记得前面的一个表格中有写关于RecyclerView的相关类:
RecyclerView.ItemDecoration | 给每一项Item视图添加子View,可以进行画分隔线之类的东西 |
---|---|
我们可以创建一个继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可以让我们每一个Item从视觉上面相互分开来,例如ListView的divider非常相似的效果。当然像我们上面的例子ItemDecoration我们没有设置也没有报错哦,那说明ItemDecoration我们并不是强制需要使用,作为我们开发者可以设置或者不设置Decoration的。实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类,内部除去已经废弃的方法以外,我们主要实现以下三个方法:
public static abstract class ItemDecoration {
public void onDraw(Canvas c,RecyclerView parent, State state) {
onDraw(c, parent);
}
public void onDrawOver(Canvas c,RecyclerView parent, State state) {
onDrawOver(c, parent);
}
public void getItemOffsets(RectoutRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect,((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
又因为当我们RecyclerView在进行绘制的时候会进行绘制Decoration,那么会去调用onDraw和onDrawOver方法,那么这边我们其实只要去重写onDraw和getItemOffsets这两个方法就可以实现啦。然后LayoutManager会进行Item布局的时候,回去调用getItemOffset方法来计算每个Item的Decoration合适的尺寸,下面我们来具体实现一个Decoration。TestDecoration.java
/**
* 当前类注释:自定义实现一个Decoration分隔线
* 项目名:FastDev4Android
* 包名:com.chinaztt.fda.widget
* 作者:江清清 on 15/11/19 12:29
* 邮箱:[email protected]
* QQ: 781931404
* 公司:江苏中天科技软件技术有限公司
*/
public class TestDecoration extends RecyclerView.ItemDecoration {
//采用系统内置的风格的分割线
private static final int[] attrs=newint[]{android.R.attr.listDivider};
private Drawable mDivider;
public TestDecoration(Context context) {
TypedArray typedArray=context.obtainStyledAttributes(attrs);
mDivider=typedArray.getDrawable(0);
}
/**
* 进行自定义绘制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int top=parent.getPaddingTop();
intbottom=parent.getHeight()-parent.getPaddingBottom();
int childCount=parent.getChildCount();
for(int i=0;i
我这边实例中采用系统主题(android.R.attr.listDivider)来设置成分隔线的,然后来获取尺寸,位置进行setBound(),绘制,接着通过outRect.set()来设置绘制整个区域范围,最后不要忘记往RecyclerView中设置该自定义的分割线,
//添加分隔线
recyclerView_one.addItemDecoration(newTestDecoration(this));
运行效果如下:
上面的分割线效果只是垂直画了分割线,但是我们水平方向也要进行画分割线,那么我们下面对于自定义的Decoration进行改进,AdvanceDecoration.java就出现啦。
/**
* 当前类注释:改进之后的自定义Decoration分割线
* 项目名:FastDev4Android
* 包名:com.chinaztt.fda.widget
* 作者:江清清 on 15/11/19 12:53
* 邮箱:[email protected]
* QQ: 781931404
* 公司:江苏中天科技软件技术有限公司
*/
public class AdvanceDecoration extends RecyclerView.ItemDecoration{
//采用系统内置的风格的分割线
private static final int[] attrs=newint[]{android.R.attr.listDivider};
private Drawable mDivider;
private int orientation;
public AdvanceDecoration(Contextcontext,int orientation) {
TypedArray typedArray=context.obtainStyledAttributes(attrs);
mDivider=typedArray.getDrawable(0);
typedArray.recycle();
this.orientation=orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHDeraction(c,parent);
drawVDeraction(c,parent);
}
/**
* 绘制水平方向的分割线
* @param c
* @param parent
*/
private void drawHDeraction(Canvas c,RecyclerView parent){
int left=parent.getPaddingLeft();
intright=parent.getWidth()-parent.getPaddingRight();
int childCount=parent.getChildCount();
for(int i=0;i
改良之后的自定义分割器的构造函数中新增一个int参数,用来表示横向还是纵向布局,这样我们可以分别来绘制分割线。具体使用方法:
recyclerView_one.addItemDecoration(newAdvanceDecoration(this,OrientationHelper.VERTICAL));
运行比较效果如下:
1.横向
2.纵向
我们知道在ListView使用的时候,该控件给我们提供一个onItemClickListener监听器,这样当我们的item发生触发事件的时候,会回调相关的方法,以便我们方便处理Item点击事件。对于RecyclerView来讲,非常可惜的时候,该控件没有给我们提供这样的内置监听器方法,不过我们可以进行改造实现。我们先来看一下之前我们写得TestRecyclerAdapter中的onCreateViewHolder()方法中的代码:
public ViewHolder onCreateViewHolder(ViewGroupparent, int viewType) {
Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
//这边可以做一些属性设置,甚至事件监听绑定
//view.setBackgroundColor(Color.RED);
ViewHolder viewHolder=newViewHolder(view);
return viewHolder;
}
该方法创建一个ViewHolder,其中承载的就是每一项Item View视图,那么我们可以在view创建出来之后给它进行添加相应的属性或者监听方法,例如:背景颜色,大小,以及点击事件。既然可以这样解决,OK,我们给View添加一个onClickListener监听器,然后点击的时候回调onClick()方法。同时我们需要自定义一个类似于onItemClickListener()的监听器来处理。
/**
* 自定义RecyclerView 中item view点击回调方法
*/
interface OnRecyclerItemClickListener{
/**
* item view 回调方法
* @param view 被点击的view
* @param position 点击索引
*/
void onItemClick(View view, intposition);
}
然后声明以及Adapter初始化的时候传入进去:
public TestRecyclerAdapter(Contextcontext,OnRecyclerItemClickListener onRecyclerItemClickListener){
……..
this.onRecyclerItemClickListener=onRecyclerItemClickListener;
}
然后我们在onClick回调方法中调用OnRecyclerItemClickListener接口的方法。
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onRecyclerItemClickListener!=null){
onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());
}
}
});
上面的onItemClick中第二个参数的position,采用view.getTag的方法获取,那么我们就需要在onBindViewHolder()方法中设置一个tag了.
public voidonBindViewHolder(ViewHolder holder, int position) {
holder.item_tv.setText(mTitles[position]);
holder.itemView.setTag(position);
}
最后我们在外部使用一下接口:
mAdapter = new TestRecyclerAdapter(this, new TestRecyclerAdapter.OnRecyclerItemClickListener() {
@Override
public void onItemClick(View view,int position) {
Toast.makeText(RecyclerViewTestActivity.this, "点击了第"+position+"项", Toast.LENGTH_SHORT).show();
}
});
运行结果如下:
讲了以上RecyclerView中各种用户,处理之后,现在我们来看一下当数据发生变化之后的处理。例如我们在使用ListView的时候,当数据发生变化的时候可以通过notifyDatasetChange()来刷新界面。对于RecyclerView控件来讲,给我们提供更加高级的使用方法notifyItemInserted(position)和notifyItemRemoved(position)
我们可以在TestRecyclerAdapter中添加数据新增和数据删除的方法如下:
//添加数据
public void addItem(String data, intposition) {
mTitles.add(position, data);
notifyItemInserted(position);
}
//删除数据
public void removeItem(String data) {
int position = mTitles.indexOf(data);
mTitles.remove(position);
notifyItemRemoved(position);
}
然后我们在Activity中进行调用即可:
//添加数据
mAdapter.addItem("additem",5);
//删除数据
mAdapter.removeItem("item4");
在运行之前我们不要忘记RecyclerView给提供了动画设置,我这边就直接采用了默认动画,设置方法如下:
//添加默认的动画效果
recyclerView_one.setItemAnimator(new DefaultItemAnimator());
最终运行效果如下图:
到此为止就完成我们RecyclerView控件使用的第一讲内容,其中包括控件的基本介绍,基本使用,高级用法(自定义间隔符,加入点击监听事件以及Item添加删除动画处理)。总体来讲RecyclerView控件是非常不错,尤其在布局以及数据绑定,动画方面,除了系统内置的三种布局方式之外,我们还可以定制出我们自己的布局管理器。同时当item数据发生变化的时候还给我们提供非常炫的效果。相信大家在今天这一讲之后,会越来越爱上RecyclerView控件的使用,从此可以抛弃ListView和GridView啦.
本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:
https://github.com/jiangqqlmj/FastDev4Android