Android 控件之 RecyclerView(一)—— 加载视图和布局选择

一、概述

RecyclerView 是在 Android5.0 之后,Google 推出的一个新控件,是作为 ListView、GridView 的替代者出现的。它具有比上述两者更高的灵活性,在功能上也更加强大。Google 官方对它的定义是:

A flexible view for providing a limited window into a large data set.
向一个有限的窗口(window)提供大量数据集的灵活视图(view)。

先来看看 RecyclerView 能实现哪些效果:
图1 图2
图3 图4
由于 RecylerView 定义在了 support 库中,如果我们想要使用 RecyclerView 控件,需要向项目中的 build.gradle添加代码 implementation ‘com.android.support:recyclerview-v7:27.1.1’ ,如下所示:

dependencies {
	implementation fileTree(include: ['*.jar'], dir: 'libs')
	implementation 'com.android.support:appcompat-v7:27.1.1'
	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:27.1.1'
}

添加完之后一定要记得点一下右上角的 Sync Now 进行同步,否则可能会出现错误。

二、列表视图的处理

1. item 的布局文件

首先,在 activity_main 中添加 RecyclerView 控件,代码如下:

<?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=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

其次,在 layout 文件夹下新建一个 Layout Resource file,命名为 item_recycler_view,它是 RecyclerView 中 item(即子项)的布局文件,我们需要的 item 布局为图片(ImageView)和文字(TextView)的组合,所以代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/iv_item"
        android:layout_width="80dp"
        android:layout_height="80dp" />

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="20dp"
        android:textSize="15sp" />

</LinearLayout>

2. 构造 Adapter 类

既然展示的是关于食物的图片和文字的视图,那么我们就新建一个 Food 类来存取我们每个食物(Food)的实例,代码如下:

public class Food {
    // 食物的图片id
    private int imageId;
    // 食物的名字
    private String name;
    // 构造函数
    public Food(String name, int imageId){
        this.imageId = imageId;
        this.name = name;
    }
    // 获取食物的图片id
    public int getImageId() {
        return imageId;
    }
    // 获取食物的名字
    public String getName() {
        return name;
    }
}

接着,为我们的 RecyclerView 定义一个 Adapter 类。和 ListView 一样,RecyclerView 也需要使用 Adapter 适配器,现在我们新建一个 MyAdapter 类,继承自 RecyclerView.Adapter,其中泛型参数 VH 表示的就是 ViewHolder ,这里我们将泛型类型指定为 MyAdapter.ViewHolder,它是 MyAdapter 中我们自己定义的一个 的内部类,继承自 Recycler.ViewHolder

我们还需要在 MyAdapter 类中覆写三个方法 :onCreateViewHolder()onBindViewHolder()getItemCount(),新建好的代码如下:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    // 存储 Food 类型数据的数据源 
    private List<Food> mFoodList;
    
    // 构造方法用于传入数据
    public MyAdapter(List<Food> list){
		mFoodList = list;
    }
    
	// 创建 ViewHolder的实例,在这里加载 item_recycler_view 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    	return null;
    }
    
    // 绑定 ViewHolder,用于对每个 item 进行赋值操作
    @Override
    public void onBindViewHolder(ViewHolder holder, int position){
    }
    
    // 获取 item 的数量
    @Override
    public int getItemCount(){
    	return 0;
    }
    
    // 自定义的 ViewHolder 类,继承自 RecyclerView.ViewHolder
    static class ViewHolder extends RecyclerView.ViewHolder{
        View view;
        ViewHolder(View view){
            super(view);
            this.view = view;
        }
    }
}

分析一下这段代码,mFoodList 是用于存储 Food 类型数据的数据源,它通过构造方法获取数据。接下来看第一个需要覆写的方法 onCreateViewHolder(),从字面上理解,顾名思义,就是需要我们在这个方法中创建 ViewHolder 实例。在这个方法中,我们将之前写好的布局文件 item_recycler_view 加载进来,并创建一个 ViewHolder 实例返回。代码如下:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
	// 传入子项(item)的布局文件
    View view = LayoutInflater.from(parent.getContext()).inflate(
            R.layout.item_recycler_view, parent, false);
    // 创建 ViewHolder 实例
    ViewHolder holder = new ViewHolder(view);
    return holder;
}

接下来是第二个覆写方法 onBindViewHolder(),该方法用于对 RecyclerView 子项的数据进行赋值,我们可以通过第二个参数 position 来获取当前项的 Food 实例,然后把 Food 实例中的数据传入 holder 的 ImageViewTextView 中。代码如下:

@Override
public void onBindViewHolder(ViewHolder holder, int position){
	// 获取当前项的 Food 实例
    Food food = mFoodList.get(position);
    TextView textView = holder.view.findViewById(R.id.tv_item);
    ImageView imageView = holder.view.findViewById(R.id.iv_item);
    textView.setText(food.getName());
    imageView.setImageResource(food.getImageId());
}

最后一个覆写方法 getItemCount() 就很简单了,因为是获取 item 的个数,所以直接返回 mFoodList 的长度即可。

@Override
public int getItemCount(){
    return mFoodList.size();
}

