ListView与RecyclerView的使用和性能优化总结

Android 专栏收录该内容
53 篇文章 1 订阅

ListView的使用

前言
  ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
 
比如:查看微信聊天记录、翻阅微博最新消息等等!
 

ListView的简单用法

  新建一个安卓项目,修改 activity_main.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

  先为 ListView 指定一个 id,然后将宽度和高度都设置为 match_parent,这样 ListView 也就占满了整个布局空间。
 
接下来修改 MainActivity 中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    private String[] data = {"Apple","Banana","Orange","Watermelon",
            "Pear","Grape","Pineapple","Strawberry","Cherry","Mango","Apple","Banana","Orange","Watermelon",
            "Pear","Grape","Pineapple","Strawberry","Cherry","Mango"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,data);
        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

  既然 ListView 是用于展示大量数据的,那么我们就应该先将数据提供好。这里我们就简单使用了一个 data 数组来测试,里面包含了很多水果的名称。
 
  不过,数组中的数据是无法直接传递给 ListView 的,我们还需要借助适配器来完成。Android 中提供了很多适配器的实现类,其中我认为最好用的就是 ArrayAdapter 。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter 有多个构造函数的重载,你应该根据实际情况选择最适合的一种。这里由于我们提供的数据都是字符串,因此将 ArrayAdapter 的泛型指定为 String ,然后在 ArrayAdapter 的构造函数中依次传入当前上下文、ListView 子项布局的 id ,以及要适配的数据。注意,我们使用了 android.R.layout.simple_list_item_1 作为 ListView 子项布局的 id ,这是一个 Android 内置的布局文件,里面只有一个 TextView ,可用于简单地显示一段文本。这样适配器对象就构建好了。
 
运行结果,如图所示:
运行结果

定制ListView的界面

  只能显示一段文本的 ListView 实在是太单调了,我们现在就来对 ListView 的界面进行定制,让它可以显示更加丰富的内容。
 
  首先定义一个实体类,作为 ListView 适配器的适配类型。新建类 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 类中只有两个字段,name 表示水果的名字,imageId 表示水果对应图片的资源 id 。
 
  然后需要为 ListView 的子项指定一个我们自定义的布局,在 layout 目录下新建 fruit_item.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="wrap_content">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="50dp"
        android:layout_height="50dp"/>

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>
</LinearLayout>

  在这个布局中,我们定义了一个 ImageView 用于显示水果的图片,又定义了一个 TextView 用于显示水果的名称,并让 TextView 在垂直方向上居中显示。
 
  接下来需要创建一个自定义的适配器,这个适配器继承自 ArrayAdapter ,并将泛型指定为 Fruit 类。新建类 FruitAdapter ,代码如下所示:

(第一种方式)

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        // 获取当前项的 Fruit 实例
        Fruit fruit = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        // 设置显示的图片
        fruitImage.setImageResource(fruit.getImageId());
        //  设置显示的文字
        fruitName.setText(fruit.getName());
        // 返回布局
        return view;
    }
}

  FruitAdapter 重写了父类的一组构造函数,用于将上下文、ListView 子项布局的 id 和数据都传递进来。另外又重写了 getView() 方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在 getView() 方法中,首先通过 getItem() 方法得到当前项的 Fruit 实例,然后使用 LayoutInflater 来为这个子项加载我们传入的布局。
 
修改 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();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view1);
        listView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.mipmap.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.mipmap.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.mipmap.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.mipmap.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.mipmap.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.mipmap.mango_pic);
            fruitList.add(mango);
        }
    }
}

  可以看到,这里添加了一个 initFruits() 方法,用于初始化所有的水果数据。在 Fruit 类的构造函数中将水果的名字和对应的图片 id 传入,然后把创建好的对象添加到水果列表中。另外我们使用了一个 for 循环将所有的水果数据添加了两遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕。接着在 onCreate() 方法中创建了 FruitAdapter 对象,并将 FruitAdapter 作为适配器传递给 ListView ,这样定制的 ListView 界面的任务就完成了。(我比较喜欢猫所以用猫的啦!)
运行效果
  其实只要修改 fruit_item.xml 中的内容,就可以定制出各种复杂的界面了。
 

提升ListView的运行效率

  上面的 FruitAdapter 类的写法性能运行效率很低,因为 FruitAdapter 的 getView() 方法中,每次都将布局重新加载一遍了,当 ListView 快速滚动的时候,这就会成为性能的瓶颈。
 
  在 getView() 方法中有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改 FruitAdapter 类的代码,如下所示:
