之前总结了ListView的用法(链接:https://www.jianshu.com/p/0c30f59e0280),但是ListView的扩展性不够好,只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView是做不到的。因此,我们需要一个更强大的滚动控件RecyclerView。
RecyclerView的基本用法
RecyclerView是属于新增的控件,为了让RecyclerView在所有Android版本上都能使用,Android团队将其定义在了support库当中。因此,想要使用RecyclerView 控件,首先需要在项目的app/build.gradle中添加相应的依赖库才行
- 打开app/build.gradle文件,在dependenceies闭包中添加如下内容:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// 添加的依赖库
implementation'com.android.support:recyclerview-v7:28.0.0'
}
添加之后记得要点击一下Sync Now来进行同步
- 修改activity_main中的代码如下所示:
在布局中加入RecyclerView控件,为其制定一个id,并将宽度和高度都设置为match_parent,这样RecyclerView就可以占满整个布局空间
- 为了实现和ListView一样的效果,我们将之前的Fruit类和fruit_item代码复制过来
Fruit类代码:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name,int imageId){
this.name=name;
this.imageId=imageId;
}
public String getName(){
return name;
}
public int getImageId(){
return imageId;
}
}
fruit_item代码
这里我们需要注意,fruit_item中的LinearLayout的高度指定了为50dp,运行的效果是这样的:
如果我们将高度指定为match_parent,则得到的效果如下:
这是为什么呢?这是因为,如果将高度指定为match_parent,那么一个fruit_item就会占满了整个布局的空间,因此我们就只能看到一个选项
- 接下来需要为RecyclerView准备一个适配器FruitAdapter,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。其中ViewHolder是我们在FruitAdapter中定义的一个内部类,代码如下所示:
package com.example.apple.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
public class FruitAdapter extends RecyclerView.Adapter {
private List mFruitList;
// 定义一个内部类ViewHolder,继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个
// view参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方法
// 来获取到布局中的ImageView等的实例了
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view){
super(view);
fruitImage=view.findViewById(R.id.fruit_image);
fruitName=view.findViewById(R.id.fruit_name);
}
}
// 用于把要展示的数据传进来 ,并赋值给一个全局变量mFruitList,后续的操作都将在这个数据源的基础上进行
public FruitAdapter(List fruitList){
mFruitList=fruitList;
}
// 用于创建ViewHolder实例,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,
// 并把加载出来的布局传入到构造函数当中,最后将ViewHolder的实例返回
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}
// 用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position
// 参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中
@Override
public void onBindViewHolder(ViewHolder holder,int position){
Fruit fruit=mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
// 用于告诉RecyclerView一共有多少子项,直接返回数据源长度
@Override
public int getItemCount(){
return mFruitList.size();
}
}
其中内部的函数的作用均在代码中有注释
- 适配器准备好了之后,我们就可以开始使用RecyclerView了,修改MainActivity中的代码,如下所示:
package com.example.apple.recyclerview;
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 List fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化水果数据
initFruits();
RecyclerView recyclerView=findViewById(R.id.recycler_view);
// 创建一个LinearLayoutManager对象,并把它设置到RecyclerView当中
// LayoutManager用于指定RecyclerView的布局方式,这里是线性布局的意思
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// 创建FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
// 初始化数据
private void initFruits(){
for(int i=0;i<10;i++){
Fruit a=new Fruit("a",R.drawable.a);
fruitList.add(a);
Fruit b=new Fruit("B",R.drawable.b);
fruitList.add(b);
Fruit c=new Fruit("C",R.drawable.c);
fruitList.add(c);
Fruit d=new Fruit("D",R.drawable.d);
fruitList.add(d);
}
}
}
现在运行一下程序,效果如下图所示:
可以看到,我们使用RecyclerView实现了和ListView几乎一模一样的效果,虽说代码量方面并没有明显的减少,但是逻辑变得更加清晰了。当然这只是RecyclerView的基本用法而已,接下来我们就看一看RecyclerView如何实现横向滚动和瀑布流布局
实现横向滚动和瀑布流布局
- 要实现横向滚动的效果,首先要对fruit_item布局进行修改,因为目前这个布局里面的元素是水平排列的,使用于纵向滚动的场景,而如果要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列的才比较合理。修改fruit_item中的代码,如下所示:
可以看到我们把LinearLayout改成竖直方向,并把宽度设置为100dp
- 接下来修改MainActivity中的代码,如下所示:
package com.example.apple.recyclerview;
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 List fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化水果数据
initFruits();
RecyclerView recyclerView=findViewById(R.id.recycler_view);
// 创建一个LinearLayoutManager对象,并把它设置到RecyclerView当中
// LayoutManager用于指定RecyclerView的布局方式,这里是线性布局的意思
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); //新加入的代码
recyclerView.setLayoutManager(layoutManager);
// 创建FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
// 初始化数据
private void initFruits(){
for(int i=0;i<10;i++){
Fruit a=new Fruit("a",R.drawable.a);
fruitList.add(a);
Fruit b=new Fruit("B",R.drawable.b);
fruitList.add(b);
Fruit c=new Fruit("C",R.drawable.c);
fruitList.add(c);
Fruit d=new Fruit("D",R.drawable.d);
fruitList.add(d);
}
}
}
MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL表示让布局横行排列。重新运行一下程序,效果如下图所示:
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager两种内置的布局排列方式。GridLayoutManager用于实现网格布局,StaggeredGridLayoutManager用于实现瀑布流布局。
瀑布流布局
- 下面我们来实现一下瀑布流布局,首先还是修改一下fruit_item中的代码,如下所示:
这里做了几处小的调整,首先将LinearLayout的宽度由100dp改成了match_parent,因为瀑布流布局的宽度是根据布局的列数来自动适配的,而不是一个固定的值。
- 接着修改MainActivity中的代码,如下所示:
package com.example.apple.recyclerview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private List fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化水果数据
initFruits();
RecyclerView recyclerView=findViewById(R.id.recycler_view);
// 创建一个瀑布流布局对象
StaggeredGridLayoutManager layoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
// 创建FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
// 初始化数据
private void initFruits(){
for(int i=0;i<10;i++){
Fruit a=new Fruit(getRandomLengthName("a"),R.drawable.a);
fruitList.add(a);
Fruit b=new Fruit(getRandomLengthName("b"),R.drawable.b);
fruitList.add(b);
Fruit c=new Fruit(getRandomLengthName("c"),R.drawable.c);
fruitList.add(c);
Fruit d=new Fruit(getRandomLengthName("d"),R.drawable.d);
fruitList.add(d);
}
}
private String getRandomLengthName(String name){
Random random=new Random();
int length=random.nextInt(20)+1;
StringBuilder builder=new StringBuilder();
for (int i=0;i
首先,在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager的实例,它的构造函数接收两个参数,第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列,最后再把创建好的实例设置到RecyclerView当中就可以了。效果如下图所示:
看起来效果还是不错的,接下来继续看看RecyclerView的点击事件
RecyclerView的点击事件
不同于ListView,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。下面我们就来看一看如何在RecyclerView中注册点击事件
- 修改FruitAdapter中的代码,如下所示:
package com.example.apple.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class FruitAdapter extends RecyclerView.Adapter {
private List mFruitList;
// 定义一个内部类ViewHolder,继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个
// view参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方法
// 来获取到布局中的ImageView等的实例了
static class ViewHolder extends RecyclerView.ViewHolder{
View fruitView; // 新加
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view){
super(view);
fruitView=view; // 新加
fruitImage=view.findViewById(R.id.fruit_image);
fruitName=view.findViewById(R.id.fruit_name);
}
}
// 用于把要展示的数据传进来 ,并赋值给一个全局变量mFruitList,后续的操作都将在这个数据源的基础上进行
public FruitAdapter(List fruitList){
mFruitList=fruitList;
}
// 用于创建ViewHolder实例,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,
// 并把加载出来的布局传入到构造函数当中,最后将ViewHolder的实例返回
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
final ViewHolder holder=new ViewHolder(view);
holder.fruitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(v.getContext(),"you clicked view"+fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
holder.fruitImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(v.getContext(),"you clicked image"+fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
return holder;
}
// 用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position
// 参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中
@Override
public void onBindViewHolder(ViewHolder holder,int position){
Fruit fruit=mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
// 用于告诉RecyclerView一共有多少子项,直接返回数据源长度
@Override
public int getItemCount(){
return mFruitList.size();
}
}
我们先是修改了ViewHolder,在ViewHolder中添加了fruitView变量来保存子项最外层布局的实例,然后在onCreateViewholder()方法中注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit实例,再使用Toast分别弹出两种不同的内容以示区别。
注意:在ListView中,注册监听器是在MainActivity中;而在RecyclerView中,注册事件是在适配器FruitAdapter中注册
文章来源:《第一行代码第2版》