Android控件之Recyclerview应用分析

一、引言

RecyclerView出现已经有一段时间了,是Android 5.0提供的新控件,作为一名码农,日常开发中多多少少有接触一些。关于RecyclerView,官方文档中是这样介绍的:

A flexible view for providing a limited window into a large data set.

从文字间可以看出,flexible(可扩展性)是RecyclerView的特点。一个用于大量数据展示的新控件,可以用来代替传统的ListView、GridView,可以有效的重用和滚动,更加强大和灵活。

二、RecyclerView介绍

RecylerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。

RecylerView架构是提供了一种插拔式的体验,高度的解耦,通过设置LayoutManager、ItemDecoration、ItemAnimator实现各种效果。RecyclerView的四大组成是:

  • Layout Manager:Item的布局
  • Adapter:为Item提供数据
  • Item Decoration:Item之间的Divider
  • Item Animator:添加、删除Item动画

三、RecyclerView应用

3.1 导入依赖

compile 'com.android.support:recyclerview-v7:23.2.1'
compile 'com.android.support:appcompat-v7:23.3.0'

3.2 布局

 <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@null"/>

3.3 设置RecyclerView

    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    //设置布局管理器
    recyclerView.setLayoutManager(layoutManager);
    //设置为垂直布局,这也是默认的
    layoutManager.setOrientation(OrientationHelper.VERTICAL);
    //设置Adapter
    recyclerView.setAdapter(recycleAdapter);
     //设置分隔线
    recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
    //设置增加或删除条目的动画
    recyclerView.setItemAnimator( new DefaultItemAnimator());

实例:

RecyclerView控件定义:

    @Bind(R.id.rv_list)
    RecyclerView rvFiles;

RecyclerView配置:

    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    rvFiles.setHasFixedSize(true);
    rvFiles.setLayoutManager(layoutManager);
    mAdapter = new FileAdapter(this);
    rvFiles.setAdapter(mAdapter);

Adapter代码,在Adapter中实现3个方法:

  • onCreateViewHolder():这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder
  • onBindViewHolder():这个方法主要用于适配渲染数据到View中
  • getItemCount():这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目
public class FileAdapter extends RecyclerView.Adapter<FileAdapter.ViewHolder> {

    private static final String TAG = FileAdapter.class.getSimpleName();

    private Context mContext;
    private ArrayList<String> files = new ArrayList<>();
    private ArrayList<MusicInfo> musics = new ArrayList<>();
    private OnItemClickListener listener;

    public FileAdapter(Context context) {
        mContext = context;
    }

    public void setFilesData(ArrayList<String> files) {
        this.files.clear();
        this.files.addAll(files);
    }

    public void setMusicsData(ArrayList<MusicInfo> musics) {
        this.musics.clear();
        this.musics.addAll(musics);
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }

    @Override
    public FileAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        FileAdapter.ViewHolder holder = new FileAdapter.ViewHolder(
                LayoutInflater.from(mContext).inflate(R.layout.item_local_file, parent, false));
        return holder;
    }

    public void onBindViewHolder(FileAdapter.ViewHolder mHolder, final int position) {
        if (position < files.size()) {
            String str = files.get(position);
            File file = new File(str);
            mHolder.tvTitle.setText(file.getName());
            mHolder.tvTitle.setTextColor(mContext.getResources().getColor(R.color.text_deep_black));
            mHolder.iv_track.setImageResource(R.drawable.ic_folder_folder);
        } else {
            mHolder.tvTitle.setText(musics.get(position - files.size()).getTitle());
            if (CSmartApplication.getInstance().getCloudMusic()) {
                mHolder.tvTitle.setTextColor(mContext.getResources().getColor(R.color.text_deep_black));
                mHolder.iv_track.setImageResource(R.drawable.ic_folder_music);
            } else {
                if (MediaManager.getInstance().getMusicInfo() != null
                        && MediaManager.getInstance().getMusicInfo().getUrl().equals(musics.get(position - files.size()).getUrl())) {
                    mHolder.tvTitle.setTextColor(mContext.getResources().getColor(R.color.color_pink));
                    mHolder.iv_track.setImageResource(R.drawable.ic_folder_musicselect);
                } else {
                    mHolder.tvTitle.setTextColor(mContext.getResources().getColor(R.color.text_deep_black));
                    mHolder.iv_track.setImageResource(R.drawable.ic_folder_music);
                }
            }

        }
        mHolder.layItem.setOnClickListener(new DoubleClick.onNoDoubleClickListener() {
            @Override
            protected void onNoDoubleClick(View v) {
                listener.onItemClick(position);
            }
        });
    }

    public Object getItem(int i) {
        if (i < files.size()) {
            return files.get(i);
        } else {
            return musics.get(i);
        }
    }

    @Override
    public int getItemCount() {
        return files.size() + musics.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.tv_title)
        TextView tvTitle;
        @Bind(R.id.img_track)
        ImageView iv_track;
        @Bind(R.id.lay_item)
        RelativeLayout layItem;

        ViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }

    public interface OnItemClickListener {
        void onItemClick(int position);
    }

}

