1. 引言
上两章简单讲了下ListView的一些基本用法。ListView由于其强大的功能,在Android 开发的过程中贡献卓越。但是由于它自身的部分缺点,不使用一些技巧来提高它的运行效率的话,它的性能体验会非常的差。还有就是ListView的扩展性不够完美,我们在实现数据纵向排列的同时,还需要数据的横向排列。
RecyclerView可以说是ListView的增强版,不仅可以轻松实现和ListView同样的效果,还会优化ListView存在的各种问题。目前官方更推荐RecyclerView,相信未来也会有更多的程序逐渐从ListView转向Recyclerview。
2. RecyclerView的基本用法
RecyclerView也属于新增的控件,所以为了能够使用RecyclerView,首先得在项目的build.gradle的dependencies中添加相应的依赖库。
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile 'com.android.support:recyclerview-v7:25.3.1'
}
添加完依赖库之后,Sync Now来进行同步,修改activity_main.xml文件中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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.demo.recyclerviewdemo.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
在你添加完了RecyclerView的依赖包之后,添加依赖控件的,记得显示完整的包名。为了和之前的ListView的项目有一个对比,这里用同样的图和布局模式。
1.item_list.xml ,展示子项目的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center_vertical"
android:padding="10dp">
<ImageView
android:id="@+id/iv_head"
android:layout_width="80dp"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/tv_student_name"
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="学生姓名"
android:layout_gravity="center"
android:gravity="center"/>
</LinearLayout>
2.Student.java 学生的实体类
public class Student {
private String name;
private int imageId;
public Student(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
}
- 自定义一个适配器,让这个适配器继承RecyclerView.Adapter ,并将泛型指定为StudentAdapter.ViewHolder,其中ViewHolder是我们自己定义的一个内部类。
public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {
private List<Student> mStudentList;
public StudentAdapter(List<Student> mStudentList) {
this.mStudentList = mStudentList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Student student=mStudentList.get(position);
holder.studentImage.setImageResource(student.getImageId());
holder.studentname.setText(student.getName());
}
@Override
public int getItemCount() {
return mStudentList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
private ImageView studentImage;
private TextView studentname;
public ViewHolder(View itemView) {
super(itemView);
studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
}
}
}
可能刚开始看的时候有点晕,哪里好理解了?我发四没有骗你,你等我慢慢给你分析下。首先我们定义了一个内部类 ViewHolder 继承RecyclerView.ViewHolder,然后ViewHolder下面会有一个构造函数ViewHolder(itemView)需要让你实现,你发现没有,你在构造函数传的这个View参数,其实就是RecyclerView中的子项的最外层布局。所以我们就可以通过findviewByid的方法来获取布局中实例。
然后我们接下来继续分析,StudentAdapter 中也有一个构造函数,这个方法是用于将要展示的数据源传进来,并赋值给全局变量,后续的操作都是围着这个操作进行的。
继续往下看,由于StudentAdapter 是继承RecyclerView.Adapter的,所以必须重写 onCreateViewHolder(), onBindViewHolder(),getItemCount()三个方法。 onCreateViewHolder()这个方法是创建ViewHolder 实例,然后将item_list.xml这个子布局也装载进去,并把加载出来的布局传入到构造函数当中,最后返回ViewHolder的实例。onBindViewHolder()这个方法是对RecyclerView的子项的数据进行赋值,会在子项被滚动到屏幕中的时候被执行。我们通过get(position)的方法得到当前项的Student的实例。getItemCount(),告诉RecyclerView,这个集合有多少个子项。直接返回数据的长度。
4.开始使用RecyclerView了。
public class MainActivity extends AppCompatActivity {
private List<Student> studentList=new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initStudent();//初始化学生数据
RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
StudentAdapter adapter=new StudentAdapter(studentList);
recyclerView.setAdapter(adapter);
}
private void initStudent() {
for (int i = 0; i < 2; i++) {
Student student1=new Student("学生一",R.mipmap.a1);
studentList.add(student1);
Student student2=new Student("学生二",R.mipmap.a2);
studentList.add(student2);
Student student3=new Student("学生三",R.mipmap.a3);
studentList.add(student3);
Student student4=new Student("学生四",R.mipmap.a4);
studentList.add(student4);
Student student5=new Student("学生五",R.mipmap.a5);
studentList.add(student5);
Student student6=new Student("学生六",R.mipmap.a6);
studentList.add(student6);
Student student7=new Student("学生七",R.mipmap.a7);
studentList.add(student7);
Student student8=new Student("学生八",R.mipmap.a8);
studentList.add(student8);
Student student9=new Student("学生九",R.mipmap.a9);
studentList.add(student9);
Student student10=new Student("学生十",R.mipmap.a10);
studentList.add(student10);
Student student11=new Student("学生十一",R.mipmap.a11);
studentList.add(student11);
Student student12=new Student("学生十二",R.mipmap.a12);
studentList.add(student12);
Student student13=new Student("学生十三",R.mipmap.a13);
studentList.add(student13);
}
}
}
可以看到我们使用同样的办法initStudent()初始化所有的学生对象,接着在OnCreate()方法中线获取到RecyclerView的实例。然后创建一个LinearLayoutManager对象,将它设置到RecyclerView当中,这里使用LinearLayoutManager是线性布局的意思。可以实现和ListView类似的效果。然后是创建Studentadapter的实例,将studentList实例数据传到StudentAdapter的构造函数当中。最后调用setAdapter()方法完成适配器的设置。这样RecyclerView和数据之间的关联就建立完成了。效果如下图所示:
3.实现横向滚动
之前说了ListView不能设置横向排列,RecyclerView可以很好的弥补这个不足,使用方法很简单,在MainActivity加入一行代码就可以了。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initStudent();//初始化学生数据
RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
StudentAdapter adapter=new StudentAdapter(studentList);
recyclerView.setAdapter(adapter);
}
发现没有,就多了一行代码 linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);设置布局的横向排列。默认是纵向排列的。显示效果如图所示:
为啥子ListView不能横向滑动,而RecyclerView可以很轻松的实现呢?这主要的得益于RecyclerView出色的设计。ListView的布局排列是由自身去管理的,而RecyclerView将这个工作给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类按照这个借口的规范来实现,这样就可以制定出各种不同的排列的方式的布局。
4.瀑布流布局
RecyclerView除了提供了LinearLayoutManager线性布局排列以外,还提供了GridLayoutManager(网格布局)和StaggeredGridLayoutManager(瀑布流布局)。
这里稍稍修改了下item_list的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
>
<ImageView
android:id="@+id/iv_head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
android:scaleType="centerCrop"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tv_student_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="学生姓名"
android:layout_marginTop="10dp"/>
</LinearLayout>
代码也更改了一下,仔细看:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initStudent();//初始化学生数据
RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
StudentAdapter adapter=new StudentAdapter(studentList);
recyclerView.setAdapter(adapter);
}
对比下前面的发现就改变了一句是不是:
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager (3, StaggeredGridLayoutManager.VERTICAL);
StaggeredGridLayoutManager 的参数:
- 第一个参数,是设置多少列,我这里设为3列
- 第二个参数 ,是指定布局的排列方向
实现的效果如下图所示:
5.网格布局
在经过了这个多的布局之后,下面的的网格布局应该也猜到了,也是修改其中的LayoutManager。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initStudent();//初始化学生数据
RecyclerView recyclerView= (RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager gridLayoutManager=new GridLayoutManager(this,4);
gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
StudentAdapter adapter=new StudentAdapter(studentList);
recyclerView.setAdapter(adapter);
}
GridLayoutManager的两个参数:
1、第一个参数,是设置上下文对象
2、第二个参数是设置排列的行数
还有最后设置好排列的方向
6.RecyclerView的点击事件
正如ListView那样,RecyclerView也必须满足点击这个硬性要求的。但是与ListView不同的是,RecyclerView并没有提供类似setOnItemClickLisenter()这样的注册监听器的方法,而是需要我们自己给子项具体的view去注册点击事件。相比较实现起来,可能会稍微复杂一点。
大兄弟,看到这里你是不是又有点晕了啊?都说了是比ListView更优秀,那搞个点击事件怎么实现起来还复杂了呢?...... 大哥,我真的没骗你,拳头放下来。因为ListView的点击处理,是点击一整个子项目,然后出发点击事件的是不,哪如果我只想子项目里面的某一个控件出发自己的点击事件呢?ListView并没有给出人性化的处理,处理起来比较麻烦。所以RecyclerView就放弃了子项的点击监听事件的监听器。所有的点击事件都由具体的View来处理,这样是不是没有这个烦恼了啊。
这里讲一下它的点击事件,需要修改StudentAdapter的代码。我们先是修改了ViewHolder,在ViewHolder 添加studentView 变量来保存子项最外层的布局的实例。然后在onCreateViewHolder()方法中注册点击事件就可以了。这里分别为最外层的布局和ImageViewd都注册了点击事件。(意思是,它不仅给每个item都设置了点击事件,连item里面的imageview也设置了点击事件)。通过holder 获取子项的view,然后设置点击事件。通过holder获取子项的view的position。这样就拿到了子项的Student的数据。设置一个点击事件,来显示具体的数据值。在点击文字的时候,我们没有给文字设置点击事件,但是给studentView设置了点击事件,所以还是被最外层的布局捕获到,弹出Toast。
ublic class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {
private List<Student> mStudentList;
public StudentAdapter(List<Student> mStudentList) {
this.mStudentList = mStudentList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
final ViewHolder holder=new ViewHolder(view);
holder.studentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Student student = mStudentList.get(position);
Toast.makeText(v.getContext(), "你点击了"+student.getName(), Toast.LENGTH_SHORT).show();
}
});
holder.studentImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Student student = mStudentList.get(position);
Toast.makeText(v.getContext(), "你点击了"+student.getName(), Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Student student=mStudentList.get(position);
holder.studentImage.setImageResource(student.getImageId());
holder.studentname.setText(student.getName());
}
@Override
public int getItemCount() {
return mStudentList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
private ImageView studentImage;
private TextView studentname;
View studentView;
public ViewHolder(View itemView) {
super(itemView);
studentView=itemView;
studentImage= (ImageView) itemView.findViewById(R.id.iv_head);
studentname= (TextView) itemView.findViewById(R.id.tv_student_name);
}
}
}
效果如图:
项目代码github地址:https://github.com/wangxin3119/recyclerview