步驟:
- 首先直接定義一個XRecyclerView繼承RecyclerView,重寫他的三個構造方法。
- init(Context mContext)方法用來初始化底部加載的view
- 回到XRecyclerView,實現init
- 判斷是否滑動到底部,並且進行加載
- 自定義一個adapter來把底部布局加進去。
- 重寫Adapter,通過狀態判斷是否顯示“正在加載”
- 定義一個mDataObserver
1. 首先直接定義一個XRecyclerView繼承RecyclerView,重寫他的三個構造方法。
public XRecylcerView(Context context) { this(context, null); } public XRecylcerView(Context context, AttributeSet attrs) {
this(context, attrs, 0); } public XRecylcerView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); init(context); }
2. init(Context mContext)方法用來初始化底部加載的view
先自定義一個底部布局LoadingMoreFooter繼承Linearlayout,里面是一個居中顯示的ProgressBar和一個TextView,添加一個方法setState(int state),來判定當前刷新的狀態
public void setState(int state) { switch (state) { // 刷新中 case STATE_LAODING:
progressCon.setVisibility(View.VISIBLE); mText.setText("正在刷新"); this.setVisibility(View.VISIBLE); break; // 刷新完成 case STATE_COMPLETE: mText.setText("刷新完成"); this.setVisibility(View.GONE); break;
// 沒有更多數據 case STATE_NOMORE: mText.setText("沒有更多數據啦");
progressCon.setVisibility(View.GONE);
this.setVisibility(View.VISIBLE); break; } }
3. 回到XRecyclerView,實現init
private void init(Context context) { mContext = context; // loadingMoreEnabled為下拉的開關
if (loadingMoreEnabled) { LoadingMoreFooter footerView = new LoadingMoreFooter(mContext); addFootView(footerView); mFootViews.get(0).setVisibility(GONE); } }
RecyclerView的上拉加載,原理很簡單,無非就是當滑動到底部的時候,如果有數據,並且允許加載,就請求數據添加到adapter,而RecyclerView需要做的就是當加載的時候,在底部顯示正在加載來提醒用戶。
4.判斷是否滑動到底部,並且進行加載
1 /**
2 * 監聽滑動,來定位當前滑動到哪個地方 3 * 4 * @param state 5 */
6 @Override 7 public void onScrollStateChanged(int state) { 8 super.onScrollStateChanged(state); 9 if (state == RecyclerView.SCROLL_STATE_IDLE 10 && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) { 11 LayoutManager layoutManager = getLayoutManager(); 12 int lastVisibleItemPosition; 13 if (layoutManager instanceof GridLayoutManager) { 14 lastVisibleItemPosition =
15 ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); 16 } else if (layoutManager instanceof StaggeredGridLayoutManager) { 17 int[] into =
18 new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; 19 ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into); 20 lastVisibleItemPosition = findMax(into); 21 } else { 22 lastVisibleItemPosition =
23 ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); 24 } 25 if (layoutManager.getChildCount() > 0
26 && lastVisibleItemPosition >= layoutManager.getItemCount() - 1
27 && layoutManager.getItemCount() > layoutManager.getChildCount() 28 && !isnomore) { 29 View footView = mFootViews.get(0); 30 isLoadingData = true; 31 if (footView instanceof LoadingMoreFooter) { 32 ((LoadingMoreFooter) footView).setState( 33 LoadingMoreFooter.STATE_LAODING); 34 } else { 35 footView.setVisibility(View.VISIBLE); 36 } 37 mLoadingListener.onLoadMore(); 38 // 一個回調接口,用來加載數據
39 } 40 } 41 }
寫到這個地方,基本的上拉加載的邏輯就搞定了,然后就是處理細節。我們需要把底部布局LoadingMoreFooter加載到RecyclerView,這時候重寫setAdapter(Adapter adapter)方法來添加一個adapter。
5. 自定義一個adapter來把底部布局加進去。自定義的Adapter如下:
1 private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> { 2 private RecyclerView.Adapter adapter; 3 private ArrayList<View> mFootViews; 4 private int headerPosition = 0; 5
6 public WrapAdapter(ArrayList<View> footViews, RecyclerView.Adapter adapter) { 7 this.adapter = adapter; 8 this.mFootViews = footViews; 9 } 10
11 @Override 12 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 13 super.onAttachedToRecyclerView(recyclerView); 14 RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); 15 if (manager instanceof GridLayoutManager) { 16 final GridLayoutManager gridManager = ((GridLayoutManager) manager); 17 gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 18 @Override 19 public int getSpanSize(int position) { 20 return (isFooter(position)) ? gridManager.getSpanCount() : 1; 21 } 22 }); 23 } 24 } 25
26 @Override 27 public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { 28 super.onViewAttachedToWindow(holder); 29 ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 30 if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isFooter(holder.getLayoutPosition())) 31 { 32 StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; 33 p.setFullSpan(true); 34 } 35 } 36
37 public boolean isFooter(int position) { 38 return position < getItemCount() && position >= getItemCount() - mFootViews.size(); 39 } 40
41 public int getFootersCount() { 42 return mFootViews.size(); 43 } 44
45 @Override 46 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 47 if (viewType == TYPE_FOOTER) { 48 return new SimpleViewHolder(mFootViews.get(0)); 49 } 50 return adapter.onCreateViewHolder(parent, viewType); 51 } 52
53 @Override 54 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 55 if (isHeader(position)) { 56 return; 57 } 58 int adjPosition = position; 59 int adapterCount; 60 if (adapter != null) { 61 adapterCount = adapter.getItemCount(); 62 if (adjPosition < adapterCount) { 63 adapter.onBindViewHolder(holder, adjPosition); 64 return; 65 } 66 } 67 } 68
69 @Override 70 public int getItemCount() { 71 if (adapter != null) { 72 return getFootersCount() + adapter.getItemCount(); 73 } else { 74 return getFootersCount(); 75 } 76 } 77
78 @Override 79 public int getItemViewType(int position) { 80 if (isFooter(position)) { 81 return TYPE_FOOTER; 82 } 83 int adjPosition = position; 84 int adapterCount; 85 if (adapter != null) { 86 adapterCount = adapter.getItemCount(); 87 if (adjPosition < adapterCount) { 88 return adapter.getItemViewType(adjPosition); 89 } 90 } 91 return TYPE_NORMAL; 92 } 93
94 @Override 95 public long getItemId(int position) { 96 if (adapter != null) { 97 int adjPosition = position - getHeadersCount(); 98 int adapterCount = adapter.getItemCount(); 99 if (adjPosition < adapterCount) { 100 return adapter.getItemId(adjPosition); 101 } 102 } 103 return -1; 104 } 105
106 private class SimpleViewHolder extends RecyclerView.ViewHolder { 107 public SimpleViewHolder(View itemView) { 108 super(itemView); 109 } 110 } 111 }
就是一個繼承自RecyclerView.Adapter的adapter,主要用於根據類型加載不同的布局,普通的itemView和“正在加載”的底部提示。 定義一個mDataObserver
6. 回到setAdapter(Adapter adapter)方法
/** * 重寫Adapter,通過狀態判斷是否顯示“正在加載” * * @param adapter */ @Override public void setAdapter(Adapter adapter) { this.mAdapter = adapter; this.mWrapAdapter = new WrapAdapter(mFootViews, mAdapter);// 定義WrapAdapter
super.setAdapter(mWrapAdapter);// 通過父類方法將自定義的Adapter重新設置進去
mAdapter.registerAdapterDataObserver(mDataObserver);//請看下面分析
}
查看super.setAdapter()方法,會找到adapter.registerAdapterDataObserver(mObserver)方法,當adapter里面的數據發生改變時會即時監聽並且更新。
那為什么還要把mAdapter再設置一遍呢?其實當我們調用通過我們自定義的RecyclerView來調用setAdapter方法時,只有當WrapAdapter數據改變的時候,才會有更新,而當我們僅僅只更新mAdapter里面的數據的時候,如果不監聽,我們看到的itemView並沒有改變。
7.定義一個mDataObserver
private final RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { mWrapAdapter.notifyDataSetChanged(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { mWrapAdapter.notifyItemMoved(fromPosition, toPosition); } };
都是直接調用父類的方法就可以。
這樣一個RecyclerView的上拉加載邏輯就全部搞定了,這是極其簡單的封裝方法,所以邏輯沒有多么的復雜。
好的,來回顧一下邏輯:重寫RecyclerView進行滑動監聽,當滑動到底部的時候通過重寫setAdapter來將底部視圖加載出來,最后對mAdapter進行數據更改的監聽。
上拉刷新的邏輯更簡單,因為有谷歌的SwipeRefreshLayout,所以實現起來就簡單很多,首先來看布局文件
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe" android:layout_width="match_parent" android:layout_height="wrap_content"/>
<com.baiyyyhjl.pullrecyclerview.recyclerview.XRecylcerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</android.support.v4.widget.SwipeRefreshLayout>
直接用SwipeRefreshLayout將我們剛才自定義的RecyclerView包裹起來,然后swipeRefreshLayout.setOnRefreshListener(this)進行監聽,實現onRefresh()接口來實現加載的邏輯。
就這樣,一個簡單實用的RecyclerView上拉加載,下拉刷新就實現了。沒有多余的布局文件,極其簡便。
當我們項目中有多個RecyclerView並且要求上拉加載,下拉刷新的時候,我們可以定義一個抽象類,只通過修改itemView的布局就能實現。
