安卓前端研习——RecycleView轻松解决消息列表

  消息列表是大部分应用都会使用到的一个组件,诸如聊天列表,通知列表,资源列表等都可以看到类消息列表的不定条目的列表组件,很多教程会使用到listview来实现消息列表,但是使用较新的RecycleView可以更为轻松地实现这个组件的布局。

  实现后效果如图:

添加支持库到项目中

  按照惯例我们需要将所需的依赖添加到项目中,在build中加入以下代码:

dependencies {
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

配置布局文件资源

  首先我们要创建一个xml文件,其中包含一个RecycleView,我们使用一个简单的布局,布局文件仅包含一个RecycleView,例如:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/new_src_container"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:padding="15dp"
        app:layout_constraintWidth_percent="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

实例化控件以及其他储存

  首先需要获取布局文件中的控件,并进行实例化,初始化包含item的列表。代码如下:

    private void initRecyclerView(){
        new_src_container = (RecyclerView) getActivity().findViewById(R.id.new_src_container);
        new_src_container.setHasFixedSize(true);
        layoutManager = new LinearLayoutManager(getActivity());
        new_src_container.setLayoutManager(layoutManager);
        src_list = new ArrayList<>();
    }

  然后我们需要构造消息item的布局以及实例化代码。

item布局的设置

  创建一个新的xml文件对每一个公式化的item进行布局。

  我创建的item布局大概如图,有一个显示文件类型的图标,一个显示文件大小的文本,一个显示文件标题的文本,一个显示描述的文本,和一个标识是否免费的组合控件,和一个覆盖整个布局的按钮。

具体布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_marginTop="20dp"
    android:id="@+id/src_item"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/src_item_bg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@drawable/src_item_bg"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/src_type_pic"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:src="@drawable/txt"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.5"
        app:layout_constraintHorizontal_bias="0.068"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_percent="0.1277" />

    <TextView
        android:id="@+id/src_size"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Unknown"
        android:textSize="12sp"
        android:gravity="center"
        android:singleLine="true"
        android:textColor="#818181"
        app:layout_constraintEnd_toEndOf="@+id/src_type_pic"
        app:layout_constraintHeight_percent="0.2084"
        app:layout_constraintStart_toStartOf="@+id/src_type_pic"
        app:layout_constraintTop_toBottomOf="@+id/src_type_pic"
        app:layout_constraintWidth_percent="0.1265" />

    <TextView
        android:id="@+id/src_title"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="Unknown title"
        android:textColor="#616161"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="@+id/src_type_pic"
        app:layout_constraintHeight_percent="0.2455"
        app:layout_constraintStart_toEndOf="@+id/src_type_pic"
        app:layout_constraintTop_toTopOf="@+id/src_type_pic"
        app:layout_constraintVertical_bias="0.038"
        app:layout_constraintWidth_percent="0.5576" />

    <TextView
        android:id="@+id/src_describe"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="20dp"
        android:text="Undefine describe"
        android:textColor="#818181"
        android:textSize="12sp"
        app:layout_constraintBottom_toTopOf="@+id/src_size"
        app:layout_constraintHeight_percent="0.2055"
        app:layout_constraintStart_toEndOf="@+id/src_type_pic"
        app:layout_constraintTop_toTopOf="@+id/src_type_pic"
        app:layout_constraintVertical_bias="1.0"
        app:layout_constraintWidth_percent="0.5576" />

    <ImageView
        android:id="@+id/free_tag"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:src="@drawable/free_bg"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.2083"
        app:layout_constraintHorizontal_bias="0.876"
        app:layout_constraintStart_toEndOf="@+id/src_type_pic"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_percent="0.0833" />

    <ImageView
        android:id="@+id/free_tag_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:src="@drawable/free_text"
        app:layout_constraintBottom_toBottomOf="@+id/free_tag"
        app:layout_constraintEnd_toEndOf="@+id/free_tag"
        app:layout_constraintHeight_percent="0.12"
        app:layout_constraintStart_toStartOf="@+id/free_tag"
        app:layout_constraintTop_toTopOf="@+id/free_tag"
        app:layout_constraintWidth_percent="0.0444" />

    <ImageView
        android:id="@+id/src_more"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="48dp"
        android:layout_marginLeft="48dp"
        android:scaleType="fitCenter"
        android:src="@drawable/src_item_more"
        app:layout_constraintBottom_toBottomOf="@+id/src_size"
        app:layout_constraintHeight_percent="0.1667"
        app:layout_constraintStart_toEndOf="@+id/src_title"
        app:layout_constraintTop_toTopOf="@+id/src_type_pic"
        app:layout_constraintVertical_bias="0.463"
        app:layout_constraintWidth_percent="0.037" />

    <Button
        android:id="@+id/src_more_btn"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@null"
        style="?android:attr/borderlessButtonStyle"
        android:theme="@style/BigRippleWhite"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_percent="1" />

</androidx.constraintlayout.widget.ConstraintLayout>

这里需要注意的是,布局文件中的android:layout_height不能使用默认的march_parent,因为这是一个item的大小,高度要根据需求进行调整。若要实现每个item中的间隔只需要给布局文件中加入 android:layout_marginTop="20dp"即可,具体间隔多少可自行调整。

创建item的实例化Java代码

  首先明确item中有什么需要设置值的控件,然后对每一个控件实现set和get方法即可,代码如下:

package com.example.quanta;

import android.graphics.drawable.Drawable;

public class SrcItem {
    private Drawable typePic;
    private String title;
    private String describe;
    private String size;
    private boolean isFree;

    public SrcItem(Drawable typePic, String title, String describe, String size, boolean isFree){
        this.typePic = typePic;
        this.title = title;
        this.describe = describe;
        this.size = size;
        this.isFree = isFree;
    }

    public Drawable getTypePic() {
        return typePic;
    }

    public void setTypePic(Drawable typePic) {
        this.typePic = typePic;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public boolean isFree() {
        return isFree;
    }

    public void setFree(boolean free) {
        isFree = free;
    }
}

实现创建item并加入储存中的逻辑

  因为我的控件需要对不同的文件类型使用不同的图片(资源文件),所以代码中会有对不同类型的处理,具体代码如下:

    public void createItem(String type, String title, String describe, String size, boolean isFree){
        Drawable typePic = null;
        Resources resources = getResources();
        switch (type){
            case "TXT":
                typePic = resources.getDrawable(R.drawable.txt);
                break;
            case "DOC":
                typePic = resources.getDrawable(R.drawable.doc);
                break;
            case "PPT":
                typePic = resources.getDrawable(R.drawable.ppt);
                break;
            case "RAR":
                typePic = resources.getDrawable(R.drawable.rar);
                break;
            default:
                break;
        }
        SrcItem srcItem = new SrcItem(typePic, title, describe, size, isFree);
        src_list.add(srcItem);
        addIntoContainer();
    }

    public void addIntoContainer(){
        mAdapter = new SrcListRecycleViewAdapter(src_list,getActivity());
        new_src_container.setAdapter(mAdapter);
    }

需要注意的是SrcListRecycleViewAdapter是自定义的RecycleViewAdapter,接下来将会讲解配置自定义RecycleViewAdapter的方法。

配置自定义RecycleViewAdapter

  因为内置的RecycleViewAdapter并不能完成我们需要的所有功能,所以我们要自定义一个RecycleViewAdapter继承自原版的RecycleViewAdapter。

  具体代码如下:

package com.example.quanta;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class SrcListRecycleViewAdapter extends RecyclerView.Adapter<SrcListRecycleViewAdapter.MyViewHolder> {
    private List<SrcItem> itemList;
    private Context context;


    public static class MyViewHolder extends RecyclerView.ViewHolder{
        TextView src_title;
        TextView src_describe;
        TextView src_size;
        ImageView isFreeBG;
        ImageView isFreeText;
        ImageView typePic;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            src_title = itemView.findViewById(R.id.src_title);
            src_describe = itemView.findViewById(R.id.src_describe);
            src_size = itemView.findViewById(R.id.src_size);
            isFreeBG = itemView.findViewById(R.id.free_tag);
            isFreeText = itemView.findViewById(R.id.free_tag_text);
            typePic = itemView.findViewById(R.id.src_type_pic);
        }
    }

    public SrcListRecycleViewAdapter(List<SrcItem> itemList, Context context){
        this.context = context;
        this.itemList = itemList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.src_item,
                parent, false);
        return new MyViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        SrcItem srcItem = this.itemList.get(position);
        holder.src_title.setText(srcItem.getTitle());
        holder.src_size.setText(srcItem.getSize());
        holder.src_describe.setText(srcItem.getDescribe());
        holder.typePic.setImageDrawable(srcItem.getTypePic());
        if (!srcItem.isFree()){
            holder.isFreeBG.setVisibility(View.INVISIBLE);
            holder.isFreeText.setVisibility(View.INVISIBLE);
        }
    }

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

其中onCreateViewHolder方法中的inflate应填入自己的item布局的标识,这个方法是用于创建一个自定义的view并且返回。

  在方法onBindViewHolder中会调用item对象的相关set方法,对指定的控件的值进行修改。因为我的布局的特殊性,isfree字段仅用于判断是否免费,并不对布局中的组件进行赋值操作,而是进行显示或者隐藏的操作,因此使用到了判断语句并实现功能,具体代码为:

        if (!srcItem.isFree()){
            holder.isFreeBG.setVisibility(View.INVISIBLE);
            holder.isFreeText.setVisibility(View.INVISIBLE);
        }

创建item并导入RecycleView中

  我们只需要简单的调用createItem方法即可完成所有一系列的操作,其中createItem中传入的值即修改item布局中值所需要的值,简单的代码如下:

        for (int i = 0; i < 20; i++){
            createItem("DOC", "测试", "小测试代码", "189KB", false);
        }

这段代码便可以创建20个一样的item并显示出来。效果如图:


初めて会ったの日から 僕の心の全てを奪った