自從有了Recycleview,很多原本是我們的Listview業(yè)務(wù)都被替代了,關(guān)于兩者的簡單比較,可以看這篇文章。我們今天就去看看他背后故事,下次再寫Listview,這名征戰(zhàn)多年的老將。
一些不要搞懂的問題
- 為何谷歌推薦用這個(gè),背后的效率是高在哪里?
- LayoutManager是怎么去弄不同布局的
起航
API:23 ,這RecyclerView有一萬多行,看起來真的亞歷山大啊。
我們常用的方式就是下面這樣:
mRecycleView.setAdapter(mAdapter);
扔給他一個(gè)適配器,所以這個(gè)就當(dāng)作我們的起航的第一個(gè)突破口吧,看下他背后都做了些什么事。
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
他先去調(diào)用setLayoutFrozen()
去停止移動(dòng),再更新適配器,最后調(diào)用requestLayout()
去更新界面。這里補(bǔ)充說下,這個(gè)RecyclerView
是直接繼承ViewGroup
的。
public void setLayoutFrozen(boolean frozen) {
if (frozen != mLayoutFrozen) {
...
final long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
onTouchEvent(cancelEvent);
mLayoutFrozen = frozen;
mIgnoreMotionEventTillDown = true;
stopScroll();
}
}
我們看到他背后做的是發(fā)送一個(gè)cancelEvent同時(shí)調(diào)用了stopScroll()
去停止?jié)L動(dòng),背后是怎么停止?jié)L動(dòng)的呢?
public void stopScroll() {
setScrollState(SCROLL_STATE_IDLE);
stopScrollersInternal();
}
private void setScrollState(int state) {
if (state == mScrollState) {
return;
}
...
mScrollState = state;
dispatchOnScrollStateChanged(state);
}
void dispatchOnScrollStateChanged(int state) {
// Let the LayoutManager go first; this allows it to bring any properties into
// a consistent state before the RecyclerView subclass responds.
if (mLayout != null) {
mLayout.onScrollStateChanged(state);
}
// Let the RecyclerView subclass handle this event next; any LayoutManager property
// changes will be reflected by this time.
onScrollStateChanged(state);
// Listeners go last. All other internal state is consistent by this point.
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(this, state);
}
if (mScrollListeners != null) {
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrollStateChanged(this, state);
}
}
}
/**
* Similar to {@link #stopScroll()} but does not set the state.
*/
private void stopScrollersInternal() {
mViewFlinger.stop();
if (mLayout != null) {
mLayout.stopSmoothScroller();
}
}
void stopSmoothScroller() {
if (mSmoothScroller != null) {
mSmoothScroller.stop();
}
}
上面代碼我們看到些有意思的東西,他先去調(diào)用我們的mLayout去設(shè)置狀態(tài)是IDLE閑置狀態(tài),再不通知監(jiān)聽的接口更新狀態(tài)。最后才是實(shí)際的調(diào)用mLayout的stopSmoothScroller()
去停止,這個(gè)SmoothScroller是一個(gè)靜態(tài)的抽象內(nèi)部類,具體干活的是LinearSmoothScroller
這個(gè)類最終是這mLayout是LayoutManager
類,它是RecycleView的一個(gè)靜態(tài)的抽象內(nèi)部類,主要負(fù)責(zé)的是Measuring和Positioning我們的Item views 。
干活的有三個(gè)StaggeredGridLayoutManager
,LinearLayoutManager
,GridLayoutManager
。
StaggeredGridLayoutManager mGridLayoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//兩列豎直方向的瀑布流
mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);
相信使用過RecyclerView的應(yīng)該對這么名字不陌生,經(jīng)典的案例就是拿來修改方向燈。這個(gè)類有個(gè)2K行的就不深挖了,點(diǎn)到即可,繼續(xù)回主線。
/**
* Stops running the SmoothScroller in each animation callback. Note that this does not
* cancel any existing {@link Action} updated by
* {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
* {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
*/
final protected void stop() {
if (!mRunning) {
return;
}
onStop();
mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
mTargetView = null;
mTargetPosition = RecyclerView.NO_POSITION;
mPendingInitialRun = false;
mRunning = false;
// trigger a cleanup
mLayoutManager.onSmoothScrollerStopped(this);
// clear references to avoid any potential leak by a custom smooth scroller
mLayoutManager = null;
mRecyclerView = null;
}
我們到一個(gè)有意思的事情了,他在運(yùn)行了得情況下并沒有實(shí)際的去停止運(yùn)行,就像我們的AsyncTask一樣,是個(gè)假停止。如果沒運(yùn)行,才調(diào)用SmoothScroller.onStop()
去實(shí)際的停止。
繼續(xù)回主線,我們看完 setLayoutFrozen(false)
的過程
現(xiàn)在繼續(xù)下一步
setAdapterInternal(adapter, false, true);
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
...
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
這個(gè)更改適配器 的界面,主要就更換了原來的適配器,然后注冊新的數(shù)據(jù)觀察者等操作
重要一句是調(diào)用Recycler的onAdapterChanged()方法。這個(gè)Recycler主要的工作是負(fù)責(zé)我們在RecyclerView上的各自小itemView的重用功能,所以我們更新了適配器需要告訴下人家。
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
clear();
getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
}
這樣他就先去調(diào)用clear函數(shù)去清空原有的。再去調(diào)用RecycledViewPool的更新。
需要補(bǔ)充下,這個(gè)RecycledViewPool是RecyclerViews的靜態(tài)內(nèi)部類,他可以讓你做到在不同的RecyclerViews內(nèi)共享Views,這確實(shí)對我們的第一個(gè)問題有一定的解答作用,因?yàn)檫@是一個(gè)靜態(tài)內(nèi)部類啊,而且我們的View都是繼承自ViewHolder
的,就像我們java的object
給人的感覺一樣。這樣用一個(gè)內(nèi)部的ViewPool的做法,就像線程池,我們可以達(dá)到了更高的復(fù)用,提高滾動(dòng)的效率。
private SparseArray<ArrayList<ViewHolder>> mScrap;
這個(gè)是RecycledViewPool內(nèi)部使用稀疏數(shù)組來存儲(chǔ)我們的ViewHolder。嗯,稀疏,直覺好像覺得不對啊,后面看完再看下是怎么回事.
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
if (oldAdapter != null) {
detach();
}
if (!compatibleWithPrevious && mAttachCount == 0) {
clear();
}
if (newAdapter != null) {
attach(newAdapter);
}
}
void detach() {
mAttachCount--;
}
void attach(Adapter adapter) {
mAttachCount++;//啊...這句讓我有點(diǎn)意外,傳的參數(shù)留著以后用?那就以后再加嘛..
}
public void clear() {
mScrap.clear();
}
這里記錄有多少個(gè)適配器,同時(shí)保存我們的ViewHolder,當(dāng)我們的適配器都移除了,那就清空緩存的ViewHolder。
我們看下他存的方式
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
scrap = new ArrayList<ViewHolder>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
他的存儲(chǔ)是用viewType來做key從而存儲(chǔ)對應(yīng)的ViewHolder列表。
目前在我的開發(fā)項(xiàng)目中,這個(gè)ViewType存在感有點(diǎn)弱啊。
查看整個(gè)過程,發(fā)現(xiàn)這個(gè)itemViewType最后就是調(diào)用的是getItemViewType(int position)
,默認(rèn)為0;
final int type = mAdapter.getItemViewType(offsetPosition);
這個(gè)補(bǔ)充一點(diǎn),在前面的一篇比較RecyclerView和Listview的文章有提到,如果要給我們的RecyclerView添加頭和尾,不想Listview那樣可以 簡單的加,實(shí)際會(huì)負(fù)責(zé)一點(diǎn),其中就需要用到這個(gè)函數(shù)。具體的看 Listview和RecycleView的簡單比較 這篇文章里面的缺點(diǎn)第一條。
看完大致的設(shè)置適配器部分內(nèi)容,我們繼續(xù)回主線。
到了最后的一個(gè)函數(shù)
requestLayout();
因?yàn)槲覀兊腞ecyclerView是直接繼承ViewGroup 的,那這句就會(huì)導(dǎo)致重畫等步驟,我們繼續(xù)看下去吧。
說道這里感覺也可以再開個(gè)貼,介紹下View的繪制流程和事件的傳遞流程,下次有空再寫吧,雖然現(xiàn)在介紹這個(gè)已是爛大街的了,但自己來寫應(yīng)該有什么感覺呢?寫了才知道 _
繼續(xù):
我們看下實(shí)際的繪制界面的部分吧
今天時(shí)間有限,下次繼續(xù)寫。。。
后記
那個(gè)layoutManager可以做很多文章啊,上次就看到一個(gè)有意思的項(xiàng)目叫倫敦眼的
他的效果就像摩天輪一樣繞著轉(zhuǎn)動(dòng)!