(第二种方式)

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        // 获取当前项的 Fruit 实例
        Fruit fruit = getItem(position);
        View view;
        if(convertView == null){
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
        }else {
            view = convertView;
        }
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        // 设置显示的图片
        fruitImage.setImageResource(fruit.getImageId());
        //  设置显示的文字
        fruitName.setText(fruit.getName());
        // 返回布局
        return view;
    }
}

  可以看到,我们在 getView() 方法中进行了判断,如果 convertView 为 null,则使用 LayoutInflater 去加载布局,如果不为 null 则直接对 convertView 进行重用。这样就大大提高了 ListView 的运行效率。
 
  虽然现在已经不会再重复去加载布局,但是每次在 getView() 方法中还是会调用 View 的 findViewById() 方法来获取一次控件的实例。
这时我们可以借助一个 VIewHoleder 来对这部分性能进行优化,修改 FruitAdapter 中的代码,如下所示:
(第三种方式)

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        // 获取当前项的 Fruit 实例
        Fruit fruit = getItem(position);
        View view;
        VIewHoleder viewHoleder;
        if (convertView == null) {
            // 动态加载布局
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            viewHoleder = new VIewHoleder();
            viewHoleder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHoleder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
            // 将 VIewHoleder 存储在 View 中
            view.setTag(viewHoleder);
        } else {
            view = convertView;
            // 重新获取 VIewHoleder
            viewHoleder = (VIewHoleder) view.getTag();
        }
        // 设置显示的图片
        viewHoleder.fruitImage.setImageResource(fruit.getImageId());
        //  设置显示的文字
        viewHoleder.fruitName.setText(fruit.getName());
        // 返回布局
        return view;
    }

    class VIewHoleder{
        ImageView fruitImage;
        TextView fruitName;
    }
}

  新增加一个内部类 VIewHoleder,用于对控件的实例进行缓存。当 convertView 为 null 的时候,创建一个 VIewHoleder 对象,并将控件的实例都存放在 VIewHoleder 里,然后调用 View 的 setTag() 方法,将 VIewHoleder 对象存储在 View 中。当 convertView 不为 null 的时候,则调用 View 的 getTag() 方法,把 VIewHoleder 重新取出。这样所有控件的实例都缓存在了 VIewHoleder 里,就没有必要每次都通过 findViewById() 方法来获取控件实例了。
 

ViewHolder的作用与用法

  ViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重新创建很多对象,从而提升性能。
 
  在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。
 
  ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。
 
getview的加载方法一般有以下三种种方式:

  最慢的加载方式是每一次都重新定义一个View载入布局,再加载数据**(上面的第一种方式)**

  正确的加载方式是当convertView不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建,然后加载数据**(上面的第二种方式)**

  最快的方式是定义一个ViewHolder,将convetView的tag设置为ViewHolder,不为空时重新使用即可**(上面的第三种方式)**
 
三种方式加载效率对比如下图所示:
三种方式加载效率对比图
说明:上述三个例子代码摘自google 2010 I/O大会
 
当处理一些耗时的资源加载的时候需要做到以下几点,以使你的加载更快更平滑:

  1. 适配器在界面主线程中进行修改

  2. 可以在任何地方获取数据但应该在另外一个地方请求数据

  3. 在主界面的线程中提交适配器的变化并调用notifyDataSetChanged()方法
     

ListView的点击事件

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();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view1);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Fruit fruit = fruitList.get(position);
                Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.mipmap.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.mipmap.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.mipmap.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.mipmap.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.mipmap.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.mipmap.mango_pic);
            fruitList.add(mango);
        }
    }
}

  通过 position 参数判断出用户点击的是哪一个子项,然后获取到响应的水果,并通过 Toast 将水果的名字显示出来。

RecyclerView的使用

注:RecyclerView 也属于新增的控件,为了让 RecyclerView 在所有Android 版本上都能使用,因此需要在项目的 build.gradle 中添加相应的依赖库。
依赖库:implementation ‘androidx.recyclerview:recyclerview:1.0.0’
添加完之后记得要点击一下Sync Now 进行同步。
 
修改 activity_main.xml 中的代码:
如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

  我们使用 RecyclerView 实现与 ListView 相同的效果。因此把 图片、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(@NonNull 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;
    }

    @NonNull
    @Override
    // onCreateViewHolder()方法用于创建 ViewHolder 实例
    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
    // onBindViewHolder()方法用于对 RecyclerView 子项的数据进行赋值
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    // getItemCount() 方法用于告诉 RecyclerView 一共有多少子项
    @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 实例
 
  onBindViewHolder():用于对用于对 RecyclerView 子项的数据进行赋值,在每个子项被滚动到屏幕内的时候执行
 
  getItemCount():用于告诉 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);
        // layoutManager 用于指定 RecyclerView 的布局方式
        // LinearLayoutManager 为线性布局的意思
        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.mipmap.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.mipmap.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.mipmap.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.mipmap.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.mipmap.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.mipmap.mango_pic);
            fruitList.add(mango);
        }
    }
}

  在 onCreate() 方法中先获取到 RecyclerView 的实例,然后创建一个 LinearLayoutManager 对象,并将它设置到 RecyclerView 当中。LayoutManager 用于指定 RecyclerView 的布局方式,这里使用 LinearLayoutManager 是线性布局的意思,可以实现与ListView 类似的效果。

