在上一篇中介绍了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);