在上一篇中介绍了ListView和RecyclerView的使用流程,这一篇将介绍一些小的不同点。
首先是空数据处理的不同。ListView提供了setEmptyView这个API让我们处理Adapter中数据为空的情况。设置代码和效果如下:
listView = findViewById(R.id.fruit_listview);
listView.setEmptyView(findViewById(R.id.test_empty));
在这里需要特别注意R.id.test_empty,通过代码来给出说明
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/fruit_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include
android:id="@+id/test_empty"
layout="@layout/item_empty"/>
</LinearLayout>
从上面的代码中我们可以看到test_empty是和ListView处于同一布局中的一个需要在空数据使需要展示的layout的id,这一点很重要。如果我们单独建立一个新的layout使它不处于当前ListView的同一布局中,你会发现没有任何效果。
RecyclerView中没有空数据的处理方式,需要我们自己来完成。我发现最简单有效的方法是在RecyclerView的外面嵌套一个布局,然后将空数据时需要展示的图片设置为布局的背景,当发现没有数据时让背景显示就可以了。
第二点是HeaderView和FootView两种类型的视图。ListView中提供了相关的API让我们设置,当我们只想在ListView的头部或者底部添加一个View的时候,可以不用影响到Adapter的编写。
HeaderView的简单使用:
首先是头部View的layout布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/header_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@mipmap/nodata"/>
<TextView
android:id="@+id/header_txt"
android:text="用来测试头部和底部View"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
</LinearLayout>
然后我们生成头部view并且设置给ListView
//绑定ListView控件
listView = findViewById(R.id.fruit_listview);
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.header_item,null);
TextView test = view.findViewById(R.id.header_txt);
test.setText("没问题了");
String text = "测试";
listView.addHeaderView(view,text,true);
//生成我们前面写的适配器
Adapter adapter = new Adapter(MainActivity.this,R.layout.fruit_list_item,FruitList);
//ListView绑定适配器
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
//在这里面实现具体操作
String s = (String) adapterView.getItemAtPosition(0);
Toast.makeText(MainActivity.this,s,Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this,i+"",Toast.LENGTH_SHORT).show();
}
});
我注意到addHeaderView()方法中有三个参数,但是第二个参数却不知道有什么作用。查阅了大量的文章之后,其实第二个参数Object data是Adapter中getItem获得的值。从上面的代码可以看出在item受到点击之后我会让Toast显示第一个Item,也就是HeaderView中getItem获取到的值,并且让Toast显示被点击item的当前位置,接下来我们看效果。
这下我们就知道第二个参数有什么作用了。此外,当我们点击列表中第三项的item时显示的位置是3,所以说我们的headView的位置是0,它也算作整个ListView的一项,这并不难理解。需要注意的是addHeaderView()要在ListView设置Adapter之前。
RecyclerView没有设计HeaderView和FootView,只能我们自己去完成。简单粗暴的方式就是添加ViewType,根据类型生成不同的item。从网上看到另外一种思路,通过装饰者模式扩充Adapter的功能,从而实现添加HeaderView和FooterView,并且不影响Adapter的编写工作,后面会单独学习。
接下来我们看局部刷新。在更新了ListView的数据源之后,需要通过Adapter的notifyDataSetChanged来通知视图更新变化,好处是比较简单,坏处是会重绘每个item,但实际上并不是每个Item都需要重绘。我在Adapter中加了一个方法:
public void updateItemView(ListView listView, int position, Fruit newFruit){
int index = position - listView.getFirstVisiblePosition();
if (index >= 0 && index < listView.getChildCount() ){
getItem(position).setFruitName(newFruit.getFruitName());
getItem(position).setImageId(newFruit.getImageId());
}
View view = listView.getChildAt(position);
getView(position,view,listView);
}
然后我们点击第0个Item,然后改变第1个Item的内容来模仿数据更新的过程,代码和效果如下:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (i == 0){
Fruit f = new Fruit("我是牛奶",R.mipmap.cow);
adapter.updateItemView(listView,1,f);
}
}
});
RecyclerView.Adapter为我们提供了notifyItemChanged()用于更新单个ItemView的刷新。代码和使用效果如下:
dataList.get(1).setName("我是齐天大圣孙悟空");
dataList.get(1).setImageId(R.mipmap.monkey);
adapter.notifyItemChanged(1);
最后就关于动画效果,RecyclerView给我们提供了一些Item动画相关的 API,ListView则没有。下面简单看一下RecyclerView的Item动画效果。
RecyclerView.ItemAnimator是一个抽象类,该抽象类有一个子类SimpleItemAnimator,不过也是一个抽象类,我们通常用的是它的孙子DefaultItemAnimator。在DefaultItemAnimator中有Add、Remove、Move、Change四种动画效果。
需要注意的是,要实现这四种动画效果,数据更新时不能通过
Adapter.notifyDataSetChanged();
而是通过:
Adapter.notifyItemInserted(int position)
Adapter.notifyItemRemoved(int position)
Adapter.notifyItemChanged(int position)
Adapter.notifyItemMoved(int fromPosition, int toPosition)
接下来通过几行代码看一下效果:
DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
defaultItemAnimator.setAddDuration(1000);
recycler.setItemAnimator(defaultItemAnimator);
Fruit f = new Fruit("我是齐天大圣孙悟空",R.mipmap.monkey);
dataList.add(defaultP,f);
adapter.notifyItemInserted(defaultP);