安卓RecyclerView那些事-(二)實現(xiàn)一個下拉刷新捏肢、劃到底部加載更多的RecyclerView

這次,我們在RecyclerView的基礎(chǔ)之上來實現(xiàn)一個下拉刷新厘惦,滑到底部加載更多的RecyclerView偷仿。如果你還不熟悉RecyclerView的基本使用方法,請看我的上一篇博客安卓RecyclerView那些事 - (一)了解RecycleView宵蕉。

需求

我們要實現(xiàn)的功能:(1)下拉刷新RecyclerView的數(shù)據(jù)酝静,更新數(shù)據(jù),(2)劃到底部加載更多數(shù)據(jù)羡玛,更新數(shù)據(jù)(3)加載更多數(shù)據(jù)時顯示“正在加載”的footerView

需求分析

1.下拉刷新數(shù)據(jù)

這個功能比較容易實現(xiàn)别智,通常的做法在RecyclerView外部嵌套一個SwipeRefreshLayout,然后給SwipeRefreshLayout添加刷新監(jiān)聽函數(shù)稼稿,在刷新監(jiān)聽函數(shù)中我們重新獲取數(shù)據(jù)薄榛,并更新RecyclerView的數(shù)據(jù)讳窟。還不清楚SwipeRefreshLayout怎么用的同學(xué)可以看這個博客android之官方下拉刷新組件SwipeRefreshLayout;

2.劃到底部加載更多數(shù)據(jù)

要實現(xiàn)這個功能我們首先要判斷什么樣的情況是劃到了RecyclerView的底部。

通過LinearLayoutManager判斷

通過LinearLayoutManager獲得一下幾個參數(shù):item的總個數(shù)敞恋,當(dāng)前可見的最后一個item在所有item中的位置丽啡,當(dāng)前可見的最后一個item的bottom,RecyclerView的bottom硬猫。
我們?yōu)镽ecyclerView添加滑動監(jiān)聽函數(shù)RecyclerView.addOnScrollListener(mOnScrollListener),在滑動監(jiān)聽函數(shù)中我們判斷當(dāng)前可見的最后一個item是不是RecyclerView中所有item的最后一個碌上,并且當(dāng)前可見的最后一個item的bottom是不是等于RecyclerView的bottom。
代碼如下:

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        addOnScrollListener(mOnScrollListener);//添加底部加載接口
    }
    
    /**
     * 滑動監(jiān)聽
     * 滑動到最后一個item的底部時加載更多信息
     */
    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof LinearLayoutManager) {
                LinearLayoutManager linearLayout = (LinearLayoutManager) layoutManager;
                int mLastChildPosition = linearLayout.findLastVisibleItemPosition();//當(dāng)前頁面最后一個可見的item的位置
                int itemTotalCount = linearLayout.getItemCount();//獲取總的item的數(shù)量
                View lastChildView = linearLayout.getChildAt(linearLayout.getChildCount() - 1);//最后一個子view
                Log.e(TAG,"mLastChildPosition:"+mLastChildPosition+",itemTotalCount:"+itemTotalCount);
                int lastChildBottom = lastChildView.getBottom();//最后一個子view的bottom
                int recyclerBottom = getBottom();
                if (mLastChildPosition == itemTotalCount - 1 && lastChildBottom == recyclerBottom) {//當(dāng)前頁面的最后一個item是item全部的最后一個并且當(dāng)前頁面的最后一個item的底部是recycleView的底部的時候浦徊,獲取新數(shù)據(jù)
                    if (listener != null) {
                        //業(yè)務(wù)代碼
                        listener.loadMore();
                    }
                }
            }
        }
    };
通過RecyclerView的canScrollVertically()函數(shù)判斷

canScrollVertically()是在View中定義的馏予,用來判斷是否能進(jìn)行滑動。我們看一下這個方法怎么用


boolean canScrollVertically(int)

這個方法需要傳入一個int型變量盔性,當(dāng)這個變量為正時函數(shù)返回否能向上滑動霞丧,為負(fù)時返回能否向下滑動。

同樣冕香,我們要利用canScrollVertically實現(xiàn)判斷是否滑倒底部蛹尝,需要在RecyclerView的滑動監(jiān)聽函數(shù)中判斷canScrollVertically(1)的值。代碼如下

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        addOnScrollListener(mOnScrollListener);//添加底部加載接口
    }

    /**
     * 滑動監(jiān)聽
     * 滑動到最后一個item的底部時加載更多信息
     */
    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if(!canScrollVertically(1)){
                if (listener != null) {
                        listener.loadMore();
                }
            }
        }
    };

