一、RecyclerView
1、LayoutInflater,View,Context的关系
解释这段代码:
View view = LayoutInfalter.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false)
这段代码的作用是将名为 R.layout.fruit_item
的布局文件转换为一个视图对象,并将其添加到 parent
视图组中。
parent
: 这是一个ViewGroup
对象,它表示父级视图,即将要将转换后的视图添加到其中的容器。在 RecyclerView 中,每个列表项都有一个父级视图,即 RecyclerView 本身。parent.getContext()
: 通过调用getContext()
方法,我们可以获取 RecyclerView 所在的上下文(Context
)对象,这个上下文用于在应用程序中访问资源和进行其他操作。LayoutInflater.from(parent.getContext())
: 我们使用LayoutInflater.from()
方法来获取一个用于在指定上下文中创建视图的LayoutInflater
实例。inflate(R.layout.fruit_item, parent, false)
:inflate
方法是LayoutInflater
类的一个重要方法,它接受三个参数:要转换的布局文件的资源 ID,父级视图(容器),以及是否将转换后的视图添加到父级视图中。R.layout.fruit_item
: 这是要转换的布局文件的资源 ID,它指定了要转换的布局文件的名称和位置。parent
: 这是要添加转换后的视图的父级视图(容器),即 RecyclerView。false
: 这个参数表示在将转换后的视图添加到父级视图时是否立即执行这个操作。false
表示在后续手动添加视图,因为在 RecyclerView 中,适配器负责创建和绑定视图。
2、RecyclerView的基本用法
2.1 为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{
FruitItemBinding binding;
public ViewHolder(@NonNull View itemView) {
super(itemView);
binding = FruitItemBinding.bind(itemView);
//binding = FruitItemBinding.inflate(LayoutInflater.from(itemView.getContext()), (ViewGroup) itemView,true);
}
}
public FruitAdapter(List<Fruit> mFruitList) {
this.mFruitList = mFruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull 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(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.binding.fruitImg.setImageResource(fruit.getImageId());
holder.binding.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
虽然这段代码看上去好像有点长,但其实它比ListView的适配器要更容易理解。这里我们==1、==首先定义了一个内部类ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传人一个View参数,这个参数通常就是RecyclerView子项的最外层布局。然后我们通过bing获取到这个View的绑定类。
2.1.1 bind与inflate(LayoutInflater.from(itemView.getContext()))有什么区别
这里有个知识点下面两段代码有什么区别?
binding = FruitItemBinding.inflate(LayoutInflater.from(itemView.getContext()));
binding = FruitItemBinding.bind(itemView);
inflate(LayoutInflater.from(itemView.getContext())) :
它会自动处理布局文件中定义的绑定信息,将布局文件中的视图与绑定类中的对应视图关联起来,**注意他是没有绑定父布局。**就相当于创建了一个新的布局绑定,和之前的没有关系。
通过源码我们发现如果使用inflate(LayoutInflater.from(itemView.getContext())) 实际上是1
调用2
再调用3
,所以这两段代码产生的数据类型相同,但是可以看见1
中调用inflate但是传入的parent
是null就相当于创建了一个新的布局绑定,和之前的没有关系。
改为这样就可以了,和bind是等价的
binding = FruitItemBinding.inflate(LayoutInflater.from(itemView.getContext()), (ViewGroup) itemView,true);
binding = FruitItemBinding.bind(itemView) :
这行代码使用 FruitItemBinding
类的 bind()
方法,将传入的 itemView
(RecyclerView 的单个项布局)与 FruitItemBinding
类所对应的布局文件关联起来。bind()
方法的作用是将布局文件 fruit_item.xml
中的视图控件与 FruitItemBinding
绑定类中的对应视图控件进行关联。
总结:inflate(LayoutInflater.from(itemView.getContext()))
绑定的是整个布局,bind
绑定的是单独控件。
==2、==往下看,FruitAdapter中也有一个构造函数,这个方法用于把要展示的数据源传进来,并赋值给一个全局变量mFru1 tList,我们后续的操作都将在这个数据源的基础上进行。
==3、==由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateviewHolder()、onBindViewHolder()和getItemCount()这3个方法。
**onCreateViewHolder()**方法是用于创建ViewHolder
实例的,我们在这个方法中将fruititem
布局加载进来,然后创建一个ViewHolder
实例,并把加载出来的布局传人到构造函数当中,最后将ViewHolder
的实例返回。**onBindViewHolder()**方法是用于对RecyclerView
子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position
参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。**getItemCount()**方法就非常简单了,它用于告诉RecyclerView
一共有多少子项,直接返回数据源的长度就可以了。
2.2 使用准备好的适配器
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(); //初始化水果数据
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//制作布局方式
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//设置布局方式
binding.recycler1.setLayoutManager(linearLayoutManager);
//传递数据给适配器
FruitAdapter adapter = new FruitAdapter(fruitList);
binding.recycler1.setAdapter(adapter);
}
//初始化水果数据
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit ariplane = new Fruit("0", R.drawable.img10);
fruitList.add(ariplane);
Fruit Banana = new Fruit("1", R.drawable.img1);
fruitList.add(Banana);
Fruit TheEarth = new Fruit("2", R.drawable.img2);
fruitList.add(TheEarth);
Fruit bourn = new Fruit("3", R.drawable.img3);
fruitList.add(bourn);
Fruit emitter = new Fruit("4", R.drawable.img4);
fruitList.add(emitter);
Fruit cati = new Fruit("5", R.drawable.img5);
fruitList.add(cati);
Fruit SatelliteVehicle = new Fruit("6", R.drawable.img6);
fruitList.add(SatelliteVehicle);
Fruit warplane = new Fruit("7", R.drawable.img7);
fruitList.add(warplane);
Fruit Saturn = new Fruit("8", R.drawable.img8);
fruitList.add(SatelliteVehicle);
Fruit Jupiter = new Fruit("9", R.drawable.img9);
fruitList.add(Jupiter);
Fruit Apple = new Fruit("10", R.drawable.img10);
fruitList.add(Apple);
Fruit Tomato = new Fruit("11", R.drawable.img11);
fruitList.add(Tomato);
Fruit Onion = new Fruit("12", R.drawable.img12);
fruitList.add(Onion);
Fruit watermelon = new Fruit("13", R.drawable.img13);
fruitList.add(watermelon);
Fruit sun = new Fruit("14", R.drawable.img14);
fruitList.add(sun);
}
}
}
实现效果如下:
3、实现横向滚动和瀑布流布局、网格布局
3.1 横向滚动
在MainActivity中加上这样一句话:linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits(); //初始化水果数据
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//制作布局方式
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//设置布局方式
binding.recycler1.setLayoutManager(linearLayoutManager);
//传递数据给适配器
FruitAdapter adapter = new FruitAdapter(fruitList);
binding.recycler1.setAdapter(adapter);
}
意思是设置为水平方向,因为默认的是竖值方向所有不用设置。
MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传人LinearLayoutManager.HORIZONTAL
表示让布局横行排列,这样Recycler View就可以横向滚动了。
3.2 瀑布流布局(StaggerredGridLayoutManager)
修改MainActivity中的onCreate代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits(); //初始化水果数据
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//制作布局方式
StaggeredGridLayoutManager staggeredGridLayoutManager = new
StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
//设置布局方式
binding.recycler1.setLayoutManager(staggeredGridLayoutManager);
//传递数据给适配器
FruitAdapter adapter = new FruitAdapter(fruitList);
binding.recycler1.setAdapter(adapter);
}
构造StaggeredGridLayoutManager
对象,构造函数的两个参数,第一个参数表示指定布局的列数,第二个参数指定布局的排列方式。
注意fruit_item.xml
中,LinearLayout
的宽度应该是macth_parent
,因为瀑布流的宽度应该是根据布局的列数自动适配的。
显示效果如下:
3.3 网格布局
同理在MainActivity中的onCreate中的之中布局方式代码改为:
//网格布局
GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
第一个参数是Activity
的context
。第二个参数是布局的列数。
显示效果如下:
4、RecycleView点击事件
RecycleView需要我们为子项具体的View注册点击事件,修改FruitAdapter中代码如下:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
FruitItemBinding binding;
//最外层的View
View public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
FruitItemBinding binding;
//最外层的View
View fruitView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
binding = FruitItemBinding.bind(itemView);
fruitView = itemView;
}
}
public FruitAdapter(List<Fruit> mFruitList) {
this.mFruitList = mFruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,
parent,false);
//设置为Final防止不小心被修改
final ViewHolder holder = new ViewHolder(view);
//注册外层监听事件
holder.fruitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(view.getContext(),"外层", Toast.LENGTH_SHORT).show();
}
});
//注册图片按钮监听事件
holder.binding.fruitImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(view.getContext(),"图片", Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.binding.fruitImg.setImageResource(fruit.getImageId());
holder.binding.fruitName.setText(fruit.getName());
Log.d("TAG",fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
我们先是修改了ViewHolder
,在ViewHolder
中添加了fruitView
变量来保存子项最外层布局的实例,然后在onCreateviewHolder
()方法中注册点击事件就可以了。这里分别为最外层布局和ImageView
都注册了点击事件,RecyclerView
的强大之处也在这里,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的positio
,然后通过position
拿到相应的Fruit
实例,再使用Toast
分别弹出两种不同的内容以示区别。
由于TextView并没有注册点击事件,因此点击文字这个事件会被子项的最外层布局捕获到。