Android简单使用 RecyclerView

对一切来说,只有热爱才是最好的老师,它远远胜过责任感。——爱因斯坦

我们写个例子来简单使用 RecyclerView

导入RecyclerView jar包

implementation 'androidx.recyclerview:recyclerview:1.1.0'

我们来看看操作步骤




我们首先做出一个最简单的例子,来看下RecyclerView的使用方法。首先,在XML中引入RecyclerView:




    


同样需要一个Adapter来将数据和Item视图绑定起来

但不同的的是RecyclerView的Adapter需要派生自RecyclerView.Adapter

package com.example.myrecyclerview;

import android.view.ViewGroup;

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

public class RecyclerAdapter extends RecyclerView.Adapter {

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }
    
}

这三个函数是强制必须重写的

  • onCreateViewHolder 用于得到我们自定义的ViewHolder,在ListView中,我们也会定义ViewHolder来承载视图中的元素.
  • onBindViewHolder 指定位置的数据和视图绑定起来
  • getItemCount 列表总共有多少条

这三项其实在ListView中也都是需要做的,只是这里单独通过回调给列出来了,我们只需要补充上这三个函数,就实现了Adapter了。

在填充RecyclerAdapter之前,我们知道,一般而言ListView的数据都是从外部传进来的,所以我们需要给RecyclerAdapter添加上一个构造函数,将数据从外部传进来:

private Context mContext;
private ArrayList mDatas;

public RecyclerAdapter(Context context, ArrayList datas) {
    mContext = context;
    mDatas = datas;
}

我们传进来的数据非常简单,就是一个String字符串列表,由于在RecyclerAdater中,经常会用到Context,所以我们也把Context传进来,并且保存起来。

接下来,我们就是先创建一个HolderView,然后填充那三个函数。我们都知道HolderView主要是为了保存每一个Item的视图的控件元素。所以我们要先创建一个Item的xml(item_layout.xml):




    


在这个item中,只有一个TextView,所以我们先写一个ViewHolder,ViewHolder的主要作用就是将XML中的控件以变量的形式保存起来,方便我们后面数据绑定。

public class NormalHolder extends RecyclerView.ViewHolder{
        public TextView mTV;

        public NormalHolder(View itemView) {
            super(itemView);

            mTV = (TextView) itemView.findViewById(R.id.item_tv);
            mTV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,mTV.getText(),Toast.LENGTH_SHORT).show();
                }
            });

        }
    }

在创建ViewHolder时,将整个itemView传了进来,然后将TextView从itemView中取出来保存在mTV变量中。我们就要逐个填充RecyclerAdapter的三个函数了。首先是onCreateViewHolder:

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false);
        return new NormalHolder(itemView);
    }

在每一次需要创建ViewHolder时,都会调用onCreateViewHolder函数,所以我们需要在onCreateViewHolder中返回我们创建的ViewHolder实例。然后在onBindViewHolder中,将数据与ViewHolder绑定起来:

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        NormalHolder normalHolder = (NormalHolder) holder;
        normalHolder.mTV.setText(mDatas.get(position));
    }

最后,在getItemCount中返回数据的个数:

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

到这里,整个RecyclerAdapter就实现完了,完整的代码如下,供大家参考:

package com.example.myrecyclerview;

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

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

import java.util.ArrayList;

public class RecyclerAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private ArrayList mDatas;

    public RecyclerAdapter(Context context, ArrayList datas) {
        mContext = context;
        mDatas = datas;
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false);
        return new NormalHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        NormalHolder normalHolder = (NormalHolder) holder;
        normalHolder.mTV.setText(mDatas.get(position));
    }

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

    public class NormalHolder extends RecyclerView.ViewHolder {

        public TextView mTV;

        public NormalHolder(View itemView) {
            super(itemView);
            mTV = itemView.findViewById(R.id.item_tv);
            mTV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, mTV.getText(), Toast.LENGTH_SHORT).show();
                }
            });
        }

    }
}

填充RecyclerView

之后,回到Activity中,首先,构造一个模拟数据的函数,用于填充RecyclerVIew:

public class LinearActivity extends AppCompatActivity {
    …………
    private ArrayList mDatas = new ArrayList<>();

    private void generateDatas() {
        for (int i = 1; i <= 100; i++) {
            mDatas.add("第 " + i + " 个item");
        }
    }
}