完整的 MyAdapter 类代码如下所示:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    // 存储 Food 类型数据的数据源 
    private List<Food> mFoodList;
    
    // 构造方法用于传入数据
    public MyAdapter(List<Food> list){
        mFoodList = list;
    }

	// 创建 ViewHolder的实例,在这里加载 item_recycler_view 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    	// 传入子项(item)的布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_recycler_view, parent, false);
        // 创建 ViewHolder 实例
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

	// 绑定 ViewHolder,用于对每个 item 进行赋值操作
    @Override
    public void onBindViewHolder(ViewHolder holder, int position){
    	// 获取当前项的 Food 实例
        Food food = mFoodList.get(position);
        TextView textView = holder.view.findViewById(R.id.tv_item);
        ImageView imageView = holder.view.findViewById(R.id.iv_item);
        textView.setText(food.getName());
        imageView.setImageResource(food.getImageId());
    }

	// 获取 item 的数量
    @Override
    public int getItemCount(){
        return mFoodList.size();
    }

	// 自定义的 ViewHolder 类,继承自 RecyclerView.ViewHolder
    static class ViewHolder extends RecyclerView.ViewHolder{
        View view;
        ViewHolder(View view){
            super(view);
            this.view = view;
        }
    }
}

3. 布局管理器

在写好 MyAdapter 类之后,就是在 MainActivity 中使用 RecyclerView 了。我们到目前为止还剩下一个问题没解决,那就是既然提到了 RecyclerView 是作为 ListView 和 GridView 的替代者出现的,那么它是如何管理它的布局的呢?

答案就是调用 RecyclerView 实例的 setLayoutManager() 方法。setLayoutManager()方法接受一个 LayoutManager 类型的参数,通过这个参数来实现 RecyclerView 的各种各样的布局,前面所展示的布局都是在这个方法中进行设置的。下面就来介绍几个布局管理器类型:

1)LinearLayoutManager

如果我们要实现像 ListView 那样的垂直线性布局,就得使用 LinearLayoutManager 。代码如下所示:

// 获取 RecyclerView 的实例
mRecyclerView = (RecyclerView)findViewById(R.id.recycler_view);
// 线性布局管理器
LinearLayoutManager manager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(manager);

先构造 LinearLayoutManager 的实例 manager,它的默认 orientationVERTICAL,与我们的需求正好一致,所以构造好之后直接将 manager 传入 setLayoutManager() 即可。最终效果如前面垂直线性布局所示。

而当我们需要实现横向线性布局时,只需要在第二句和第三句之间添加setOrientation()方法即可:

// 线性布局管理器
LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this);
manager.setOrientation(LinearLayoutManager.HORIZONTAL); 
mRecyclerView.setLayoutManager(manager);

效果如前面水平线性布局所示。

2)GridLayoutManager

如果我们要实现像 GridView 那样的网格布局,就得使用 GridLayoutManager 。代码如下所示:

// 网格布局管理器
GridLayoutManager manager = new GridLayoutManager(MainActivity.this, 3);
mRecyclerView.setLayoutManager(manager);

GridLayoutManager 的构造函数的第二个参数类型是 int,表示列数,这里我们指定为 3,即显示三列的数据。效果如前面网格布局所示。

3)StaggeredGridLayoutManager

如果我们想实现瀑布流布局的效果的话,就得使用 StaggeredGridLayoutManager,代码如下所示:

// 瀑布流布局管理器
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(3,
        StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(manager);

StaggeredGridLayoutManager 的构造方法第一个参数类型为int,表示列数,这里我们同样指定为3。第二个参数表示布局的方向,这里我们设定为垂直方向。实现效果如前面瀑布流布局所示。

4. 完整的 MainActivity 代码

这里我们以垂直线性布局为例,直接上代码:

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private List<Food> foodList;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化食物的数据
        initData();
        mRecyclerView = (RecyclerView)findViewById(R.id.recycler_view);
        // 垂直线性布局
        LinearLayoutManager manager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(manager);
        // 将 foodList 传入 adapter 中
        adapter = new MyAdapter(MainActivity.this, foodList);
        mRecyclerView.setAdapter(adapter);
    }

    // 这里为了方便展示随便初始化了 20 个食物的数据
    private void initData(){
        foodList = new ArrayList<>();
        for(int i = 0; i < 5; i++) {
            Food food1 = new Food("Cake", R.drawable.cake);
            Food food2 = new Food("Biscuit", R.drawable.biscuit);
            Food food3 = new Food("Bread", R.drawable.bread);
            Food food4 = new Food("Hamburger", R.drawable.hamburger);
            foodList.add(food1);
            foodList.add(food2);
            foodList.add(food3);
            foodList.add(food4);
        }
    }
}

到这里相信很多人都会看的比较明白了,在 MainActivity 中我们首先初始化了我们要展示的食物的数据,然后将布局设置为线性(默认为垂直布局)。然后我们会初始化adpater,它是我们自定义的 MyAdapter 类的实例,我们将初始好后的 foodList 作为参数传给它。最后,它会被作为参数传给 setAdapter() 方法。

至此,我们的代码就全部完成了!运行一下,效果如下图所示:
图5
如果我们需要设置简单的分割线的话,可以添加一句代码:

mRecyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this,
		 DividerItemDecoration.VERTICAL));

效果如下图所示:
图6
为了节省篇幅,其他的布局代码这里就不贴出来了,方法也很简单,无非是在setLayoutManager() 方法中传入不同的布局器参数,然后对 item_recycler_view 或数据进行简单的修改即可。

三、总结

对于 RecyclerView 的使用,可分为 4 步:
Step 1:向 build.gradle 中添加相应的依赖
Step 2:为 item 添加一个布局文件
Step 3:新建一个继承自 RecyclerView.Adapter 的 Adapter 类,并覆写 onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 这三个方法。
Step 4:在 MainActivity 中,获取 RecyclerView 的实例,然后通过 setLayoutManager() 进行布局设置,通过 setAdapter() 设置适配器,然后就大功告成了!

最后,本篇博客是我写的第一篇博客,如果有写的不清楚或者不清晰的地方,望见谅!如果对于我的博客有任何想法建议的话,欢迎评论或私信!

猜你喜欢

转载自blog.csdn.net/qq_38182125/article/details/84000483
今日推荐