如果你已经对RecyclerView熟练运用,那你是否想过RecyclerView是如何通过设置如下几行代码,就能实现应变千变万化的UI效果呢
/*==============探究RecyclerView的设计和实现 ==================*/
RecyclerView recyclerView = new RecyclerView(this);
//设置布局方式为线性布局
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
//设置适配器
recyclerView.setAdapter(new MyRcAapter());
某个控件或框架大家都说它设计的如何牛逼,而你也一直在用,那我们何尝不试着深入其源码的实现,看源码的过程,也在学习他人优秀的代码。
下面我们就一起来一探究竟吧!
首先我们从调用setAdapter方法开始入手,看看里面的具体实现是什么:
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)注册观察者,刷新视图
具体的实现是RecyclerView内部的RecyclerViewDataObserver
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();//重新绘制布局
}
}
//....
}
这里的逻辑是,当数据发生变化的时候调用Adapter的notifyDataSetChanged方法之后最终会调用RecyclerViewDataObserver的onChanged函数,然后在onChanged又回调用requestLayout函数进行重新布局。
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();
}
}
//...
}
关于如何布局,RecyclerView中把这个职责则交给了LayoutManager,回到我们调用 setLayoutManager函数,其内部会调用requestLayout函数进行绘制布局,然后就会调用RecyclerView 的onLayout函数,再调用dispatchLayout函数,
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();
//调用分发方法
this.dispatchLayout();
this.resumeRequestLayout(false);
this.mFirstLayoutComplete = true;
}
在dispatchLayout中会调用Adapter 中的getCount函数获取到元素的个数,通过调用LayoutManager的onLayoutChilden函数,对所有子元素进行布局。
这里有三个函数dispatchLayoutStep1 、dispatchLayoutStep2 、dispatchLayoutStep3都会执行,做了简单的逻辑处理,避免重复执行某个方法,其中dispatchLayoutStep2是真正起布局作用的
(如果大家看过ListView的源码,就会知道ListView是在添加到窗口时调用其父类absListView的onAttachedAToWindow函数,然后获取元素的个数,再执行onLayoutChilden函数,并在ListView实现这个函数)
void dispatchLayout() {
//...
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {//没有执行过布局
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// 执行过布局,但是宽高发生改变
mLayout.setExactMeasureSpecsFrom(this);
//真正的视图的布局
dispatchLayoutStep2();
} else {// 执行过布局,宽高没有改变
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
private void dispatchLayoutStep2() {
//...
//获取Item数量
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//执行布局,调用LayoutManager的onLayoutChilden函数
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
//...
}
那么在LayoutManager的onLayoutChildren方法中具体做了什么,则是根据布局模式来布局ItemView,例如从上到下布局,还是从下到上布局,在每一种布局方式中都会调用fill函数,在fill函数中又回循环的layoutChunk函数进行布局,每次布局完之后判断,计算当前屏幕剩余的空间和是否需还有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) {
//存储当前可用空间
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函数
1)首先是从layoutstate中获取到ItemView的布局参数、尺寸信息
2)然后并且根据布局方式计算出Item View的上下左右坐标
3)最后调用layoutDecoratedWithMargins函数实现布局,调用Item View的layout函数将Item View布局到具体的位置。
这么一处理,LayoutManager就把RecyclerView布局的职责分离了出来,这也使得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的布局参数
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的布局参数
measureChildWithMargins(view, 0, 0);
//4 计算该ItemView 需要的宽度或高度
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//ItemView的上下左右的坐标
int left, top, right, bottom;
//5 竖直方向 计算上下左右的坐标
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 {////水平方向 计算上下左右的坐标
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);
}
看到这里,我们已经知道如何布局,还有一个特别重要的点没讲,就是如何获取创建布局和添加数据以及它的缓存机制
那我们需要通过LayoutState对象next这个重要的函数入手
View next(RecyclerView.Recycler recycler) {
//...
//调用Recycler的getViewForPosition
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
从上面可以看到,实际上调用Recycler的getViewForPosition函数,再通过tryGetViewHolderForPositionByDeadline函数返回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) {
//调用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,则需要创建ViewHolder,这里就会调用createViewHolder函数
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)绑定数据,这里会调用Adapter的onBindViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
/*===============设置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的内部类Recycler 中有mAttachedScrap 、mChangedScrap、mCachedViews几个ViewHolder列表对象,它们用于缓冲ViewHolder。
深入tryGetViewHolderForPositionByDeadline函数
1)首先从几个ViewHolder缓存对象获取对应位置的ViewHolder
2)如果没有缓存则调用RecyclerView.Adapter.createViewHolder函数创建ViewHolder
/**
*createViewHolder函数实际是调用了onCreateViewHolder函数创建了ViewHolder
* 这就是为什么在继承RecyclerView.Adapter是需要复写 onCreateViewHolder函数,
*并返回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;
}
//创建ViewHolder,子类需复写
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
3)在调用完RecyclerView.Adapter的onCreateViewHolder后,则执行tryBindViewHolderByDeadline,调用Adapter的onBindViewHolder
//bindViewHolder进行数据绑定,执行完onBindViewHolder函数之后数据就绑定到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);
}
//绑定数据,子类需复写,
public abstract void onBindViewHolder(VH holder, int position);
看到这里,我相信大家对RecyclerView整体的设计有了一定的了解,
这个时候你再使用RecyclerView,继承Adapter并实现onCreateViewHolder 、onBindViewHolder、getItemCount这个三个方法,就知道为什么了,而不再是简单的使用
总结
最后还是要把这篇博客总结一下
RecyclerView 通过Adapter 和观察者模式进行数据绑定,在Adapter中封装了ViewHolder的创建与绑定逻辑,使用起来更加方便,而其缓存单元不同于ListView,而是用ViewHolder代替了View,代替的之前的繁琐的步骤。并且把布局的工作交给了LayoutManager,在LayoutManager的onLayoutChilden中对ItemView 进行布局等一系列操作,这样一来也大大的增加了布局的灵活性。把布局责任独立出来也更符合设计模式中的单一职责原则,减少代码的耦合,使得RecyclerView的布局更具扩张性。
风后面是风,天空上面是天空,而你的生活可以与众不同
网友评论