之后,在OnCreate函数中填充RecyclerView:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        generateDatas();
        RecyclerView mRv = findViewById(R.id.linearRv);

        //线性布局
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRv.setLayoutManager(linearLayoutManager);

        RecyclerAdapter adapter = new RecyclerAdapter(this, mDatas);
        mRv.setAdapter(adapter);
    }

与ListView唯一的区别是,设置的是LinearLayoutManager,也就是垂直列表。而RecyclerView与ListView不一样的是,它不仅能实现传统的滑动的列表,还能实现GridView和瀑布流造型,或者其它各式各样的特殊造型。而这些造型的实现就是通过LayoutManger来实现的,我们通过Adapter将Item填充了以后,那每个Item怎么摆放是由谁来做的呢?摆放Item的操作就是使用LayoutManager来实现出来的。所以每个LayoutManger所实现的摆放Item的方式都是不一样的,比如:LinearLayoutManager就是传统的ListView的功能,上下滚动或者左右滚动。而GridLayoutManager则是网格摆放,而StaggeredGridLayoutMnager则是瀑布流摆放。

到这里,我们就实现了本部分开头的上下滚动的效果了。


其它LayoutManager

从上面的分析可以看出,摆放Item的操作主要是由LayoutManager来实现的,这也就是RecyclerView可以制作出各种特殊列表样式的原因。系统为我们提供了几个已经写好的LayoutManager:


其中WearableLinearLayoutManager用于在穿戴设备上使用,比如智能手表等,所以我们这里不讨论它。下面我们逐个看下这些LayoutManger所实现的效果。

GridLayoutManager

LayoutManger的职责就是如何摆放Item,所以对于Adapter与RecyclerView是没有影响的,比如为了实现瀑布流效果而需要改变每个Item的宽或高等。一般而言,我们更改LayoutManager,不需要对其它对象操作。所以这也是RecyclerView比较好的一个地方,通过RecycerView本身,Adapter,LayoutManger实现了完全解耦。各自实现各自的功能,与其它部分无关。而GridLayoutManager的主要作用就是将Item进行网格状摆放,进而实现网格布局效果。

所以我们要设置GridLayoutManager时,也只需要更改Acitivity中的设置LayoutManager这块代码即可,其它都不需要动:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        generateDatas();
        RecyclerView mRv = findViewById(R.id.linearRv);

        //线性布局
//        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
//        mRv.setLayoutManager(linearLayoutManager);
        //如果是横向滚动,后面的数值表示的是几行,如果是竖向滚动,后面的数值表示的是几列
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 5);
        gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRv.setLayoutManager(gridLayoutManager);

        RecyclerAdapter adapter = new RecyclerAdapter(this, mDatas);
        mRv.setAdapter(adapter);
    }

其中public GridLayoutManager(Context context, int spanCount),spanCount:如果是竖向滚动,则表示当前划分为几列;如果是横向滚动,则表示当前划分为几行。


可以看到,这里就实现了网格效果,并且是上下滚动。我们通过gridLayoutManager.setOrientation();可以设置RecyclerView的滚动方向,取值有LinearLayoutManager.VERTICAL和LinearLayoutManager.HORIZONTAL

如果我们将它改为横向滚动:

 GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 5);
        gridLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        mRv.setLayoutManager(gridLayoutManager);

在横向滚动的情况下,列表就变成了五行了。

StaggeredGridLayoutMnager

StaggeredGridLayoutMnager主要用来实现瀑布流效果。同样,我们直接把LayoutManager改为StaggeredGridLayoutMnager:

 // 瀑瀑流管理器
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.VERTICAL);
        mRv.setLayoutManager(layoutManager);

其中 public StaggeredGridLayoutManager(int spanCount, int orientation)

  • spanCount:同样表示行数或列数,如果是竖向滚动,则表示当前划分为几列;如果是横向滚动,则表示当前划分为几行。
  • orientation:表示滚动方向,取值有:StaggeredGridLayoutManager.HORIZONTAL和StaggeredGridLayoutManager.VERTICAL



    可以看到,这里由于每个Item的高度是一定的,所有的Item的高度都一样,导致所实现的瀑布流布局跟网格布局完全相同,所以如果想实现瀑布流布局,那必然需要每个Item的高度是不一样的。