3.RecyclerView自定義footerView

我們知道Adapter通過先調(diào)用onCreateViewHolder(ViewGroup parent, int viewType)創(chuàng)建視圖(當(dāng)創(chuàng)建足夠的ViewHolder后便不再創(chuàng)建悉尾,我的上一篇博客 安卓RecyclerView那些事 - (一)了解RecycleView)突那,然后調(diào)用onBindViewHolder(MyViewHolder holder, int position)為視圖綁定數(shù)據(jù)。
大家有沒有注意onCreateViewHolder(ViewGroup parent, int viewType)的參數(shù)中有一個viewType构眯,那這個參數(shù)是哪里來的呢愕难?其實RecyclerView還有一個方法getItemViewType(int position)
其實,Adapter創(chuàng)建每一個item時惫霸,先調(diào)用getItemViewType(int position)獲得item的類型猫缭,然后調(diào)用onCreateViewHolder(ViewGroup parent, int viewType)創(chuàng)建視圖,最后調(diào)用onBindViewHolder(MyViewHolder holder, int position)為視圖綁定數(shù)據(jù)壹店。順序如下圖所示猜丹。

image.png

我們可以在getItemViewType(int position)中根據(jù)position返回不同的ViewType,在onCreateViewHolder(ViewGroup parent, int viewType)中根據(jù)viewType創(chuàng)建不同的ViewHolder,最后在onBindViewHolder(MyViewHolder holder, int position)中綁定數(shù)據(jù)硅卢。這樣射窒,就可以在RecyclerView中顯示不同類型的item,甚至可以在RecyclerView中嵌套RecyclerView等各種View将塑。

代碼實現(xiàn)

1.自定義一個ViewHolder

public class MyViewHolder extends RecyclerView.ViewHolder{
    private SparseArray<View> mHolderView;//緩存子控件View
    private View ParentView;    //最外層view

    public MyViewHolder(View itemView) {
        super(itemView);
        this.ParentView=itemView;

        if(mHolderView==null){
            mHolderView=new SparseArray<>();
        }
    }

    /**
     * 根據(jù)layoutID創(chuàng)建ViewHolder
     * @param parent
     * @param layoutId
     * @return
     */
    public static MyViewHolder createViewHolder(ViewGroup parent,int layoutId){
        View view= LayoutInflater.from(parent.getContext()).inflate(layoutId,parent,false);
        return new MyViewHolder(view);
    }

    /**
     * 根據(jù)View創(chuàng)建viewHolder
     * @param view
     * @return
     */
    public static MyViewHolder createViewHolder(View view){
        return new MyViewHolder(view);
    }

    /**
     * 獲取View
     * @param id
     * @param <T>
     * @return
     */
    public <T extends View> T getView(int id){
        View view=mHolderView.get(id);
        if(view==null){
            view=ParentView.findViewById(id);
            mHolderView.put(id,view);
        }
        return (T) view;
    }
}

2.定義一個Model,用于模擬數(shù)據(jù)加載

public class Model {
    public static List<Integer> getData(){
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<20;i++){
            list.add(i);
        }
        return list;
    }
}

3.自定義Adapter

重寫Adapter中的getItemViewType脉顿,onCreateViewHolder,onBindViewHolder抬旺,getItemCount弊予。

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    private List<Integer> list;//數(shù)據(jù)list
    private static final String TAG="MyAdapter";
    public MyAdapter(List<Integer> list) {
        this.list = list;
    }

    /**
     * 返回view類型
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        Log.e(TAG,"position:"+position);
        Log.e(TAG,"getItemViewType");
        if (position == getItemCount() - 1)//如果是最后一個item祥楣,則是底部布局
            return VIEW_TYPE_FOOTER;
        return VIEW_TYPE_NOMAL;  //正常item
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.e(TAG,"onCreateViewHolder");
        if (viewType == VIEW_TYPE_FOOTER)
            return MyViewHolder.createViewHolder(parent, R.layout.item_root_footer);//返回底部布局
        return MyViewHolder.createViewHolder(parent, R.layout.item_normal); //返回正常item
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Log.e(TAG,"onBindViewHolder");
        int viewType = getItemViewType(position);
        if (viewType == VIEW_TYPE_NOMAL) {//正常item需要綁定數(shù)據(jù)
            TextView textView=holder.getView(R.id.item_TV);
            textView.setText(list.get(position).toString());
        }
    }

    /**
     * 返回item的數(shù)量
     * 因為在原有數(shù)據(jù)數(shù)量基礎(chǔ)上加了一個底部布局开财,所以總的item數(shù)量應(yīng)該+1
     * @return
     */
    @Override
    public int getItemCount() {
        int count = list.size();
        count++;   //因為多了一個footerView汉柒,所有item的總數(shù)應(yīng)該+1
        return count;
    }

    /**
     * 刷新數(shù)據(jù)
     * @param mList
     */
    public void notifyAllDatas(List<Integer> mList,MyRecyclerView recyclerView) {
        this.list = mList;
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });
    }
}

