這次,我們在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)行滑動。我們看一下這個方法怎么用
這個方法需要傳入一個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ù)壹店。順序如下圖所示猜丹。
我們可以在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