运行效果如图:
运行效果

实现横向滚动和瀑布流布局

前言
  ListView 的扩展性并不好,它只能实现纵向滚动的效果,如果想进行横向滚动的话,ListView就做不到。而 RecyclerView 能够做到。
 
  首先对 fruit_item.xml 布局进行修改,因为目前这个布局里面的元素是水平排列的,适用于纵向滚动的场景,而如果我们要实现横向滚动的话,应该把 fruit_item.xml 里面的元素改成垂直排列。代码如下所示:

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

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="50dp"
        android:layout_height="50dp"
        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_marginLeft="10dp"/>
</LinearLayout>

修改 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);
        // layoutManager 用于指定 RecyclerView 的布局方式
        // LinearLayoutManager 为线性布局的意思
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        // 调用 LinearLayoutManager 的 setOrientation() 方法来设置布局的排列方向
        // 默认是纵向排列的,传入 LinearLayoutManager.HORIZONTAL 表示让布局横向排列
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        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.mipmap.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.mipmap.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.mipmap.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.mipmap.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.mipmap.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.mipmap.mango_pic);
            fruitList.add(mango);
        }
    }
}

  MainActivity 中只加入了一行代码,调用 LinearLayoutManager 的 setOrientation() 方法来设置布局的排列方向,默认是纵向排列,我们传入 LinearLayoutManager.HORIZONTAL 表示让布局横向排列,这样 RecyclerView 就可以横向滚动了。
 
运行效果如图:
运行效果图
  为什么 ListView 很难或者根本无法实现的效果在 RecyclerView 上这么轻松就实现了?
 
  因为 RecyclerView 主要益于 它出色的设计,ListView 的布局排列是由自身去管理的,而 RecyclerView 则将这个工作交给了 LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局。
 
  除了 LinearLayoutManager 之外,RecyclerView 还给我们提供了 GridLayoutManager 和 StaggeredGridLayoutManager 两种内置的布局排列方式,GridLayoutManager 用于实现网格布局,StaggeredGridLayoutManager 用于实现瀑布流布局。
 

实现瀑布流布局

  这里我们实现以下瀑布流布局效果:
修改 fruit_item.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="wrap_content"
    android:orientation="vertical"
    android:layout_margin="5dp">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="50dp"
        android:layout_height="50dp"
        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>

接着修改 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);
        // 第一个参数:3 代表会把布局分为 3 列
        // 第二个参数:StaggeredGridLayoutManager.VERTICAL 表示会让布局纵向排列
        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.mipmap.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("Banana"), R.mipmap.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(getRandomLengthName("Orange"), R.mipmap.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.mipmap.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLengthName("Pear"), R.mipmap.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomLengthName("Grape"), R.mipmap.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.mipmap.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.mipmap.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.mipmap.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("Mango"), R.mipmap.mango_pic);
            fruitList.add(mango);
        }
    }

    // 由于瀑布流布局需要各个子项的高度不一致才看得出明显的效果
    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 中。
 
运行效果图:
运行效果图

RecyclerView 的点击事件

修改 FruitAdapter 的代码,如下所示:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
        // 添加 fruitView 变量来保存子项最外层布局的实例
        View fruitView;
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(@NonNull View view) {
            super(view);
            // fruitView 变量来保存子项最外层布局的实例
            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;
    }

    @NonNull
    @Override
    // onCreateViewHolder()方法用于创建 ViewHolder 实例
    public ViewHolder onCreateViewHolder(@NonNull 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();
            }
        });
        // 注册点击事件(Image 的点击事件)
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 获取用户点击的 position
                int position = holder.getAdapterPosition();
                // 通过 position 拿到相应的 Fruit 实例
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(),"you clicked image"+fruit.getName(),Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    @Override
    // onBindViewHolder()方法用于对 RecyclerView 子项的数据进行赋值
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    // getItemCount() 方法用于告诉 RecyclerView 一共有多少子项
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }


}

运行效果图:
运行效果图
 
摘抄自:第一行代码 Android
与摘抄自:https://blog.csdn.net/jacman/article/details/7087995

  • 0
    点赞
  • 0
    评论
  • 7
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值