4.自定義一個RecyclerView

public class MyRecyclerView extends RecyclerView{
    private static final String TAG="MyRecyclerView";
    private OnFooterAutoLoadMoreListener listener;//監(jiān)聽底部
    public static final int VIEW_TYPE_NOMAL = 0;//item的類型-正常的item
    public static final int VIEW_TYPE_FOOTER = 200;//item的類型-底部

    public MyRecyclerView(Context context) {
        this(context,null);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        addOnScrollListener(mOnScrollListener);//添加底部加載接口
    }

    /**
     * 滑動監(jiān)聽
     * 滑動到最后一個item的底部時加載更多信息
     */
    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if(!canScrollVertically(1)){
                if (listener != null) {
                        listener.loadMore();
                }
            }
        }
    };
    /**
     * 添加底部加載接口
     * @param listener
     */
    public void addFooterAutoLoadMoreListener(OnFooterAutoLoadMoreListener listener){
        this.listener=listener;
    }
}

5.使用MyRecyclerView

布局如下

<?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="com.tinymonster.myrecyclerview.MainActivity">

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

        <com.tinymonster.myrecyclerview.MyRecyclerView
            android:id="@+id/myRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

代碼如下

package com.tinymonster.myrecyclerview;

import android.content.Intent;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;

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

public class MainActivity extends AppCompatActivity implements OnFooterAutoLoadMoreListener{
    private SwipeRefreshLayout swipeRefreshLayout;
    private MyRecyclerView myRecyclerView;
    private List<Integer> dataList=new ArrayList<>();
    private MyAdapter myAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeRefreshLayout);
        myRecyclerView=(MyRecyclerView)findViewById(R.id.myRecyclerView);
        myRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        myRecyclerView.addFooterAutoLoadMoreListener(this);
        myAdapter=new MyAdapter(dataList);
        myRecyclerView.setAdapter(myAdapter);
        loadMore();
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                myAdapter.notifyAllDatas(dataList,myRecyclerView);
                dataList.clear();
                dataList.addAll(Model.getData());
                swipeRefreshLayout.setRefreshing(false);
            }
        });
    }

    @Override
    public void loadMore() {
        myAdapter.notifyAllDatas(dataList,myRecyclerView);
        List<Integer> list=Model.getData();
        dataList.addAll(list);
        swipeRefreshLayout.setRefreshing(false);
    }
}

代碼已經(jīng)上傳到GitHub,如果你覺得有幫助责鳍,請幫忙點一個星星碾褂。

https://github.com/Tiny-Monster/MyRecyclerView

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市历葛,隨后出現(xiàn)的幾起案子正塌,更是在濱河造成了極大的恐慌,老刑警劉巖恤溶,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乓诽,死亡現(xiàn)場離奇詭異,居然都是意外死亡咒程,警方通過查閱死者的電腦和手機鸠天,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帐姻,“玉大人稠集,你說我怎么就攤上這事〖⒋桑” “怎么了剥纷?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呢铆。 經(jīng)常有香客問我晦鞋,道長,這世上最難降的妖魔是什么棺克? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任鳖宾,我火速辦了婚禮,結(jié)果婚禮上逆航,老公的妹妹穿的比我還像新娘鼎文。我一直安慰自己,他們只是感情好因俐,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布拇惋。 她就那樣靜靜地躺著,像睡著了一般抹剩。 火紅的嫁衣襯著肌膚如雪撑帖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天澳眷,我揣著相機與錄音胡嘿,去河邊找鬼。 笑死钳踊,一個胖子當(dāng)著我的面吹牛衷敌,可吹牛的內(nèi)容都是我干的勿侯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缴罗,長吁一口氣:“原來是場噩夢啊……” “哼助琐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起面氓,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兵钮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舌界,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掘譬,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年呻拌,在試婚紗的時候發(fā)現(xiàn)自己被綠了屁药。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡柏锄,死狀恐怖酿箭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趾娃,我是刑警寧澤缭嫡,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站抬闷,受9級特大地震影響妇蛀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笤成,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一评架、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炕泳,春花似錦纵诞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至籽腕,卻和暖如春嗡呼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皇耗。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工南窗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓万伤,卻偏偏與公主長得像窒悔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壕翩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容