所以我们需要修改Adapter,在代码中动态设置每个Item的高度,让每个Item的高度尽量都不一样,这样就可以看到瀑布流效果了。所以,主要修改了这两个地方:

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        NormalHolder normalHolder = (NormalHolder) holder;
        normalHolder.mTV.setText(mDatas.get(position));

        ViewGroup.LayoutParams lp = normalHolder.mTV.getLayoutParams();
        lp.height = getRandomHeight();
        normalHolder.mTV.setLayoutParams(lp);
    }

    private int getRandomHeight() {
        int randomHeight = 0;
        do {
            randomHeight = (int) (Math.random() * 300);
        } while (randomHeight == 0);
        randomHeight += 100;
        return randomHeight;
    }

定义了一个getRandomHeight()函数,得到一个0-300之间的一个数值。然后在onBindViewHolder中,将这个数值设置给TextView,作为TextVIew的高度。


首先,可以看到,这里已经实现了瀑布流效果,但是每当滚动到顶部时,所有的Item会跳动一下,重新布局,这是为什么呢?

这是因为在每次拉到顶部的时候,所有的Item会重新执行一次onBindViewHolder函数,因为我们Item的高度就是在这个函数中随机生成的,所以在拉到顶部时,每个Item的高度就会重新生成,造成的结果就是看起来跳了一下,重新布局了。

所以,要解决这个问题也比较简单,那就是用一个数组,在Adapter初始化的时候,就把每个Item的高度生成好,然后在onBindViewHolder中直接取出即可。

所以,我们可以先申请一个数组,并且在Adapter初始化时,保存每个Item的高度

public class StaggeredRecyclerAdapter extends Adapter {

    private Context mContext;
    private ArrayList mDatas;
    private ArrayList mHeights = new ArrayList<>();

    public StaggeredRecyclerAdapter(Context context, ArrayList datas) {
        mContext = context;
        mDatas = datas;

        if (mDatas.size()>0){
            for (int i = 0;i

然后在onBindViewHolder中,直接使用数组中的高度即可:

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    NormalHolder normalHolder = (NormalHolder) holder;
    normalHolder.mTV.setText(mDatas.get(position));

    ViewGroup.LayoutParams lp = normalHolder.mTV.getLayoutParams();
    lp.height = mHeights.get(position);
    normalHolder.mTV.setLayoutParams(lp);
}

可以看到,这时候,在拉到顶部时,就不会出现重新布局的情况了。到这里,RecyclerView的基本使用方法,就讲完了。

加载不同类型的View

在ListView中,我们经常也会遇到加载不同类型视图的情况,下面我们同样使用RecyclerView来实现加载不同布局视图的效果,在这部分,我们实现一个很常见的分组视图的效果。有细心的同学,估计已经注意到,在public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)中,第二个参数就是View的类型,而且这个类型,我们是可以人为更改的。我们就可以根据类型的不同,传进去不同的参数。

在自定义的Adapter中,我们可以通过getItemViewType函数来返回每个position所对应的类型

@Override
public int getItemViewType(int position) {
    …………
}

下面,我们就在上面的代码上继续更改。首先,我们需要再写一个ViewHolder,用于保存另外一个显示为组标题的Item的视图。




    


将它设置为蓝色背景,并且中间只有一个TextView,居中显示,然后在RecyclerAdapter中,另外再添加一个ViewHolder,命名为SectionHolder

public class SectionHolder extends RecyclerView.ViewHolder {
        public TextView mSectionTv;

        public SectionHolder(View itemView) {
            super(itemView);
            mSectionTv = itemView.findViewById(R.id.item_section_tv);
        }
}

同样,是把xml中的控件取出来,以变量的形式保存在这个ViewHolder中。

绑定ViewHolder

首先,我们需要在getItemViewType函数中,根据位置返回不同的类型:

public static enum ITEM_TYPE {
        ITEM_TYPE_SECTION,
        ITEM_TYPE_ITEM
    }

    private int SECTION_ITEM_NUM  = 10;

    @Override
    public int getItemViewType(int position) {
        if (position % SECTION_ITEM_NUM   == 0) {
            return ITEM_TYPE.ITEM_TYPE_SECTION.ordinal();
        }
        return ITEM_TYPE.ITEM_TYPE_ITEM.ordinal();
    }

我们假设每十个常规Item添加一个组item,所以这里定义了一个常量SECTION_ITEM_NUM ,它的值设定为10,每十个元素返回一个组item的类型数值。为了标识组类型和常规item类型,这里定义一个枚举类型,在其中定义两个值。而ITEM_TYPE.ITEM_TYPE_SECTION.ordinal()的ordinal()函数,则是返回当前枚举值的位置索引。

然后在onCreateViewHolder(ViewGroup parent, int viewType)中根据不同的viewType返回不同的viewHolder:

 @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        if (viewType == ITEM_TYPE.ITEM_TYPE_ITEM.ordinal()) {
            return new NormalHolder(inflater.inflate(R.layout.item_layout, parent, false));
        }
        return new SectionHolder(inflater.inflate(R.layout.item_section_layout, parent, false));
    }

如果是常规类型就返回NormalHolder的实例,如果是标题样式,则返回SectionHolder的实例。最后,是在onBindViewHolder(ViewHolder holder, int position)中将数据与VolderHolder绑定

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof SectionHolder) {
            SectionHolder sectionHolder = (SectionHolder) holder;
            sectionHolder.mSectionTv.setText("第 " + ((position / SECTION_ITEM_NUM) + 1) + " 组");
        } else if (holder instanceof NormalHolder) {
            NormalHolder normalHolder = (NormalHolder) holder;
            normalHolder.mTV.setText(mDatas.get(position));
        }
    }

