Android RecyclerView的使用及下拉刷新和上拉加载更多

一. 简介

       RecyclerView 是android5.0提出的代替ListView的新控件,还可以实现GridView的效果,自带分割线,也可以自定义分割线,增加List显示的美观性,而新增的LayoutManager可用来确定item的排列方式,可以通过LayoutManager来设置list要展示的是垂直还是水平,还添加了默认的增加和删除item动画。

二. 配置

在model下添加:

    compile 'com.android.support:recyclerview-v7:21.0.0'

三. RecyclerView的使用和下拉刷新,上拉加载更多

(1)简单布局

activity_layout.xml布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

注:在布局中SwipeRefreshLayout是下拉加载的控件,需要包裹RecyclerView.

recycler_layout.xml list的布局:

<?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="45dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_recycler"
        android:gravity="center"
        android:background="#0f0"
        android:textColor="#f00"
        android:textSize="20dp"
        android:layout_margin="3dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

footview_layout.xml上拉加载更多的foot布局:

<?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:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/foot"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:textSize="15sp"
        android:background="#fff"
        android:layout_gravity="center"
        android:layout_marginBottom="1dp"/>

</LinearLayout>

注:列表布局我只显示了一个TextView,简单明了。需要注意的是父控件的高度是wrap_content,这样当TextView隐藏的时候,LinnearLayout也就跟着隐藏了,从而达到foot的整体隐藏。

(2)RecyclerView适配器

都知道写ListView展示数据都需要适配器,RecyclerView也不例外

RecyclerViewAdapter.java : 

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyHolder> {

    List<String> list;    //数据集合
    Context context;

    public RecyclerViewAdapter(List<String> list, Context context){
        this.list = list;
        this.context = context;
    }
    
    //初始化item布局
    @Override
    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view =  LayoutInflater.from(context).inflate(R.layout.recycler_layout, parent, false);
        MyHolder holder = new MyHolder(view);
        return holder;
    }

    //想要怎么处理item,等同于BaseAdapter的getView
    @Override
    public void onBindViewHolder(MyHolder holder, int position) {
        holder.textView.setText(list.get(position));
    }

    //展示的数量
    @Override
    public int getItemCount() {
        return list.size();
    }

    //自动的HolderView优化,初始化item控件
    public class MyHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public MyHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_recycler);
        }
    }

}

注:RecyclerView的适配器跟ListView略不同,但可以看出解耦性很高。

(3)MainActivity.java 下拉刷新,上拉加载更多整体实现代码

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private SwipeRefreshLayout swipe;
    private List<String> list;

    private int lastVisibleItem = 0;
    private final int PAGE_COUNT = 20;
    private GridLayoutManager mLayoutManager;
    private ItemAdapter adapter;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
    }

    //添加演示数据
    private void initData() {
        list = new ArrayList<>();
        for (int i = 1; i <= 50; i++)
            list.add("item " + i);

        //下拉刷新
        swipe = (SwipeRefreshLayout) findViewById(R.id.swipe);
        swipe.setColorSchemeColors(Color.RED);          //设置下拉刷新的动画颜色
        swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { //下拉刷新的处理事件
            @Override
            public void onRefresh() {
                swipe.setRefreshing(true);
                Log.i("textShow", "now : " + adapter.getRealLastPosition());
                updateRecyclerView2(0, adapter.getRealLastPosition());//刷新加载50条数据
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        swipe.setRefreshing(false);     //关闭下拉刷新,此处做下拉刷新的结果处理
                    }
                }, 1000);
            }
        });
    }

    private void initView() {
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        adapter = new ItemAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false);
        mLayoutManager = new GridLayoutManager(this, 1);
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setAdapter(adapter);
//        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));

        // 实现上拉加载重要步骤,设置滑动监听器,RecyclerView自带的ScrollListener
        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                // 在newState为滑到底部时
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    // 如果没有隐藏footView,那么最后一个条目的位置就比我们的getItemCount少1,自己可以算一下
                    if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) {
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                // 然后调用updateRecyclerview方法更新RecyclerView
                                if (adapter.getRealLastPosition() == list.size())
                                    updateRecyclerView(0, 0);
                                else
                                    updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
                                Log.i("textShow", "sum1 = " + adapter.getRealLastPosition());
                            }
                        }, 500);
                    }
                    // 如果隐藏了提示条,我们又上拉加载时,那么最后一个条目就要比getItemCount要少2
                    if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                Log.i("textShow", "sum2 = " + adapter.getRealLastPosition());
                                updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
                            }
                        }, 500);

                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                // 在滑动完成后,拿到最后一个可见的item的位置
                lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
