Android中RecyclerView的使用与解析
用惯了ListView的我最近觉得RecyclerView是越来越好用了,不仅自己不再需要用ViewHolder做种种优化,布局上也更加自由了。本文着重对于RecyclerView的基本原理做一些解析(特别是Adapter的作用和调用过程),并且说一下基本的使用方法。
一、RecyclerView是什么?
中文翻译过来就是循环使用的View,官方把它作为ListView的升级版。为什么说是升级版呢?因为之前我们为了达到“列表”的效果,有很多控件要学习使用,普通点的ListView,支持多行多列的GridView等等。现在只要一个RecyclerView,你就可以实现这各种各样的效果。
总的来说它有两个优点:
1、封装了对ViewHolder的复用,让你面向ViewHolder去编写Adapter,不再需要自己额外的优化了。
2、把列表显示控件进行了高度的解耦,把不同需求的功能进行了模块化的划分,给你提供了非常灵活的编写方式。把显示方式用LayoutManager来控制,间隔用ItemDecoration来控制,增删动画用ItemAnimator来控制,想要改变只需要替换相应的功能模块,是不是非常灵活!
二、RecyclerView的基本API
用惯了ListView,我们想必也不会陌生RecyclerView,下面来看看使用的代码:
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this );
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置Adapter
recyclerView.setAdapter( recycleAdapter);
//设置分隔线
recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//设置增加或删除条目的动画
recyclerView.setItemAnimator( new DefaultItemAnimator());
这里基本上把所有可以自定义的部分都试了一遍,看惯了ListView的肯定会觉得这个RecyclerView怎么这么麻烦。但我已经说了这上面把所有的自定义部分都尝试了,如果只是简单的显示,很简单的代码就可以完成。
三、RecyclerView中的Adapter使用与解析(重要)
我们来看一个最简单的显示例子:
先给大家看一下效果,和传统的ListView没有什么区别:
界面很简单,放一个RecyclerView的Layout即可
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.lee.recyclerviewtest.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/id_rev_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
然后是我们很熟悉的列表项的布局(这个和ListView一样)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/id_main_text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="测试"
android:textSize="20sp"/>
</LinearLayout>
之后是最重要的Adapter,注意观察之中的特殊之处
package com.example.lee.recyclerviewtest;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import java.util.zip.Inflater;
/**
* Created by Lee on 2016/9/28.
*/
public class MainListAdapter extends RecyclerView.Adapter<MainListAdapter.MainListViewHolder>{
private List<String> mStringList;
private LayoutInflater mLayoutInflater;
public MainListAdapter(Context context, List<String> strings){
mLayoutInflater = LayoutInflater.from(context);
mStringList = strings;
}
@Override
public MainListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = mLayoutInflater.inflate(R.layout.item_layout_main_list, parent, false);
return new MainListViewHolder(itemView);
}
@Override
public void onBindViewHolder(MainListViewHolder holder, int position) {
holder.mainTextView.setText(mStringList.get(position));
}
@Override
public int getItemCount() {
return mStringList.size();
}
public class MainListViewHolder extends RecyclerView.ViewHolder{
public TextView mainTextView;
public MainListViewHolder(View itemView){
super(itemView);
mainTextView = (TextView) itemView.findViewById(R.id.id_main_text_view);
}
}
}
第一次从ListView转过来的时候对于这个Adapter肯定非常熟悉了,但RecyclerView中的Adapter又和传统的ListView中有些区别,我们来看一下传统的ListView的Adapter,从中找出区别能让你更快速理解这个新的Adapter:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ListViewAdapter.ViewHolder holder = null;
if(convertView == null){
holder = new ListViewAdapter.ViewHolder();
convertView = mInflater.inflate(R.layout.item_layout_main_list, null);
holder.mainTextView = (TextView) convertView.findViewById(R.id.id_main_text_view);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mainTextView.setText(mStringList.get(position));
return convertView;
}
class ViewHolder{
TextView mainTextView;
}
这是传统Adapter中最重要的一个方法(我附加了ViewHolder,以便对比),getView是ListView中用于得到每个项View的接口,最终在obtainView函数中调用。我们来先简单了解一下ListView中Adapter接口的调用过程:
1、调用getView之前,ListView会从缓存中尝试取出缓存的View(当你的某项在屏幕中不可见,它就会进入缓存之中),之后把它作为第二个参数传入getView函数中(如果没有就是null),所以我们在getView中做了判断;
2、如果没有缓存(第一次加载时),我们就从resourceId中创建View,之后进入第二个优化,ViewHolder,为了防止每次都调用findViewById,我们把view中的对应控件用holder存下来并且和view对应起来(Tag);
3、得到了Holder,这个时候无论是新创建的View还是得到的缓存,我们都要对它的数据进行设置,这里我们成为绑定数据,把数据绑定到对应的View上,也就完成了整个getView的过程。
那么我们来看看RecyclerView的Adapter怎么分解这个过程的:
首先,RecyclerView中操作单位不再是View了,而直接是ViewHolder,有两点区别:
1、直接省去了每次都要find的过程,直接用标准的ViewHolder来操作,你创建ViewHolder时就已经把控件找到并且得到了引用,这个过程被放在了构造函数中。
2、缓存的数据不再是View,直接用ViewHolder来进行缓存,如果没有缓存,创建的对象也是ViewHolder(你可以发现onCreateViewHolder的返回值类是ViewHolder)
其实你理解了ListView的操作,你直接通过函数名就可以理解RecyclerView的Adapter接口调用过程:
1、获取ViewHolder:首先RecyclerView尝试获取ViewHolder的缓存,如果没有缓存就调用onCreateViewHolder函数来创建,这其中完成从资源生成View的过程并且根据View创建对应的ViewHodler(构造函数完成find操作):
@Override
public MainListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = mLayoutInflater.inflate(R.layout.item_layout_main_list, parent, false); //生成View
return new MainListViewHolder(itemView); //返回的是ViewHolder对象
}
在ViewHolder的构造函数中,我们可以看到对应的itemView,而之前setTag的绑定工作就不需要我们去完成了,因为itemView本身就是Holder的一个变量,直接就一一对应了,我们只需要在之后调用相应的findViewById来获取相应控件的引用即可
public MainListViewHolder(View itemView){
super(itemView); //父类完成绑定
mainTextView = (TextView) itemView.findViewById(R.id.id_main_text_view);
//获取引用,方便绑定数据(避免多次嗲用findViewById)
}
2、绑定数据:调用onBindViewHolder,绑定数据,把相应的数据绑定到ViewHolder上,即通过相应的控件引用设定相应数据(这样才能正确显示)。
@Override
public void onBindViewHolder(MainListViewHolder holder, int position) {
holder.mainTextView.setText(mStringList.get(position)); //绑定数据
}
我们可以看出onCreateViewHolder只有在第一次创建View的时候才调用,而onBindViewHolder是每次绑定数据都需要的。
通过这个分析我们可以发现,RecyclerView中的Adapter过程和ListView的Adapter过程是密切相关的,官方只是把我们之前需要自己去做的优化标准化了,并且省略了自己去判断的过程,用RecyclerView只需要去完善相应的接口即可。
在了解了这么一个详细的调用过程之后,我们来看一下Activity中的简单代码:
package com.example.lee.recyclerviewtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView mMainRecyclerView;
private List<String> mNames = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews(){
mNames.add("1");
mNames.add("2");
mNames.add("3");
mNames.add("4");
mNames.add("5");
mNames.add("6");
mNames.add("7");
mNames.add("8");
mNames.add("9");
mNames.add("10");
mMainRecyclerView = (RecyclerView) findViewById(R.id.id_rev_main);
mMainRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mMainRecyclerView.setAdapter(new MainListAdapter(this, mNames));
}
}
四、RecyclerView中Item点击事件设置
ListView可以通过setOnItemClickListener来处理item的点击事件,但RecyclerView并没有提供这个接口,那么在RecyclerView中怎么来实现呢?我们可以定义一个接口,并且注入到Adapter中,在绑定数据的时候,在View的点击事件中回掉这个函数即可。
具体做法如下所示:
1、定义接口(可以定义在Adapter中)
public static interface OnItemClickListener{
public void onClick(View view,int position);
}
2、在Adapter中加入Listener变量
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener listener){
mOnItemClickListener = listener;
}
3、在绑定数据中增加回调过程
@Override
public void onBindViewHolder(MainListViewHolder holder, final int position) {
holder.mainTextView.setText(mStringList.get(position)); //绑定数据
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mOnItemClickListener != null){
mOnItemClickListener.onClick(v, position); //回调函数
}
}
});
}
从回调的这个语句中的holder.itemView我们也可以看出我们之前得出的结论,官方的ViewHolder是具有itemView的引用的(通过这个方式让Holder和itemView一一对应起来)
如果是长按呢?长按同理可得,只需要在接口中加上一个回调即可,如下所示:
1、接口中加入新的函数
public void onLongClick(View view,int position);
2、在绑定数据时多加一个语句
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(mOnItemClickListener!= null){
mOnItemClickListener.onLongClick(v, position);
}
return false;
}
});
当然你如果只想实现其中一个或者分别用两个也是完全可以的。
五、RecyclerView中的LayoutManager
这是RecyclerView的另一个特点,也是非常赞的特性!刚在的代码中这一句用惯了ListView肯定没有见过:
mMainRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
这是RecyclerView模块化解耦的一个巨大优势,你可以通过LayoutManager来轻松的实现显示方式的改变,在RecyclerView进行布局的过程中会调用LayoutManager的onLayoutChildren来进行布局,从而让自定义布局变得更加简单和轻松。
作为初学者,自己实现LayoutManager确实有些难了,但系统已经实现了三个常用的布局方式,通过三个实现类:
1、LinearLayoutManager 线性布局管理器,支持水平和垂直两个方向。
2、GridLayoutManager 网格布局管理器
3、StaggeredGridLayoutManager 瀑布式网格布局管理器
我们上面用的就是简单的LinearLayoutManager,我们来看一看网格式,只需要在设置LayoutManager时做一点改动即可,将上面那个语句改为:
mMainRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
我们来看看效果:
是不是觉得非常赞!只需要简单的修改就可以得到这个效果,让我们再来看看瀑布式的效果如何,我又增加了一些项,调整了一下大小,让我们来看一看:
(这里如果是HORIZONTAL就表示有4行,如果是VERTICAL那么就是4列)
mMainRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL));
轻松实现瀑布效果,只需要不同的LayoutManager即可。
讲了这么多,我们已经掌握了RecyclerView的基本原理和用法,理解了这个Adapter的调用过程和原理,算是已经入门了。但RecyclerView提供的不仅仅是我介绍的这些,它还提供了分隔线和删除动画的效果,这里我觉得单纯的使用也没有必要写了,我着重讲解得是对于Adapter函数的调用过程和解耦的理解,毕竟理解了过程才能更好地使用它。如果是单纯的使用,我推荐一篇博文,上面对于使用讲解的比较详细大家可以去看一下:
http://blog.youkuaiyun.com/lmj623565791/article/details/45059587
希望我的解析能够让大家更好地理解RecyclerView的原理和使用!