自從有了Recycleview揉忘,很多原本是我們的Listview業(yè)務(wù)都被替代了最疆,關(guān)于兩者的簡單比較,可以看這篇文章珍德。我們今天就去看看他背后故事练般,下次再寫Listview,這名征戰(zhàn)多年的老將锈候。
一些不要搞懂的問題
- 為何谷歌推薦用這個薄料,背后的效率是高在哪里?
- LayoutManager是怎么去弄不同布局的
起航
API:23 泵琳,這RecyclerView有一萬多行摄职,看起來真的亞歷山大啊。
我們常用的方式就是下面這樣:
mRecycleView.setAdapter(mAdapter);
扔給他一個適配器获列,所以這個就當作我們的起航的第一個突破口吧谷市,看下他背后都做了些什么事。
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
他先去調(diào)用setLayoutFrozen()
去停止移動击孩,再更新適配器迫悠,最后調(diào)用requestLayout()
去更新界面。這里補充說下巩梢,這個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ā)送一個cancelEvent同時調(diào)用了stopScroll()
去停止?jié)L動,背后是怎么停止?jié)L動的呢且改?
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)又跛。最后才是實際的調(diào)用mLayout的stopSmoothScroller()
去停止碍拆,這個SmoothScroller是一個靜態(tài)的抽象內(nèi)部類,具體干活的是LinearSmoothScroller
這個類最終是這mLayout是LayoutManager
類慨蓝,它是RecycleView的一個靜態(tài)的抽象內(nèi)部類感混,主要負責的是Measuring和Positioning我們的Item views 。
干活的有三個StaggeredGridLayoutManager
礼烈,LinearLayoutManager
弧满,GridLayoutManager
。
StaggeredGridLayoutManager mGridLayoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//兩列豎直方向的瀑布流
mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);
相信使用過RecyclerView的應(yīng)該對這么名字不陌生此熬,經(jīng)典的案例就是拿來修改方向燈庭呜。這個類有個2K行的就不深挖了滑进,點到即可,繼續(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;
}
我們到一個有意思的事情了扶关,他在運行了得情況下并沒有實際的去停止運行,就像我們的AsyncTask一樣数冬,是個假停止节槐。如果沒運行,才調(diào)用SmoothScroller.onStop()
去實際的停止拐纱。
繼續(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();
}
這個更改適配器 的界面,主要就更換了原來的適配器秸架,然后注冊新的數(shù)據(jù)觀察者等操作
重要一句是調(diào)用Recycler的onAdapterChanged()方法揍庄。這個Recycler主要的工作是負責我們在RecyclerView上的各自小itemView的重用功能,所以我們更新了適配器需要告訴下人家咕宿。
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
clear();
getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
}
這樣他就先去調(diào)用clear函數(shù)去清空原有的币绩。再去調(diào)用RecycledViewPool的更新。
需要補充下府阀,這個RecycledViewPool是RecyclerViews的靜態(tài)內(nèi)部類缆镣,他可以讓你做到在不同的RecyclerViews內(nèi)共享Views,這確實對我們的第一個問題有一定的解答作用试浙,因為這是一個靜態(tài)內(nèi)部類啊董瞻,而且我們的View都是繼承自ViewHolder
的,就像我們java的object
給人的感覺一樣田巴。這樣用一個內(nèi)部的ViewPool的做法钠糊,就像線程池,我們可以達到了更高的復(fù)用壹哺,提高滾動的效率抄伍。
private SparseArray<ArrayList<ViewHolder>> mScrap;
這個是RecycledViewPool內(nèi)部使用稀疏數(shù)組來存儲我們的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++;//啊...這句讓我有點意外箩朴,傳的參數(shù)留著以后用岗喉?那就以后再加嘛..
}
public void clear() {
mScrap.clear();
}
這里記錄有多少個適配器,同時保存我們的ViewHolder炸庞,當我們的適配器都移除了钱床,那就清空緩存的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;
}
他的存儲是用viewType來做key從而存儲對應(yīng)的ViewHolder列表埠居。
目前在我的開發(fā)項目中查牌,這個ViewType存在感有點弱啊事期。
查看整個過程,發(fā)現(xiàn)這個itemViewType最后就是調(diào)用的是getItemViewType(int position)
纸颜,默認為0刑赶;
final int type = mAdapter.getItemViewType(offsetPosition);
這個補充一點,在前面的一篇比較RecyclerView和Listview的文章有提到懂衩,如果要給我們的RecyclerView添加頭和尾,不想Listview那樣可以 簡單的加金踪,實際會負責一點浊洞,其中就需要用到這個函數(shù)。具體的看 Listview和RecycleView的簡單比較 這篇文章里面的缺點第一條胡岔。
看完大致的設(shè)置適配器部分內(nèi)容法希,我們繼續(xù)回主線。
到了最后的一個函數(shù)
requestLayout();
因為我們的RecyclerView是直接繼承ViewGroup 的靶瘸,那這句就會導(dǎo)致重畫等步驟苫亦,我們繼續(xù)看下去吧。
說道這里感覺也可以再開個貼怨咪,介紹下View的繪制流程和事件的傳遞流程屋剑,下次有空再寫吧,雖然現(xiàn)在介紹這個已是爛大街的了诗眨,但自己來寫應(yīng)該有什么感覺呢唉匾?寫了才知道 _
繼續(xù):
我們看下實際的繪制界面的部分吧
今天時間有限,下次繼續(xù)寫匠楚。巍膘。。
后記
那個layoutManager可以做很多文章啊芋簿,上次就看到一個有意思的項目叫倫敦眼的
他的效果就像摩天輪一樣繞著轉(zhuǎn)動峡懈!