自從有了Recycleview,很多原本是我們的Listview業務都被替代了,關于兩者的簡單比較,可以看這篇文章。我們今天就去看看他背后故事,下次再寫Listview,這名征戰多年的老將。
一些不要搞懂的問題
- 為何谷歌推薦用這個,背后的效率是高在哪里?
- 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();
}
他先去調用setLayoutFrozen()
去停止移動,再更新適配器,最后調用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();
}
}
我們看到他背后做的是發送一個cancelEvent同時調用了stopScroll()
去停止滾動,背后是怎么停止滾動的呢?
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();
}
}
上面代碼我們看到些有意思的東西,他先去調用我們的mLayout去設置狀態是IDLE閑置狀態,再不通知監聽的接口更新狀態。最后才是實際的調用mLayout的stopSmoothScroller()
去停止,這個SmoothScroller是一個靜態的抽象內部類,具體干活的是LinearSmoothScroller
這個類最終是這mLayout是LayoutManager
類,它是RecycleView的一個靜態的抽象內部類,主要負責的是Measuring和Positioning我們的Item views 。
干活的有三個StaggeredGridLayoutManager
,LinearLayoutManager
,GridLayoutManager
。
StaggeredGridLayoutManager mGridLayoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//兩列豎直方向的瀑布流
mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);
相信使用過RecyclerView的應該對這么名字不陌生,經典的案例就是拿來修改方向燈。這個類有個2K行的就不深挖了,點到即可,繼續回主線。
/**
* 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一樣,是個假停止。如果沒運行,才調用SmoothScroller.onStop()
去實際的停止。
繼續回主線,我們看完 setLayoutFrozen(false)
的過程
現在繼續下一步
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();
}
這個更改適配器 的界面,主要就更換了原來的適配器,然后注冊新的數據觀察者等操作
重要一句是調用Recycler的onAdapterChanged()方法。這個Recycler主要的工作是負責我們在RecyclerView上的各自小itemView的重用功能,所以我們更新了適配器需要告訴下人家。
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
clear();
getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
}
這樣他就先去調用clear函數去清空原有的。再去調用RecycledViewPool的更新。
需要補充下,這個RecycledViewPool是RecyclerViews的靜態內部類,他可以讓你做到在不同的RecyclerViews內共享Views,這確實對我們的第一個問題有一定的解答作用,因為這是一個靜態內部類啊,而且我們的View都是繼承自ViewHolder
的,就像我們java的object
給人的感覺一樣。這樣用一個內部的ViewPool的做法,就像線程池,我們可以達到了更高的復用,提高滾動的效率。
private SparseArray<ArrayList<ViewHolder>> mScrap;
這個是RecycledViewPool內部使用稀疏數組來存儲我們的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++;//啊...這句讓我有點意外,傳的參數留著以后用?那就以后再加嘛..
}
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從而存儲對應的ViewHolder列表。
目前在我的開發項目中,這個ViewType存在感有點弱啊。
查看整個過程,發現這個itemViewType最后就是調用的是getItemViewType(int position)
,默認為0;
final int type = mAdapter.getItemViewType(offsetPosition);
這個補充一點,在前面的一篇比較RecyclerView和Listview的文章有提到,如果要給我們的RecyclerView添加頭和尾,不想Listview那樣可以 簡單的加,實際會負責一點,其中就需要用到這個函數。具體的看 Listview和RecycleView的簡單比較 這篇文章里面的缺點第一條。
看完大致的設置適配器部分內容,我們繼續回主線。
到了最后的一個函數
requestLayout();
因為我們的RecyclerView是直接繼承ViewGroup 的,那這句就會導致重畫等步驟,我們繼續看下去吧。
說道這里感覺也可以再開個貼,介紹下View的繪制流程和事件的傳遞流程,下次有空再寫吧,雖然現在介紹這個已是爛大街的了,但自己來寫應該有什么感覺呢?寫了才知道 _
繼續:
我們看下實際的繪制界面的部分吧
今天時間有限,下次繼續寫。。。
后記
那個layoutManager可以做很多文章啊,上次就看到一個有意思的項目叫倫敦眼的
他的效果就像摩天輪一樣繞著轉動!