//                recyclerView.scrollToPosition(49);
            }
        });
    }

    //还有数据需要加载
    private List<String> getDatas(final int firstIndex, final int lastIndex) {
        List<String> resList = new ArrayList<>();
        for (int i = firstIndex; i < lastIndex; i++) {
            if (i < list.size()) {
                resList.add(list.get(i));
            }
        }
        return resList;
    }

    // 上拉加载时调用的更新RecyclerView的方法
    private void updateRecyclerView(int fromIndex, int toIndex) {
        // 获取从fromIndex到toIndex的数据
        List<String> newDatas = getDatas(fromIndex, toIndex);
        if (newDatas.size() > 0) {
            // 然后传给Adapter,并设置hasMore为true
            adapter.updateList(newDatas, true);
        } else {
            adapter.updateList(null, false);
        }
    }

    // 下拉刷新时调用的更新RecyclerView的方法
    private void updateRecyclerView2(int fromIndex, int toIndex) {
        // 获取从fromIndex到toIndex的数据
        List<String> newDatas = getDatas(fromIndex, toIndex);
        if (newDatas.size() > 0) {
            // 然后传给Adapter,并设置hasMore为true
            adapter.updateList2(newDatas, false);
        } else {
            adapter.updateList2(null, false);
        }
    }

}

四. 添加分割线

(1)新建MyItemDecoration.java类,用于分割线的处理

package com.jmg.recyclerviewtext;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

public class MyItemDecoration extends RecyclerView.ItemDecoration {

    /*
     * 定义4个常量值,代表布局方向,分别是竖向线性布局、横向线性布局、竖向网格布局、横向网格布局
     */
    public static final int LINEAR_LAYOUT_ORIENTATION_VERTICAL = 0;
    public static final int LINEAR_LAYOUT_ORIENTATION_HORIZONTAL = 1;
    public static final int GRID_LAYOUT_ORIENTATION_VERTICAL = 2;
    public static final int GRID_LAYOUT_ORIENTATION_HORIZONTAL =  3;

    private int orientation = -1; // 当前的布局方向
    // 如果是网格布局我们要计算出每一行或者每一列(取决于布局方向)中的子项数目
    private int rawOrColumnSum = 0;
    // Drawable 对象用于绘制分隔线
    private Drawable myDivider = null;

    public MyItemDecoration(Context context, int orientation) {
        /* 这个构造方法用于处理线性布局传入的情况,我们要对myDivider对象进行初始化
        * (绘制的颜色和宽度等等)
        * R.drawable.my_list_divider 是我们自定义的一个drawable资源文件,我们通过
        * myContext来获取它
        */
        myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);

