RecyclerView出來的時間已經不短了,現在估計大部分的列表類的需求實現首選肯定是RecyclerView,基本上可以跟ListView說再見了。那麼問題來了,一般情況下一個列表頁面都會有下拉刷新和加載更多功能,RecyclerView本身並沒有下拉刷新和加載更多功能,當然現在已經有很多優秀的開源的支持下拉刷新,加載更多功能的三方RecyclerView,可以直接拿過來用。但是。。。有時候光會用是不夠的,還需要知道它們是這麼實現的,實現的原理是什麼。下面就來介紹一下RecyclerView下拉刷新,加載更多功能的實現套路。
要實現的效果
一、下拉刷新view和加載更多的view放在哪?
要達到上面的效果首先要考慮的是這個頂部下拉的刷新的view和底部加載更多的view放在什麼地方合適,答案就是自定義一個WrapAdapter適配器,通過包裝Adapter來提供header和footer。因爲RecyclerView的Adapter是支持顯示多種不同類型的view的,只需要重寫RecyclerView.Adapter的 getItemViewType(int position)方法,根據不同位置返回不同類型即可。可以利用這個特性把第0個位置和最後一個位置預留出來,固定把第0個item存放下拉刷新的view,把最後一個位置存放加載更多的view。
具體代碼如下:
/**
* 實現顯示頭部和尾部item的adapter,把頭部尾部的事情交給這個adapter來做,其他的交給子adapter
*/
public class MyWrapAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public ArrayList<View> headerViews=new ArrayList<>();
public ArrayList<View> footViews=new ArrayList<>();
public RecyclerView.Adapter adapter;
public MyWrapAdapter(RecyclerView.Adapter adapter, ArrayList<View> headerViews, ArrayList footViews){
this.adapter=adapter;
this.headerViews=headerViews;
this.footViews=footViews;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType== RecyclerView.INVALID_TYPE){
//頭部item
return new RecyclerView.ViewHolder(headerViews.get(0)){};
}else if(viewType== (RecyclerView.INVALID_TYPE-1)){
//尾部item
return new RecyclerView.ViewHolder(footViews.get(0)){};
}
return adapter.onCreateViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(position>=0&&position<headerViews.size()){
return;
}
if(adapter!=null){
int p=position-headerViews.size();
if(p<adapter.getItemCount()){
adapter.onBindViewHolder(holder,p);
}
}
}
@Override
public int getItemViewType(int position) {
if(position>=0&&position<headerViews.size()){
//如果是頭部則返回一個不可用的標識,表示這是頭部item
return RecyclerView.INVALID_TYPE;
}
if(adapter!=null){
int p=position-headerViews.size();
if(p<adapter.getItemCount()){
return adapter.getItemViewType(p);
}
}
return RecyclerView.INVALID_TYPE-1;//默認返回表示是尾部的item
}
@Override
public int getItemCount() {
return getCount();
}
public int getCount(){
int count=headerViews.size()+footViews.size();
if(adapter!=null){
count+=adapter.getItemCount();
}
return count;
}
}
二、下拉刷新的實現
整體實現下拉刷新的思路是,當把下拉刷新view添加到adapter中的第0個item的時候,高度是0,也就看不見下拉刷新的view。當我們下拉刷新的時候根據滑動的值來設置下拉刷新view的高度,這樣下拉刷新的view就慢慢的顯示出來了。但是現在的效果是,先看到的是頂部,下拉刷新view是從頂部開始一點一點顯示的。這樣的效果當然可以,但是我們需要的效果是下拉刷新的view從底部開始一點點顯示出來,做如下調整,我們一開始讓下拉刷新的view的高度就是自身高,讓它距離頂部的距離是負的自身高度,這樣正好就看不見了。並且把下拉刷新view的Gravity設置爲Gravity.BOTTOM,這樣當我們滑動的時候下拉刷新的view就會從底部開始一點一點顯示出來。
現在思路是有了,但是實現的時候,需要解決下面幾個問題:
1、下拉刷新只有在滑動到頂部的時候,纔會觸發下拉刷新,那麼RecyclerView如何判斷滑動到了頂部?
研究了幾個開源的下拉刷新RecyclerView,發現每個實現的方法都不太一樣,踩了幾個坑後。決定用
ViewCompat.canScrollVertically()這個方法來實現,這個方法能夠檢測出RecyclerView是否滑動到了頂部或底部。
2、如何獲得滑動的值
這個老套路了,通過重寫RecyclerView的onTouchEvent來計算獲得。還有一共方式可以獲得滑動的值,重寫LayoutManager的scrollVerticallyBy方法這個方法的參數值是LayoutManager幫我們計算好的滑動距離,但是用這種方式還得自己包一個LayoutManager,所以不考慮,還是老套路吧。。。
三、加載更多的實現
加載更多就比較好實現了,核心是如何判斷recyclerview滾動到了底部。
一種思路給RecyclerView添加addOnScrollListener監聽,重寫onScrollStateChanged方法,在這裏面判斷最後一個可見的view是否是最後一個item即可。
判斷的代碼如下:
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (isRefresh) {
return;
}
if (mState != STATE_NORMAL) {
return;
}
//判斷是否最後一item個顯示出來
LayoutManager layoutManager = getLayoutManager();
//可見的item個數
int visibleChildCount = layoutManager.getChildCount();
if (visibleChildCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && !isLoadMore) {
View lastVisibleView = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
int lastVisiblePosition = recyclerView.getChildLayoutPosition(lastVisibleView);
if (lastVisiblePosition >= layoutManager.getItemCount() - 1) {
footerView.setVisibility(VISIBLE);
isLoadMore = true;
if (myRecyclerViewListener != null) {
myRecyclerViewListener.onLoadMore();
}
} else {
footerView.setVisibility(GONE);
}
}
}
});