《第一行代码》学习笔记:更强大的滚动控件——RecyclerView(增强版的 ListView)
一、基本用法
- 添加依赖库并编写布局文件
想要使用RecyclerView这个控件,首先需要在项目的app/build.gradle中添加相应的依赖库才行,在dependencies 闭包中添加如下内容:
implementation 'androidx.recyclerview:recyclerview:1.1.0'
**添加完之后记得要点击一下右上角Sync Now来进行同步。**然后修改activity_main.xml中的代码,如下所
示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView //***
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。
- 为RecyclerView准备一个适配器
首先将上例中Fruit 类和fruit_item.xml也复制过来。接下来需要为RecyclerView准备一个适配器,新建FruitAdapter 类,让这个适配器继承自RecyclerView.Adapter ,并将泛型指定为FruitAdapter.ViewHolder 。其中,ViewHolder 是我们在FruitAdapter 中定义的一个内部类,代码如下所示:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@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;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
这里我们首先定义了一个内部类ViewHolder ,ViewHolder 要继承自RecyclerView.ViewHolder 。然后ViewHolder 的构造函数中要传入一个View 参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById() 方法来获取到布局中的ImageView和TextView的实例了。接着往下看,FruitAdapter 中也有一个构造函数,这个方法用于把要展示的数据源传进来,并赋值给一个全局变量mFruitList ,我们后续的操作都将在这个数据源的基础上进行。继续往下看,由于FruitAdapter 是继承自RecyclerView.Adapter 的,那么就必须重写
onCreateViewHolder() 、onBindViewHolder() 和getItemCount() 这3个方法。onCreateViewHolder() 方法是用于创建ViewHolder 实例的,我们在这个方法中将fruit_item 布局加载进来,然后创建一个ViewHolder 实例,并把加载出来的布局传入到构造函数当中,最后将ViewHolder 的实例返回。onBindViewHolder() 方法是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position 参数得到当前项的Fruit 实例,然后再将数据设置到ViewHolder 的ImageView和TextView当中即可。getItemCount() 方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。
- 使用RecyclerView
修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits(); // 初始化水果数据
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
fruitList.add(banana);
//其它水果。。。
}
}
}
这里使用了一个同样的initFruits() 方法,用于初始化所有的水果数据。接着在onCreate() 方法中我们先获取到RecyclerView的实例,然后创建了一个LinearLayoutManager 对象,并将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter 的实例,并将水果数据传入到FruitAdapter 的构造函数中,最后调用RecyclerView的setAdapter() 方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。
这里和ListView几乎一模一样的效果,虽说在代码量方面并没有明显地减少,但是逻辑变得更加清晰。
二、实现横向滚动和瀑布流布局(ListView无法完成)
- 横向滚动
首先要对fruit_item 布局进行修改,因为目前这个布局里面的元素是水平排列的,适用于纵向滚动的场景,而如果我们要实现横向滚动的话,应该把fruit_item 里的元素改成垂直排列才比较合理。修改fruit_item.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="100dp"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp" />
</LinearLayout>
将LinearLayout改成垂直方向排列,并把宽度设为100dp。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content 的话,RecyclerView的子项就会有长有短,非常不美观;而如果用match_parent 的话,就会导致宽度过长,一个子项占满整个屏幕。然后使用layout_marginTop 属性让文字和图片之间保持一些距离。接下来修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);//新加的
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
...
}
MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation() 方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL 表示让布局横行排列,这样RecyclerView就可以横向滚动了。
ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。
- 瀑布流布局
除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。这里实现一下效果更加炫酷的瀑布流布局:
首先还是来修改一下fruit_item.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height=" wrap_content"
android:layout_margin="5dp" >
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp" />
</LinearLayout>
这里做了几处小的调整,首先将LinearLayout的宽度由100dp改成了match_parent ,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。另外我们使用了layout_margin 属性来让子项之间互留一点间距,还有就是将TextView的对齐属性改成了居左对齐,因为待会我们会将文字的长度变长,如果还是居中显示就会感觉怪怪的。修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName("Apple"),R.drawable.apple_pic);fruitList.add(apple);
Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);fruitList.add(banana);
//其它水果。。。
}
private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(name);
}
return builder.toString();
}
}
首先,在onCreate() 方法中,我们创建了一个StaggeredGridLayoutManager 的实例。StaggeredGridLayoutManager 的构造函数接收两个参数,第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL 表示会让布局纵向排列,最后再把创建好的实例设置到RecyclerView当中就可以了。不过由于瀑布流布局需要各个子项的高度不一致才能看出明显的效果,为此又使用了一个小技巧。getRandomLengthName() 这个方法使用了Random 对象来创造一个1到20之间的随机数,然后将参数中传入的字符串随机重复几遍。在initFruits() 方法中,每个水果的名字都改成调用getRandomLengthName() 这个方法来生成,这样就能保证各水果名字的长短差距都比较大,子项的高度也就各不相同。
三、RecyclerView的点击事件
不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件,相比于ListView来说,实现起来要复杂一些。但是ListView在点击事件上的处理并不人性化,setOnItemClickListener() 方法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮呢?虽然ListView也是能做到的,但是实现起来就相对比较麻烦了。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册,就再没有这个困扰了。
修改FruitAdapter 中的代码,如下所示:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitView = view;
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@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;
}
...
}
我们先是修改了ViewHolder ,在ViewHolder 中添加了fruitView 变量来保存子项最外层布局的实例,然后在onCreateViewHolder() 方法中注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件,RecyclerView的强大之处也在这里,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit 实例,再使用Toast分别弹出两种不同的内容以示区别。
总结
本文地址:https://blog.csdn.net/weixin_44267444/article/details/107075274
上一篇: OpenCV Android解决相机旋转90度及全屏问题
下一篇: Play(一):搭建项目