首先在學習 RecyclerView 的源碼之前,建議先閱讀 ListView 的源碼分析,之后學 RecyclerView 會更快更輕松。
傳送門在這里 http://www.itdecent.cn/p/ad621b49735e
一、使用
導包: compile 'com.android.support:recyclerview-v7:23.4.0'
1、創(chuàng)建 Adapter
- 新建類繼承 RecyclerViewAdapter<MyRecyclerViewAdapter.MyViewHolder>,
泛型指定為 MyRecyclerViewAdapter 的內(nèi)部類 MyRecyclerViewAdapter.MyViewHolder, - 快捷鍵創(chuàng)建自定義內(nèi)部類 MyViewHolder 類
- 快捷鍵修改自定義的MyViewHolder 類繼承自 V7 包的 RecyclerView.ViewHolder
- 重寫MyViewHolder 的一個參數(shù)的構造方法
- 快捷鍵導入 RecyclerViewAdapter的抽象方法
1) ViewHolder 類創(chuàng)建步驟
- 首先聲明需要顯示的控件
- 在構造方法中,通過構造方法的參數(shù) View ,由 View.findViewById() 方法初始化各控件
2) Adapter 類實現(xiàn)步驟
- 重寫 onCreateViewHolder() ,調(diào)用 MyViewHolder 的參數(shù)為 View 的構造方法創(chuàng)建
MyViewHolder 對象,該 View 對象通過 LayoutInflater 加載 item 布局的 xml 文件得到,然后將 MyViewholder 作為返回值返回。 - 重寫 onBindViewHolder() ,調(diào)用方法中的 ViewHolder 對象,以及由參數(shù) position
得到要現(xiàn)實的數(shù)據(jù), 將數(shù)據(jù)綁定至 ViewHolder - 重寫 getItemCount() , 返回數(shù)據(jù)集合的 size
2、RecycleView 配置步驟
- 初始化 RecyclrView 對象
- 為 RecyclerView 設置布局管理器,new 一個 LinearLayoutManager可以通過 setOrientation 設置水平或垂直滾動,默認的是垂直滾動的
- 初始化 RecyclerViewAdapter
- 為 RecyclerView 設置 Adapter
1) RecyclerView 設置 ItemClickListener/ItemLongClickListener
- 在 Adapter 中定義 Listener 接口,定義監(jiān)聽方法,在 Adapter 中定義 Listener 對象和 setListener 的方法
- 在 onBindViewHolder() 方法中判斷如果 Listener 對象如果不為空就為 viewHolder.itemView
添加 onClickListener() 方法,在 onClick() 方法中調(diào)用 Listener 接口中的監(jiān)聽方法 - 如果需要添加監(jiān)聽器,就在 RecyclerViewAdapter 對象初始化后調(diào)用 setListener() 方法
2) RecyclerView 的布局管理器
- LinearLayoutManager ListView 型 可以設置水平或垂直滑動
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- GridLayoutMAnager GridView 型 可以設置水平或垂直排列,最后一個參數(shù)表示是否反向排列,即數(shù)據(jù)從尾到頭
GridLayoutManager layoutManager = new GridLayoutManager(this, 4, GridLayoutManager.HORIZONTAL, false);
- StaggeredGridLayoutManager 瀑布流 onBindViewHolder() 方法中為每一個 item 設置不同高度后,即實現(xiàn)瀑布流效果,可以在一個長度為與數(shù)據(jù)集合長度相同的集合中保存每一個 item 的高度,然后在 onBindViewHolder 方法中為 item 設置高度時就可以在這個集合中直接取值。
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);
3) RecyclerView item 添加移除動畫
RecyclerViewAdapter 的 notifyItemInserted(position) 和 notifyItemRemoved(position) 方法默認添加了添加
和移除 item 時的動畫,注意?。?!在添加數(shù)據(jù)時要將 item 高度的集合同時添加數(shù)據(jù),要不然會報 數(shù)組越界異常
源碼: https://github.com/renxuelong/RecycleViewDemo
二、RecyclerView 源碼分析
1. setAdapter 方法
RecyclerView 的 setAdapter 方法中同 ListView 一樣,會使用觀察者模式,將 RecyclerView 作為觀察者,Adapter 作為被觀察者,在 Adapter 中一個集合添加 RecyclerView,在調(diào)用 Adapter 的 notify 系列方法時,會調(diào)用 RecyclerView 中的相應方法實現(xiàn)刷新數(shù)據(jù)
2. setLayoutManager 方法
不同于 ListView 自己實現(xiàn) item 的排列和顯示,RecyclerView 將這一任務交給了 LayoutManager ,由 LayoutManager 負責 item 的排列,更加解耦,同一個 RecyclerView 可以顯示不同的效果
RecyclerView 的 setLayoutManager 方法中,會調(diào)用 requestLayout 方法,我們知道 requestLayout 會申請重新測量、布局、繪制整個過程,在 layout 過程中會調(diào)用 onLayout 方法,onLayout 方法中,會調(diào)用 dispatchLayout 方法,其中會調(diào)用對應 LayoutManager 的 onLayoutChildren 方法
// RecyclerView
public void setLayoutManager(LayoutManager layout) {
...
mLayout = layout;
requestLayout();
}
//RecyclerView
private void dispatchLayoutStep1() {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
}
由于 LayoutManager 的 onLayoutChildren 方法是空方法,其子類具體實現(xiàn)了該方法,以 LinearLayoutManager 為例
// LinearLayoutManager
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
fill(recycler, mLayoutState, state, false);
...
}
// LinearLayoutManager
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 計算可用寬高
// 開啟循環(huán)向其中添加 item
// 1.通過 layoutChunk 方法添加一個 item
// 2. 計算偏移量
// 3. 計算剩余空間,如果還有剩余空間則繼續(xù)循環(huán),如果超出空間則跳出循環(huán)
}
// LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 1. 獲取要顯示的 item
View view = layoutState.next(recycler);
// 2. 獲取 LayoutParams
LayoutParams params = (LayoutParams) view.getLayoutParams();
// 3. 將 item 添加到 RecyclerView addView --> recyclrView.add()
addView(view);
// 4. 測量 item
measureChildWithMargins(view, 0, 0);
// 5. 根據(jù) item 的大小、裝飾大小、布局方向(水平垂直)、RecyclerView 中已經(jīng)占用空間 等信息計算 item 在 RecyclerView 中的坐標
// 6. 根據(jù)上一步得到的坐標布局 item
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
}
// LayoutManager
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
// 布局子 View 確定子 View 在 父 View 的坐標
child.layout(left + insets.left, top + insets.top, right - insets.right,
bottom - insets.bottom);
}
有上面代碼以及注釋可知,在 LayoutManager 的 onLayoutChildren 中,為 RecyclerView 填充了 item,并通過測量和布局過程確定了每一個 item 在 RecyclerView 中的位置。
上面代碼中還有一個很重要的方法,ayoutState.next(recycler) 方法,剛才一筆帶過,現(xiàn)在再來看這個獲取 item 的重要方法
// LayoutState
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
// Recycler 用來存放回收的 ViewHolder 以及提供 item
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
...
// 獲取 item 的 View 對象的方法
View getViewForPosition(int position, boolean dryRun) {
// 1. 從廢棄集合中獲取需要的 ViewHolder
// 2. 如果從廢棄的集合中沒有獲取到,則通過 Adapter 創(chuàng)建
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
// 通過 mAdapter 設置要顯示的 item 的 View 的顯示數(shù)據(jù)
mAdapter.bindViewHolder(holder, holder.mPosition);
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
// 設置默認 LayoutParams
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
// 返回 item 要顯示的 View
return holder.itemView;
}
}
先看第一步,通過 Recycler 對象的 getViewForPosition 方法獲取 item,Recycler 中定義了很多集合,用來存放被回收的 ViewHolder 對象。這幾集合的功能類似 ListView 的 RecycleBin 中的廢棄 item 集合,在 ListView 中是存放廢棄的 item 布局,并在 get 方法中將廢棄的 item 返回。而在 Recycler 中存放的是廢棄的 ViewHolder
從上面源碼中可以看出,getViewForPosition 方法會先從廢棄的從廢棄的 ViewHolder 集合中獲取 ViewHolder,如果沒有對應的廢棄 ViewHolder 則通過 mAdapter.createViewHolder 方法創(chuàng)建 ViewHolder ,還記得這個方法嗎,這個方法是需要我們自定義 Adapter 時重寫的方法,這個方法中根據(jù)當前位置 position 創(chuàng)建了 ViewHolder 并返回。
不管是從廢棄的集合中獲取到的 ViewHolder 還是從 mAdapter 中創(chuàng)建的,之后都會調(diào)用 mAdapter.bindViewHolder 方法,這個方法也是需要我們定義 Adapter 時重寫的,根據(jù) ViewHolder 和當前位置 position 的數(shù)據(jù)初始化 ViewHolder 中 itemView 的顯示
最后在 ViewHolder 的 itemView 設置 LayoutParams 后將 holder.itemView 返回,也就是返回當前 item 處需要顯示的 View
Recycler 獲取 item 要顯示 View 的步驟
- 從廢棄的 ViewHolder 集合中獲取
- 如果沒有對應的廢棄 ViewHolder 則通過 mAdapter 創(chuàng)建 ViewHolder
- 根據(jù) ViewHolder 和當前位置 position 的數(shù)據(jù)初始化 ViewHolder 中 itemView 的顯示
- 返回 View
三、總結
分析了使用 RecyclerView 的使用和原理,我們再來整理一下做一個總結
初始化 RecyclerView 并為其設置 Adapter 之后,就可以為其設置 LayoutManager 來確定 RecyclerView 怎么顯示了,之后 LayoutManager 會根據(jù)數(shù)據(jù)量的大小和 RecyclerView 的空間大小通過循環(huán)來填充 RecyclerView。每次循環(huán)都會從 Recycler 中獲取需要顯示的 View,Recycler 中通過廢棄 ViewHolder 的集合或者 Adapter 的 createViewHolder 方法得到 ViewHolder,然后調(diào)用 mAdapter 的 bindViewHolder 為 ViewHolder 中的 itemView 設置要顯示的數(shù)據(jù)。得到要顯示的 View 后 LayoutManager 會將該 View
添加到 RecyclerView 中并根據(jù) View 的大小和 RecyclerView 剩余空間、布局方式等確定 View 在 RecyclerView 中的坐標,繪制完成之后界面上就會顯示我們添加的 item 了
同 ListView 一樣,在滑動產(chǎn)生時會回收已經(jīng)滑出屏幕的 item 對應的 ViewHoler 到 Recycler 中,這樣在成百上千條數(shù)據(jù)時也不會創(chuàng)建大量的 ViewHolder,保證了程序的性能。