如果你已經(jīng)對RecyclerView熟練運用猾愿,那你是否想過RecyclerView是如何通過設(shè)置如下幾行代碼,就能實現(xiàn)應(yīng)變千變?nèi)f化的UI效果呢
/*==============探究RecyclerView的設(shè)計和實現(xiàn) ==================*/
RecyclerView recyclerView = new RecyclerView(this);
//設(shè)置布局方式為線性布局
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
//設(shè)置適配器
recyclerView.setAdapter(new MyRcAapter());
某個控件或框架大家都說它設(shè)計的如何牛逼账阻,而你也一直在用蒂秘,那我們何嘗不試著深入其源碼的實現(xiàn),看源碼的過程淘太,也在學(xué)習(xí)他人優(yōu)秀的代碼姻僧。
下面我們就一起來一探究竟吧!
首先我們從調(diào)用setAdapter方法開始入手蒲牧,看看里面的具體實現(xiàn)是什么:
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
//先移除原來的mAdapter撇贺,注銷觀察者,和從RecyclerView Detached冰抢。
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
//判斷是否清除原有的ViewHolder
if (!compatibleWithPrevious || removeAndRecycleViews) {
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;
//刷新試圖
setDataSetChangedAfterLayout();
}
這里主要做了幾件事事情
1)判斷是否有Adapter,如果有則先移除原來的松嘶,注銷觀察者,和從RecyclerView Detached挎扰。
2)判斷是否清除原有的ViewHolder
3)注冊觀察者,刷新視圖
具體的實現(xiàn)是RecyclerView內(nèi)部的RecyclerViewDataObserver
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();//重新繪制布局
}
}
//....
}
這里的邏輯是翠订,當(dāng)數(shù)據(jù)發(fā)生變化的時候調(diào)用Adapter的notifyDataSetChanged方法之后最終會調(diào)用RecyclerViewDataObserver的onChanged函數(shù)巢音,然后在onChanged又回調(diào)用requestLayout函數(shù)進(jìn)行重新布局。
public abstract static class Adapter<VH extends ViewHolder> {
//....
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
//...
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
//...
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
//...
}
關(guān)于如何布局尽超,RecyclerView中把這個職責(zé)則交給了LayoutManager官撼,回到我們調(diào)用 setLayoutManager函數(shù),其內(nèi)部會調(diào)用requestLayout函數(shù)進(jìn)行繪制布局橙弱,然后就會調(diào)用RecyclerView 的onLayout函數(shù)歧寺,再調(diào)用dispatchLayout函數(shù)主儡,
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
//...
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
//...
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
this.eatRequestLayout();
//調(diào)用分發(fā)方法
this.dispatchLayout();
this.resumeRequestLayout(false);
this.mFirstLayoutComplete = true;
}
在dispatchLayout中會調(diào)用Adapter 中的getCount函數(shù)獲取到元素的個數(shù)竭鞍,通過調(diào)用LayoutManager的onLayoutChilden函數(shù),對所有子元素進(jìn)行布局邓夕。
這里有三個函數(shù)dispatchLayoutStep1 蛀缝、dispatchLayoutStep2 顷链、dispatchLayoutStep3都會執(zhí)行,做了簡單的邏輯處理屈梁,避免重復(fù)執(zhí)行某個方法嗤练,其中dispatchLayoutStep2是真正起布局作用的
(如果大家看過ListView的源碼,就會知道ListView是在添加到窗口時調(diào)用其父類absListView的onAttachedAToWindow函數(shù)在讶,然后獲取元素的個數(shù)煞抬,再執(zhí)行onLayoutChilden函數(shù),并在ListView實現(xiàn)這個函數(shù))
void dispatchLayout() {
//...
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {//沒有執(zhí)行過布局
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// 執(zhí)行過布局构哺,但是寬高發(fā)生改變
mLayout.setExactMeasureSpecsFrom(this);
//真正的視圖的布局
dispatchLayoutStep2();
} else {// 執(zhí)行過布局革答,寬高沒有改變
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
private void dispatchLayoutStep2() {
//...
//獲取Item數(shù)量
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//執(zhí)行布局,調(diào)用LayoutManager的onLayoutChilden函數(shù)
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
//...
}
那么在LayoutManager的onLayoutChildren方法中具體做了什么,則是根據(jù)布局模式來布局ItemView,例如從上到下布局曙强,還是從下到上布局残拐,在每一種布局方式中都會調(diào)用fill函數(shù),在fill函數(shù)中又回循環(huán)的layoutChunk函數(shù)進(jìn)行布局碟嘴,每次布局完之后判斷溪食,計算當(dāng)前屏幕剩余的空間和是否需還有Item View。
public void onLayoutChildren(Recycler recycler, State state) {
//...
if (this.mAnchorInfo.mLayoutFromEnd) {//從下往上布局
this.updateLayoutStateToFillStart(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForStart;
this.fill(recycler, this.mLayoutState, state, false);
startOffset1 = this.mLayoutState.mOffset;
if (this.mLayoutState.mAvailable > 0) {
extraForEnd += this.mLayoutState.mAvailable;
}
this.updateLayoutStateToFillEnd(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForEnd;
this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
endOffset = this.mLayoutState.mOffset;
} else {//從上到下部劇
this.updateLayoutStateToFillEnd(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForEnd;
this.fill(recycler, this.mLayoutState, state, false);
endOffset = this.mLayoutState.mOffset;
if (this.mLayoutState.mAvailable > 0) {
extraForStart += this.mLayoutState.mAvailable;
}
this.updateLayoutStateToFillStart(this.mAnchorInfo);
this.mLayoutState.mExtra = extraForStart;
this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
startOffset1 = this.mLayoutState.mOffset;
}
//...
}
int fill(Recycler recycler, LayoutState layoutState, State state, boolean stopOnFocusable) {
//存儲當(dāng)前可用空間
int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != -2147483648) {
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
this.recycleByLayoutState(recycler, layoutState);
}
//計算RecyclerView的可用布局寬或高
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
//迭代布局Item View
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//布局ItemView
this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
//計算布局偏移量
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
if (!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
//計算剩余的可用空間
remainingSpace -= layoutChunkResult.mConsumed;
}
//...
}
return start - layoutState.mAvailable;
}
接下來我們看一下layoutChunk函數(shù)
1)首先是從layoutstate中獲取到ItemView的布局參數(shù)娜扇、尺寸信息
2)然后并且根據(jù)布局方式計算出Item View的上下左右坐標(biāo)
3)最后調(diào)用layoutDecoratedWithMargins函數(shù)實現(xiàn)布局错沃,調(diào)用Item View的layout函數(shù)將Item View布局到具體的位置。
這么一處理雀瓢,LayoutManager就把RecyclerView布局的職責(zé)分離了出來枢析,這也使得RecyclerView更靈活。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LinearLayoutManager.LayoutState layoutState, LayoutChunkResult result) {
// 1致燥,獲取Item View
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there
// is
// no more items to layout.
result.mFinished = true;
return;
}
//2 獲取 Item View的布局參數(shù)
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//3 測量Item View的布局參數(shù)
measureChildWithMargins(view, 0, 0);
//4 計算該ItemView 需要的寬度或高度
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//ItemView的上下左右的坐標(biāo)
int left, top, right, bottom;
//5 豎直方向 計算上下左右的坐標(biāo)
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {////水平方向 計算上下左右的坐標(biāo)
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
//6 布局Item View
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:"
+ (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
public void layoutDecoratedWithMargins(View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
看到這里,我們已經(jīng)知道如何布局排截,還有一個特別重要的點沒講嫌蚤,就是如何獲取創(chuàng)建布局和添加數(shù)據(jù)以及它的緩存機(jī)制
那我們需要通過LayoutState對象next這個重要的函數(shù)入手
View next(RecyclerView.Recycler recycler) {
//...
//調(diào)用Recycler的getViewForPosition
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
從上面可以看到辐益,實際上調(diào)用Recycler的getViewForPosition函數(shù),再通過tryGetViewHolderForPositionByDeadline函數(shù)返回ViewHolde獲取其中的Item View
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
//...
public View getViewForPosition(int position) {
//調(diào)用Recycler的getViewForPosition
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
//返回ViewHolder的ItemView
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//...
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0)如果有緩存, 從mChangedScrap中獲取ViewHolder緩存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) 從 mAttachedScrap 中獲取ViewHolder 緩存
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//...
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//...
final int type = mAdapter.getItemViewType(offsetPosition);
// 2)從其他ViewHolder緩存中檢測是否有緩存
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//...
// 3)沒有ViewHolder,則需要創(chuàng)建ViewHolder,這里就會調(diào)用createViewHolder函數(shù)
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(this, type);
//...
}
}
//...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 4)綁定數(shù)據(jù),這里會調(diào)用Adapter的onBindViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
/*===============設(shè)置Item View的LayoutParams=================*/
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final RecyclerView.LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (RecyclerView.LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (RecyclerView.LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (RecyclerView.LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
//返回ViewHolder
return holder;
}
}
在RecyclerView的內(nèi)部類Recycler 中有mAttachedScrap 脱吱、mChangedScrap智政、mCachedViews幾個ViewHolder列表對象,它們用于緩沖ViewHolder箱蝠。
深入tryGetViewHolderForPositionByDeadline函數(shù)
1)首先從幾個ViewHolder緩存對象獲取對應(yīng)位置的ViewHolder
2)如果沒有緩存則調(diào)用RecyclerView.Adapter.createViewHolder函數(shù)創(chuàng)建ViewHolder
/**
*createViewHolder函數(shù)實際是調(diào)用了onCreateViewHolder函數(shù)創(chuàng)建了ViewHolder
* 這就是為什么在繼承RecyclerView.Adapter是需要復(fù)寫 onCreateViewHolder函數(shù)续捂,
*并返回ViewHolder的原因
*/
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
//創(chuàng)建ViewHolder,子類需復(fù)寫
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
3)在調(diào)用完RecyclerView.Adapter的onCreateViewHolder后宦搬,則執(zhí)行tryBindViewHolderByDeadline牙瓢,調(diào)用Adapter的onBindViewHolder
//bindViewHolder進(jìn)行數(shù)據(jù)綁定,執(zhí)行完onBindViewHolder函數(shù)之后數(shù)據(jù)就綁定到Item View上
public final void bindViewHolder(VH holder, int position) {
//...
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
//...
}
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
//綁定數(shù)據(jù)间校,子類需復(fù)寫矾克,
public abstract void onBindViewHolder(VH holder, int position);
看到這里,我相信大家對RecyclerView整體的設(shè)計有了一定的了解憔足,
這個時候你再使用RecyclerView胁附,繼承Adapter并實現(xiàn)onCreateViewHolder 、onBindViewHolder滓彰、getItemCount這個三個方法控妻,就知道為什么了,而不再是簡單的使用
總結(jié)
最后還是要把這篇博客總結(jié)一下
RecyclerView 通過Adapter 和觀察者模式進(jìn)行數(shù)據(jù)綁定揭绑,在Adapter中封裝了ViewHolder的創(chuàng)建與綁定邏輯弓候,使用起來更加方便,而其緩存單元不同于ListView,而是用ViewHolder代替了View,代替的之前的繁瑣的步驟洗做。并且把布局的工作交給了LayoutManager,在LayoutManager的onLayoutChilden中對ItemView 進(jìn)行布局等一系列操作弓叛,這樣一來也大大的增加了布局的靈活性。把布局責(zé)任獨立出來也更符合設(shè)計模式中的單一職責(zé)原則诚纸,減少代碼的耦合撰筷,使得RecyclerView的布局更具擴(kuò)張性。
風(fēng)后面是風(fēng)畦徘,天空上面是天空毕籽,而你的生活可以與眾不同