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,第二個參數:分割線顯示的方向


五. 效果圖

下拉刷新及默認分割線:

上拉加載更多及自定義分割線:


六. 拓展

在MyItemDecoration的構造方法中,獲取圖片資源的getDrawable()方法已經過時,這樣雖然不會報錯,但是並不好

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

谷歌給了新的方法來替換,過時方法被替換肯定是有原因的,我們就是要與時俱進

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

每天進步一點點!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章