        if(orientation == LinearLayoutManager.HORIZONTAL) {
            this.orientation = LINEAR_LAYOUT_ORIENTATION_HORIZONTAL;
        }else if(orientation == LinearLayoutManager.VERTICAL) {
            this.orientation = LINEAR_LAYOUT_ORIENTATION_VERTICAL;
        }

    }

    public MyItemDecoration(Context context, int orientation, int rawOrColumnSum) {
        // 这个构造方法用于处理网格布局传入的情况,原理同上
        myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);

        if(orientation == GridLayoutManager.HORIZONTAL) {
            this.orientation = GRID_LAYOUT_ORIENTATION_HORIZONTAL;
        } else if(orientation == GridLayoutManager.VERTICAL) {
            this.orientation = GRID_LAYOUT_ORIENTATION_VERTICAL;
        }
        this.rawOrColumnSum = rawOrColumnSum;
    }

    // 在这个方法中。我们对布局方向进行判断,由此来调用正确的分隔线绘制方法
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL ||
                orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) {
            linearLayoutDrawItemDecoration(c, parent);
        } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL ||
                orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) {
            gridLayoutItemDecoration(c, parent);
        }

    }

    /*
     * 当排布方式为线性布局的时候,绘制分割线的方法:
     */
    private void linearLayoutDrawItemDecoration(Canvas canvas, RecyclerView parent) {
        int childCount = parent.getChildCount(); // 获取RecyclerView控件中的子控件总数
        int left, top, right, bottom;
        View child = parent.getChildAt(0);
        // 获取分割线的高度(把分割线看成一个小矩形)
        int drawableHeight = myDivider.getIntrinsicHeight();
        // 如果是竖直排布,那么分割线为横线
        if(orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) {
            left = parent.getLeft();
            right = parent.getRight(); // 获取子控件开始 x 坐标和结束 x 坐标
            for (int i = 1; i < childCount; i++) {
                top = child.getBottom() - drawableHeight/2; // 获取开始点y坐标
                bottom = child.getBottom()  + drawableHeight/2; // 获取结束点y坐标
                myDivider.setBounds(left, top, right, bottom); // 设置绘制区域,下同
                myDivider.draw(canvas); // 在Canvas对象上绘制区域
                child = parent.getChildAt(i);
            }
            // 如果是水平排布,那么分割线为竖线
        } else if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL) {
            top = child.getTop();
            bottom = child.getBottom(); // 获取子控件的开始 y 坐标和结束 y 坐标
            for(int i = 1; i < childCount; i++) {
                left = child.getRight() - drawableHeight/2; // 获取开始点 x 坐标
                right = child.getRight() + drawableHeight/2; // 获取结束点 x 坐标
                myDivider.setBounds(left, top, right, bottom); // 设置绘制区域
                myDivider.draw(canvas);
                child = parent.getChildAt(i);
            }
        }
    }

    /*
     * 当排布方式为网格布局的时候,分割线的绘制方法:
     */
    private void gridLayoutItemDecoration(Canvas canvas, RecyclerView parent) {
        // 顺着布局方向上的要绘制的分割线条数
        int childCount = parent.getChildCount();
        int lineSum = childCount / rawOrColumnSum - 1;
        lineSum += childCount % rawOrColumnSum == 0 ? 0 : 1;
        // 获取分割线的高度(把分割线看成一个小矩形)
        int drawableHeight = myDivider.getIntrinsicHeight();
        int left, right, top, bottom;
        View child = parent.getChildAt(0);

        // 布局方向为竖直排布方式
        if(orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) {
            left = parent.getLeft();
            right = parent.getRight();
            for(int i = 0; i < lineSum; i++) { // 循环用于绘制横向分割线
                child = parent.getChildAt(i*rawOrColumnSum);
                top = child.getBottom() - drawableHeight/2;
                bottom = child.getBottom() + drawableHeight/2;
                myDivider.setBounds(left, top, right, bottom);
                myDivider.draw(canvas);
            }
            top = parent.getTop();
            bottom = parent.getBottom();
            for(int i = 0; i < rawOrColumnSum-1; i++) { // 循环用于绘制竖向分割线
                child = parent.getChildAt(i);
                left = child.getRight() - drawableHeight/2;
                right = child.getRight() + drawableHeight/2;
                myDivider.setBounds(left, top, right, bottom);
                myDivider.draw(canvas);
            }
            // 布局方向为横向排布方式
        } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL) {
            top = parent.getTop();
            bottom = parent.getBottom();
            for(int i = 0; i <= lineSum; i++) { // 循环绘制竖向分割线
                child = parent.getChildAt(i*rawOrColumnSum);
                left = child.getRight() - drawableHeight/2;
                right = child.getRight() + drawableHeight/2;
                myDivider.setBounds(left, top, right, bottom);
                myDivider.draw(canvas);
            }
            left = parent.getLeft();
            right = parent.getRight();
            for(int i = 0; i < rawOrColumnSum; i++) { // 循环绘制横向分割线
                child = parent.getChildAt(i);
                top = child.getBottom() - drawableHeight/2;
                bottom = child.getBottom() + drawableHeight/2;
                myDivider.setBounds(left, top, right, bottom);
                myDivider.draw(canvas);
            }
        }
    }
}
(2)添加分割线颜色,在drawable下新建my_list_divider.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <size android:height="4dp"></size>
    <gradient
        android:startColor="#ff0000"
        android:centerColor="#00ff00"
        android:endColor="#0000ff">
    </gradient>

</shape>

(3)在MainActivity.java中引用

我这里引用的是两个参数的构造方法,第一个参数:context,第二个参数:分割线显示的方向

Android RecyclerView的使用及下拉刷新和上拉加载更多

五. 效果图

下拉刷新及默认分割线:

Android RecyclerView的使用及下拉刷新和上拉加载更多

上拉加载更多及自定义分割线:

Android RecyclerView的使用及下拉刷新和上拉加载更多

六. 拓展

在MyItemDecoration的构造方法中,获取图片资源的getDrawable()方法已经过时,这样虽然不会报错,但是并不好

myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);

谷歌给了新的方法来替换,过时方法被替换肯定是有原因的,我们就是要与时俱进

myDivider = ContextCompat.getDrawable(context,R.drawable.my_list_divider);

每天进步一点点!