效果如下:



在使用RecyclerView时候,必须指定一个适配器Adapter和一个布局管理器LayoutManager。适配器继承RecyclerView.Adapter类,具体实现类似ListView的适配器,取决于数据信息以及展示的UI。布局管理器用于确定RecyclerView中Item的展示方式以及决定何时复用已经不可见的Item。

四、RecyclerView 和 ListView的差别

从上面实例可以看到,只要用Android提供的LieanerLayoutManager并配以VERTICAL模式,RecyclerView就可以完美达到ListView的基本效果。两者的设计结构也都是数据(Dataset)与视图(View)分离,然后通过适配器(Adapter)来连接的方式。所以还是有必要看下有什么不同的地方:

4.1 RecyclerView 和 ListView 布局效果的对比

Android 默认提供的 RecyclerView 就能支持线性布局、网格布局、瀑布流布局三种,而且同时还能够控制横向还是纵向滚动,这些功能比ListView、GridView强大多了。

4.2 RecyclerView 和 ListView 一些常用的功能 和 API 的对比

当然,一个控件不能完全只看效果,关键还是要看实用性,看看有没有方便开发者调用的 API提高开发效率。所以,接下来就从看看 RecyclerView 和 ListView 在提供的API调用上的一些实践比较。

4.2.1 基础使用

继承重写 BaseAdapter 类
自定义 ViewHolder 和 convertView 一起完成复用优化工作

RecyclerView 基础使用关键点同样有两点:

  • 继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 设置布局管理器,控制布局效果

从基础使用上看,我们明显可以看出,RecyclerView 相比 ListView 在基础使用上的区别主要有如下几点:

  • ViewHolder 的编写规范化了
  • RecyclerView 复用 Item 的工作 控件 全帮你搞定,不再需要像 ListView 那样自己调用 setTag
  • RecyclerView 需要多出一步 LayoutManager 的设置工作
4.2.2 布局效果

前面提到,RecyclerView 能够支持各种各样的布局效果,这是 ListView 所不具有的功能,其核心关键在于 RecyclerView.LayoutManager 类中。从前面的基础使用可以看到,RecyclerView 在使用过程中要比 ListView 多一个 setLayoutManager 步骤,这个 LayoutManager 就是用于控制 RecyclerView 最终的展示效果的。

而 LayoutManager 只是一个抽象类而已,系统已经提供了三个相关的实现类 LinearLayoutManager(线性布局效果)、GridLayoutManager(网格布局效果)、StaggeredGridLayoutManager(瀑布流布局效果)。如果想用 RecyclerView 来实现特定的效果,则应该去继承实现自己的 LayoutManager,并重写相应的方法,而不应该想着去改写 RecyclerView。关于 LayoutManager 的使用有下面一些常见的 API(有些在 LayoutManager 实现的子类中)

canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置

setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向

findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置

这里仅仅是列出一些常用的 API 而已,更多的 API 可以查看官方文档,通常你想用 RecyclerView 实现某种效果,例如指定滚动到某个 Item 位置,但是在 RecyclerView 中又找不到可以调用的 API 时,就可以跑到 LayoutManager 的文档去看看,基本都在那里。

4.2.3 空数据处理

ListView 提供了 setEmptyView 这个 API 来让我们处理 Adapter 中数据为空的情况,只需轻轻一 set 就能搞定一切。而 RecyclerView 并没有提供此类 API,所以,这些工作需要自己来处理了。

4.2.4 局部刷新

在 ListView 中,刷新通常用到 notifyDataSetChanged() ,但是说到局部刷新估计就知道得比较少了。更新了 ListView 的数据源后,需要通过 Adapter 的 notifyDataSetChanged 来通知视图更新变化,这样做比较的好处就是调用简单,坏处就是它会重绘每个 Item,但实际上并不是每个 Item 都需要重绘。

RecyclerView.Adapter 则我们提供了 notifyItemChanged 用于更新单个 Item View 的刷新,可以省去自己写局部更新的工作。

4.2.5 监听 Item 的事件

ListView 有几个专门用于监听 Item 的回调接口,如单击、长按、选中某个 Item 等。而RecyclerView 并没有像 ListView 提供太多关于 Item 的某种事件监听,唯一的就是 addOnItemTouchListener。

五、总结

这里只是初步的分析一些使用上的差异,存在即合理,开发人员可以根据自己的使用场景来选择是要用 RecyclerView 还是 ListView,毕竟,合适的才是最好的。


扩展

RecyclerView是基础控件,而总有热心的人在些基础上做多样化的封装,如SwipeRecyclerView:

  • SwipeRecyclerView:RecyclerView侧滑菜单,Item拖拽,滑动删除Item,自动加载更多,HeaderView,FooterView,Item分组黏贴