代码很简单,完整的RecyclerAdapter的代码如下:

package com.example.myrecyclerview;

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

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

import java.util.ArrayList;

public class RecyclerAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private ArrayList mDatas;
    private ArrayList mHeights = new ArrayList<>();

    public RecyclerAdapter(Context context, ArrayList datas) {
        mContext = context;
        mDatas = datas;

        if (mDatas.size() > 0) {
            for (int i = 0; i < mDatas.size(); i++) {
                mHeights.add(getRandomHeight());
            }
        }
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        if (viewType == ITEM_TYPE.ITEM_TYPE_ITEM.ordinal()) {
            return new NormalHolder(inflater.inflate(R.layout.item_layout, parent, false));
        }
        return new SectionHolder(inflater.inflate(R.layout.item_section_layout, parent, false));
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof SectionHolder) {
            SectionHolder sectionHolder = (SectionHolder) holder;
            sectionHolder.mSectionTv.setText("第 " + ((position / SECTION_ITEM_NUM) + 1) + " 组");
        } else if (holder instanceof NormalHolder) {
            NormalHolder normalHolder = (NormalHolder) holder;
            normalHolder.mTV.setText(mDatas.get(position));
        }
    }

    private int getRandomHeight() {
        int randomHeight = 0;
        do {
            randomHeight = (int) (Math.random() * 300);
        } while (randomHeight == 0);
        randomHeight += 100;
        return randomHeight;
    }

    public static enum ITEM_TYPE {
        ITEM_TYPE_SECTION,
        ITEM_TYPE_ITEM
    }

    private int SECTION_ITEM_NUM = 10;

    @Override
    public int getItemViewType(int position) {
        if (position % SECTION_ITEM_NUM == 0) {
            return ITEM_TYPE.ITEM_TYPE_SECTION.ordinal();
        }
        return ITEM_TYPE.ITEM_TYPE_ITEM.ordinal();
    }

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

    public class NormalHolder extends RecyclerView.ViewHolder {

        public TextView mTV;

        public NormalHolder(View itemView) {
            super(itemView);
            mTV = itemView.findViewById(R.id.item_tv);
            mTV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, mTV.getText(), Toast.LENGTH_SHORT).show();
                }
            });
        }

    }

    public class SectionHolder extends RecyclerView.ViewHolder {
        public TextView mSectionTv;

        public SectionHolder(View itemView) {
            super(itemView);
            mSectionTv = itemView.findViewById(R.id.item_section_tv);
        }
    }


}


这篇的内容就到这里了,内容比较简单,单纯只是为了介绍RecyclerView的基本用法,下篇我们就要开始放大招了,讲解ItemDecoration的实现、原理以及炫酷的效果哦。

你可能感兴趣的:(Android简单使用 RecyclerView)