該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!java
上文咱們很詳細的分析了ListView的使用、優化、及ListView的RecycleBin機制,讀者若是對ListView不太清楚,那麼請參看個人上篇博文。不過呢,Google Material Design提供的RecyclerView已經逐漸的取代ListView。RecyclerView提供了一種插拔式的體驗,高度的解耦,異常的靈活,經過設置它提供的不一樣LayoutManager,ItemDecoration , ItemAnimator實現使人瞠目的效果。android
若是說上面的理由只是大而空泛的話,那咱們來看如下場景git
除了上述場景外,RecyclerView強制使用了ViewHolder模式,咱們知道ListView使用ViewHolder來進行性能優化,可是這不是必須得,可是在RecyclerView中是必須的,另外RecyclerView還有許多優點,這裏就不一一列舉了,整體來講如今愈來愈多的項目使用RecyclerView,許多老舊項目也漸漸使用RecyclerView來替代ListView。github
注:當咱們想要一個列表顯示控件的時候,須要支持動畫,或者頻繁更新,局部刷新,建議使用RecyclerView,更增強大完善,易擴展;其餘狀況下ListView在使用上反而更加方便,快捷。canvas
前言咱們就講到這,那麼咱們來進入正題。設計模式
做爲一個「新」控件,RecyclerView的使用有許多須要注意的地方緩存
同樣的咱們新建一個Demo來演示RecyclerView的使用性能優化
[RecyclerViewDemo1Activity.java]服務器
public class RecyclerViewDemo1Activity extends AppCompatActivity { @BindView(R.id.recycler_view) RecyclerView mRecyclerView; private List<String> mData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler_demo1_view); ButterKnife.bind(this); //LayoutManager必須指定,不然沒法顯示數據,這裏指定爲線性佈局, mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); //虛擬數據 mData = createDataList(); //設置Adapter必須指定,不然數據怎麼顯示 mRecyclerView.setAdapter(new RecyclerViewDemo1Adapter(mData)); } protected List<String> createDataList() { mData = new ArrayList<>(); for (int i=0;i<20;i++){ mData.add("這是第"+i+"個View"); } return mData; } }
其對應的佈局文件也很簡單activity_recycler_demo1_view.xmlapp
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" > </android.support.v7.widget.RecyclerView> </LinearLayout>
那麼咱們再來看RecyclerViewDemo1Adapter
/** * 與ListView的Adapter不一樣,RecyclerView的Adapter須要繼承RecyclerView.Adapter<VH>(VH是ViewHolder的類名) * 記爲RecyclerViewDemo1Adapter。 * 建立ViewHolder:在RecyclerViewDemo1Adapter中建立一個繼承RecyclerView.ViewHolder的靜態內部類,記爲ViewHolder * (RecyclerView必須使用ViewHolder模式,這裏的ViewHolder實現幾乎與ListView優化時所使用的ViewHolder一致) * 在RecyclerViewDemo1Adapter中實現: * ViewHolder onCreateViewHolder(ViewGroup parent, int viewType): 映射Item Layout Id,建立VH並返回。 * * void onBindViewHolder(ViewHolder holder, int position): 爲holder設置指定數據。 * * int getItemCount(): 返回Item的個數。 * * 能夠看出,RecyclerView將ListView中getView()的功能拆分紅了onCreateViewHolder()和onBindViewHolder()。 */ public class RecyclerViewDemo1Adapter extends RecyclerView.Adapter<RecyclerViewDemo1Adapter.ViewHolder> { private List<String> mData; public RecyclerViewDemo1Adapter(List<String> data) { this.mData = data; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater .from(parent.getContext()) .inflate(R.layout.item_menu_main, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.setData(this.mData.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //item點擊事件 } }); } @Override public int getItemCount() { return this.mData != null ? this.mData.size() : 0; } static class ViewHolder extends RecyclerView.ViewHolder{ private TextView mTextView; public ViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(R.id.tv_title); } public void setData(String title) { this.mTextView.setText(title); } } }
須要注意的是RecyclerView沒有提供如ListView的setOnItemClickListener或者setOnItemLongClickListener之類的Item點擊事件,咱們必須本身去實現該部分功能,實現的方法有不少種,也比較容易,本例中採用在Adapter中BindViewHolder綁定數據的時候爲item設置了點擊事件。
RecyclerView的四大組成分別是:
因此上面代碼的運行結果看起來像是是一個沒有分割線的ListView
上面的基本使用咱們是會了,並且點擊Item也有反應了,不過巨醜無比啊有木有。起碼的分割線都沒有,真無語
那麼如何建立分割線呢,
建立一個類並繼承RecyclerView.ItemDecoration,重寫如下兩個方法:
而後使用RecyclerView經過addItemDecoration()方法添加item之間的分割線。
咱們來看一下代碼
public class RecyclerViewDemo2Activity extends AppCompatActivity { @BindView(R.id.recycler_view) RecyclerView mRecyclerView; private List<String> mData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler_demo1_view); ButterKnife.bind(this); //LayoutManager必須指定,不然沒法顯示數據,這裏指定爲線性佈局, mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); //虛擬數據 mData = createDataList(); //設置Adapter必須指定,不然數據怎麼顯示 mRecyclerView.setAdapter(new RecyclerViewDemo2Adapter(mData)); //設置分割線 mRecyclerView.addItemDecoration( new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); } protected List<String> createDataList() { mData = new ArrayList<>(); for (int i=0;i<20;i++){ mData.add("這是第"+i+"個View"); } return mData; } }
佈局文件還跟上面的一致,代碼也大體相同,不過咱們多了一行
//設置分割線 mRecyclerView.addItemDecoration( new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
這裏的DividerItemDecoration是Google給了一個參考的實現類,這裏咱們經過分析這個例子來看如何自定義Item Decoration。
[DividerItemDecoration.java]
public class DividerItemDecoration extends RecyclerView.ItemDecoration { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; private Drawable mDivider; private int mOrientation; private final Rect mBounds = new Rect(); /** * 建立一個可以使用於LinearLayoutManager的分割線 * */ public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null) { return; } if (mOrientation == VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } @SuppressLint("NewApi") private void drawVertical(Canvas canvas, RecyclerView parent) { canvas.save(); final int left; final int right; if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0; right = parent.getWidth(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
首先看構造函數,構造函數中得到系統屬性android:listDivider,該屬性是一個Drawable對象。接着設置mOrientation,咱們這裏傳入的是DividerItemDecoration.VERTICAL。
上面咱們就說了如何添加分割線,那麼做爲實例,咱們先看DividerItemDecoration的getItemOffsets方法
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } }
outRect是當前item四周的間距,相似margin屬性,如今設置了該item下間距爲mDivider.getIntrinsicHeight()。
那麼getItemOffsets()是怎麼被調用的呢?
RecyclerView繼承了ViewGroup,並重寫了measureChild(),該方法在onMeasure()中被調用,用來計算每一個child的大小,計算每一個child大小的時候就須要加上getItemOffsets()設置的外間距:
public void measureChild(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; ...... }
也就是說getItemOffsets()方法是肯定分割線的大小的(這個大小指的是高度,寬度)
那麼接着onDraw()以及onDrawOver(),二者的做用是什麼呢以及二者之間有什麼關係呢?
public class RecyclerView extends ViewGroup { @Override public void draw(Canvas c) { super.draw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } ...... } @Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } } }
根據View的繪製流程,首先調用RecyclerView重寫的draw()方法,隨後super.draw()即調用View的draw(),該方法會先調用onDraw()(這個方法在RecyclerView重寫了),再調用dispatchDraw()繪製children。所以:ItemDecoration的onDraw()在繪製Item以前調用,ItemDecoration的onDrawOver()在繪製Item以後調用。
在RecyclerView的onDraw()方法中會獲得分割線的數目,並循環調用其onDraw()方法,咱們再來看分割線實例DividerItemDecoration的onDraw()方法
@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null) { return; } if (mOrientation == VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } }
這裏調用了drawVertical
@SuppressLint("NewApi") private void drawVertical(Canvas canvas, RecyclerView parent) { canvas.save(); final int left; final int right; if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0; right = parent.getWidth(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); }
drawVertical的邏輯比較簡單,重要的代碼
//爲分割線設置bounds mDivider.setBounds(left, top, right, bottom); //畫出來 mDivider.draw(canvas);
在RecyclerView中添加分割線須要的操做已經在上文中比較詳細的說明了,這裏再總結一下。咱們在爲RecyclerView添加分割線的時候使用
//設置分割線 mRecyclerView.addItemDecoration( new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
其中addItemDecoration方法的參數即爲分割線的實例,那麼如何建立分割線呢,
建立一個類並繼承RecyclerView.ItemDecoration,重寫如下兩個方法:
RecyclerView沒有提供相似ListView的addHeaderView或者addFooterView方法,因此咱們要本身實現。關於實現的方法也有不少種。目前網上能搜到的主流解決辦法是在Adapter中重寫getItemViewType方法爲頭部或者底部佈局生成特定的item。從而實現頭部佈局以及底部佈局。
本篇的解決辦法與上面的並沒有本質上的不一樣,只是咱們在Adapter的外面再包上一層,以相似裝飾者設計模式的方式對Adapter進行無侵入式的包裝。
咱們但願使用的方式比較簡單
//這個是真正的Adapter,在本例中不須要對其改變 mAdapter = new RecyclerViewDemo2Adapter(mData); //包裝的wrapper,對Adapter進行包裝。實現添加Header以及Footer等的功能 mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter); TextView t1 = new TextView(this); t1.setText("Header 1"); TextView t2 = new TextView(this); t2.setText("Header 2"); mHeaderAndFooterWrapper.addHeaderView(t1); mHeaderAndFooterWrapper.addHeaderView(t2); mRecyclerView.setAdapter(mHeaderAndFooterWrapper); mHeaderAndFooterWrapper.notifyDataSetChanged();
咱們下面先對HeaderAndFooterWrapper基本功能
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { //以較高的數值做爲基數,每個Header或者Footer對應不一樣的數值 private static final int BASE_ITEM_TYPE_HEADER = 100000; private static final int BASE_ITEM_TYPE_FOOTER = 200000; //存儲Header和Footer的集合 private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>(); private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>(); //內部的真正的Adapter private RecyclerView.Adapter mInnerAdapter; public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) { mInnerAdapter = adapter; } private boolean isHeaderViewPos(int position) { return position < getHeadersCount(); } private boolean isFooterViewPos(int position) { return position >= getHeadersCount() + getRealItemCount(); } public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view); } public void addFootView(View view) { mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view); } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFootViews.size(); } }
咱們這裏使用SparseArrayCompat做爲存儲Header和Footer的集合,SparseArrayCompat有什麼特色呢?它相似於Map,只不過在某些狀況下比Map的性能要好,而且只能存儲key爲int的狀況。
咱們這裏能夠看到HeaderAndFooterWrapper是繼承於RecyclerView.Adapter<RecyclerView.ViewHolder>的,這裏爲何要這麼作呢,固然爲了可擴展性,假如RecyclerView須要添加HeaderView或者FooterView,同時呢又須要爲其設置空View,此時添加HeaderView或者FooterView可以使用HeaderAndFooterWrapper,同時呢咱們可使用EmptyWrapper爲RecyclerView設置空View。以下例
//真正進行數據處理以及展現的Adapter mAdapter = new RecyclerViewDemo2Adapter(mData); //添加Header以及Footer的wrapper mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter); //設置空View的wrapper mEmptyWrapperAdapter = new EmptyWrapper(mHeaderAndFooterWrapper); mRecyclerView.setAdapter(mEmptyWrapperAdapter);
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int BASE_ITEM_TYPE_HEADER = 100000; private static final int BASE_ITEM_TYPE_FOOTER = 200000; //SparseArrayCompat相似於Map,其用法與map類似 private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>(); private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>(); private RecyclerView.Adapter mInnerAdapter; public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) { mInnerAdapter = adapter; } /** * 重寫onCreateViewHolder,建立ViewHolder * @param parent 父容器,這裏指的是RecyclerView * @param viewType view的類型,用int表示,也是SparseArrayCompat的key * @return */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mHeaderViews.get(viewType) != null) {//若是以viewType爲key獲取的View爲null //建立ViewHolder並返回 ViewHolder holder = new ViewHolder(parent.getContext(), mHeaderViews.get(viewType)); return holder; } else if (mFootViews.get(viewType) != null) { ViewHolder holder = new ViewHolder(parent.getContext(), mFootViews.get(viewType)); return holder; } return mInnerAdapter.onCreateViewHolder(parent, viewType); } /** * 得到對應position的type * @param position * @return */ @Override public int getItemViewType(int position) { if (isHeaderViewPos(position)) { return mHeaderViews.keyAt(position); } else if (isFooterViewPos(position)) { return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount()); } return mInnerAdapter.getItemViewType(position - getHeadersCount()); } private int getRealItemCount() { return mInnerAdapter.getItemCount(); } /** * 綁定數據 * @param holder * @param position */ @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderViewPos(position)) { return; } if (isFooterViewPos(position)) { return; } mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount()); } /** * 獲得item數量 (包括頭部佈局數量和尾部佈局數量) * @return */ @Override public int getItemCount() { return getHeadersCount() + getFootersCount() + getRealItemCount(); } private boolean isHeaderViewPos(int position) { return position < getHeadersCount(); } private boolean isFooterViewPos(int position) { return position >= getHeadersCount() + getRealItemCount(); } /** *以mHeaderViews.size() + BASE_ITEM_TYPE_HEADER爲key,頭部佈局View爲Value *放入mHeaderViews */ public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view); } public void addFootView(View view) { mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view); } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFootViews.size(); } class ViewHolder extends RecyclerView.ViewHolder { private View mConvertView; private Context mContext; public ViewHolder(Context context, View itemView) { super(itemView); mContext = context; mConvertView = itemView; } } }
看上面的代碼,HeaderAndFooterWrapper繼承於RecyclerView.Adapter<RecyclerView.ViewHolder>,須要重寫相關方法,
/** * 重寫onCreateViewHolder,建立ViewHolder * @param parent 父容器,這裏指的是RecyclerView * @param viewType view的類型,用int表示,也是SparseArrayCompat的key * @return */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mHeaderViews.get(viewType) != null) {//若是以viewType爲key獲取的View爲null //建立ViewHolder並返回 ViewHolder holder = new ViewHolder(parent.getContext(), mHeaderViews.get(viewType)); return holder; } else if (mFootViews.get(viewType) != null) { ViewHolder holder = new ViewHolder(parent.getContext(), mFootViews.get(viewType)); return holder; } return mInnerAdapter.onCreateViewHolder(parent, viewType); }
咱們先看onCreateViewHolder方法,該方法返回ViewHolder,咱們在其中爲頭部以及底部佈局單首創建ViewHolder,對於普通的item,咱們依然調用內部的mInnerAdapter的onCreateViewHolder方法
建立好ViewHolder後,便進行綁定的工做了
/** * 綁定數據 * @param holder * @param position */ @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderViewPos(position)) { return; } if (isFooterViewPos(position)) { return; } mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount()); }
這裏咱們頭部以及底部佈局不進行數據的綁定,其餘普通的item依然調用內部真正的mInnerAdapter.onBindViewHolder
運行結果以下
上面咱們已經初步實現爲RecyclerView添加Header以及Footer了,不過上面的咱們的佈局模式是LinearyLayoutManager,當咱們使用GridLayoutManager時,效果就不是咱們所想像的那樣了
//設置GridLayoutManager mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
當咱們設置GridLayoutManager時,能夠看到頭部佈局所展現的樣子,頭部佈局還真的被當作一個普通的item佈局了。那麼咱們須要爲這個佈局作一些特殊處理。咱們知道使用GridLayoutManager的SpanSizeLookup設置某個Item所佔空間
在咱們的HeaderAndFooterWrapper中重寫onAttachedToRecyclerView方法(該方法在Adapter與RecyclerView相關聯時回調),以下:
@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mInnerAdapter.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int viewType = getItemViewType(position); if (mHeaderViews.get(viewType) != null) { return layoutManager.getSpanCount(); } else if (mFootViews.get(viewType) != null) { return layoutManager.getSpanCount(); } if (spanSizeLookup != null) return spanSizeLookup.getSpanSize(position); return 1; } }); gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount()); } }
當發現layoutManager爲GridLayoutManager時,經過設置SpanSizeLookup,對其getSpanSize方法,返回值設置爲layoutManager.getSpanCount();
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, OrientationHelper.VERTICAL));
當咱們設置StaggeredGridLayoutManager時,能夠看到以下效果
而針對於StaggeredGridLayoutManager,咱們須要使用 StaggeredGridLayoutManager.LayoutParams
在咱們的HeaderAndFooterWrapper中重寫onViewAttachedToWindow方法(該方法在Adapter與RecyclerView相關聯時回調),以下:
@Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { mInnerAdapter.onViewAttachedToWindow(holder); int position = holder.getLayoutPosition(); if (isHeaderViewPos(position) || isFooterViewPos(position)) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } }
上面已經詳細給出了爲RecyclerView添加Header以及Footer的例子,關於EmptyView的實現方法與上面基本相似,讀者可自行實現,固然在本篇末會給出完整的源碼地址。
RecyclerView和ListView的回收機制很是類似,可是ListView是以View做爲單位進行回收,RecyclerView是以ViewHolder做爲單位進行回收。相比於ListView,RecyclerView的回收機制更爲完善
Recycler是RecyclerView回收機制的實現類,他實現了四級緩存:
要想理解RecyclerView的回收機制,咱們就必須從其數據展現談起,咱們都知道RecyclerView使用LayoutManager管理其數據佈局的顯示。
注:如下源碼來自support-v7 25.4.0
LayoutManager是RecyclerView下的一個抽象類,Google提供了LinearLayoutManager,GridLayoutManager以及StaggeredGridLayoutManager基本上能知足大部分開發者的需求。這三個類的代碼都很是長,這要分析下來可了不起。本篇文章只分析LinearLayoutManager的一部份內容
與分析ListView時相似,RecyclerView做爲一個ViewGroup,確定也跑不了那幾大過程,咱們依然仍是隻分析其layout過程
[RecyclerView.java]
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; } void dispatchLayout() { if (mAdapter == null) { Log.e(TAG, "No adapter attached; skipping layout"); // leave the state in START return; } if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START return; } mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { //1 沒有執行過佈局流程的狀況 dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { //2 執行過佈局流程,可是以後size又有變化的狀況 mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else { //3 執行過佈局流程,能夠直接使用以前數據的狀況 mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); }
不過,不管什麼狀況,最終都是完成dispatchLayoutStep1,dispatchLayoutStep2和dispatchLayoutStep3這三步,這樣的狀況區分只是爲了不重複計算。
其中第二步的dispatchLayoutStep2是真正的佈局!
private void dispatchLayoutStep2() { ...... // 設置狀態 mState.mInPreLayout = false; // 更改此狀態,確保不是會執行上一佈局操做 // 真正佈局就是這一句話,佈局的具體策略交給了LayoutManager mLayout.onLayoutChildren(mRecycler, mState); ......// 設置和恢復狀態 }
由上面的代碼能夠知道佈局的具體操做都交給了具體的LayoutManager,那咱們來分析其中的LinearLayoutManager
[LinearLayoutManager.java]
/** *LinearLayoutManager的onLayoutChildren方法代碼也比較多,這裏也不進行逐行分析 *只來看關鍵的幾個點 */ @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ...... //狀態判斷以及一些準備操做 onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); /** *1 感受這個函數應該跟上一篇咱們所分析的ListView的detachAllViewsFromParent();有點像 */ detachAndScrapAttachedViews(recycler); ...... //2 感受這個函數跟上一篇咱們所分析的ListView的fillUp有點像 fill(recycler, mLayoutState, state, false); }
上面已經給出了真正佈局的代碼。咱們仍是按照上一篇的思路來分析,兩次layout
[RecyclerView$LayoutManager]
/** *暫時detach和scrap全部當前附加的子視圖。視圖將被丟棄到給定的回收器中(即參數recycler)。 *回收器(即Recycler)可能更喜歡重用scrap的視圖。 * * @param recycler 指定的回收器Recycler */ public void detachAndScrapAttachedViews(Recycler recycler) { final int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } }
第1次layout時,RecyclerView並無Child,因此跳過該函數,不過咱們從上面的代碼註釋也知道了該函數跟緩存Recycler有關
[LinearLayoutManager.java]
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ...... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {//這裏循環判斷是否還有空間放置item ...... //真正放置的代碼放到了這裏 layoutChunk(recycler, state, layoutState, layoutChunkResult); ...... } if (DEBUG) { validateChildOrder(); } return start - layoutState.mAvailable; }
跟進layoutChunk
[LinearLayoutManager.java]
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { /** *獲取一個View,這個函數應該是重點了, */ View view = layoutState.next(recycler); ...... //添加View addView(view); ...... //計算View的大小 measureChildWithMargins(view, 0, 0); ...... //佈局 layoutDecoratedWithMargins(view, left, top, right, bottom); ...... }
跟進next()
[LinearLayoutManager$LayoutState]
View next(RecyclerView.Recycler recycler) { if (mScrapList != null) { return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }
getViewForPosition方法能夠說是RecyclerView中緩存策略最重要的方法,該方法是從RecyclerView的回收機制實現類Recycler中獲取合適的View,或者新建立一個View
View getViewForPosition(int position, boolean dryRun) { /** *從這個函數就能看出RecyclerView是以ViewHolder爲緩存單位的些許端倪 */ return tryGetViewHolderForPositionByDeadline (position, dryRun, FOREVER_NS).itemView; }
跟進tryGetViewHolderForPositionByDeadline
/** *試圖得到給定位置的ViewHolder,不管是從 *mAttachedScrap、mCachedViews、mViewCacheExtensions、mRecyclerPool、仍是直接建立。 * * @return ViewHolder for requested position */ @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ...... // 1) 嘗試從mAttachedScrap獲取 if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); ...... } if (holder == null) { ...... final int type = mAdapter.getItemViewType(offsetPosition); // 2) 嘗試從mCachedViews獲取 if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } // 3) 嘗試從mViewCacheExtensions獲取 if (holder == null && mViewCacheExtension != null) { ...... final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); ...... } } // 4) 嘗試從mRecyclerPool獲取 if (holder == null) { // fallback to pool holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { // 5) 直接建立 holder = mAdapter.createViewHolder(RecyclerView.this, type); } } ...... // 6) 判斷是否須要bindHolder if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline (holder, offsetPosition, position, deadlineNs); } ...... return holder; }
那麼在第1次layout時,,前4步都不能得到ViewHolder,那麼進入第5, 直接建立
holder = mAdapter.createViewHolder(RecyclerView.this, type); public final VH createViewHolder(ViewGroup parent, int viewType) { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); //這裏終於看到咱們的親人onCreateViewHolder final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; TraceCompat.endSection(); return holder; }
這個onCreateViewHolder正是在RecyclerViewDemo1Adapter中咱們重寫的
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Log.d(TAG,"onCreateViewHolder->viewtype"+viewType); View view = LayoutInflater .from(parent.getContext()) .inflate(R.layout.item_menu_main, parent, false); return new ViewHolder(view); }
初次建立了ViewHolder以後,便進入6,致使咱們重寫的onBindViewHolder回調,數據與View綁定了
從上一篇ListView中咱們就知道了再簡單的View也至少須要兩次Layout,在ListView中經過把屏幕的子View detach並加入mActivieViews,以免重複添加item並可經過attach提升性能,那麼在RecyclerView中,它的作法與ListView十分相似,RecyclerView也是經過detach子View,並把子View對應的ViewHolder加入其1級緩存mAttachedScrap。這部分咱們就不詳細分析了,讀者可參照上一篇的步驟進行分析。
ListView和RecyclerView最大的區別在於數據源改變時的緩存的處理邏輯,ListView是」一鍋端」,將全部的mActiveViews都移入了二級緩存mScrapViews,而RecyclerView則是更加靈活地對每一個View修改標誌位,區分是否從新bindView。
在一些場景下,如界面初始化,滑動等,ListView和RecyclerView都能很好地工做,二者並無很大的差別,可是在須要支持動畫,或者頻繁更新,局部刷新,建議使用RecyclerView,更增強大完善,易擴展
本篇呢,咱們分析了RecyclerView的使用方法以及RecyclerView部分源碼。目的是爲了更好的掌握RecyclerView。
這裏呢再上圖總結一下RecyclerView的layout流程
下篇呢,也是一篇乾貨,上面兩篇文章,咱們的數據都是虛擬的,靜態的,而實際開發中數據一般都是從服務器動態得到的,這也產生了一系列問題,如列表的下拉刷新以及上拉加載、ListVIew異步獲取圖片顯示錯位等等問題
http://blog.csdn.net/lmj623565791/article/details/51854533
此致,敬禮