对于一个初学者来说,最需要掌握的技能之一是 ListView 加载布局,不管是同一种布局还是不同的布局,又或者是加载头文件或者加载底部文件。这些掌握起来不是很难,而对于一个中级的搬砖的来说,这些就太过于肤浅了,如果不懂他的原理,那你就只能在一个叫做万恶的 Adapter 上各种吃亏了。那么请允许我来带你揭开它的神秘面纱,能力一般,水平有限,有不对的请告诉我。
ListView 的用法以及为什么 ListView 要这么设计,要使用一个 Adapter 来展示布局,就不细说了,总之一句话,就是为了更好地拓展。
废话少说,进入正题,直接从 setAdapter 方法分析,上源码:
@Override
public void setAdapter(ListAdapter adapter) {
//清空数据操作
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
...
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
省略了一部分,省略号上边的主要是清空数据的操作,还有我们发现源码 ListView 设置的 Adapter 是一个观察者模式啊,我们继续往下看,其实关键代码只有 10 - 13 行 和第 19 行 还有第 41 行代码,下边一一说明:
(1) mOldItemCount:数据改变前的item数量
(2) mItemCount 所有item的数量
(3) mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); mAdapter.getViewTypeCount() 是不是很眼熟,没错,就是设置 View 显示类型的
(4) requestLayout 方法,就是我们要继续往下看的方法了,他便是绘制 ListView 视图的方法。
我们继续往下看源码,接来下看 requestLayout 源码,点击进入这个方法,我们发现这个方法实际上是在 AbsListView 中的犯法,而最终调的确是 View 中的方法,源码如下:
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
//如果当前的整个View树在进行布局流程的话,则会调用requestLayoutDuringLayout()
//让这次的布局延时执行 反正我是没理解
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//PFLAG_FORCE_LAYOUT会在执行View的measure()和layout()方法时判断
//只有设置过该标志位,才会执行measure()和layout()流程
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
//自己布局 则调用父类的requestLayout 开始布局
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
该方法主要是设置了 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 到当前 View 的 Flag 中,这个过程,就是设置当前View 标志位后,就不断的向上调用父布局 View 的 requestLayout(),最后调用到根 View 即 DecorView 的 requestLayout()。然后调用父 View 的 requestLayout 方法,但是我们进入父 View (ViewParent) 发现他就是一个接口,没有具体的实现啊,所以我们需要去找它的实现类,ViewParent 的实现类是 ViewRootImpl,我们来看下 ViewRootImpl 中 requestLayout 的代码:
@Override
public void requestLayout() {
//该boolean变量会在ViewRootImpl.performLayout()开始时置为ture,结束置false
//表示当前不处于Layout过程
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
如果当前没有进行 layout 过程,则会调用 scheduleTraversals() 方法,进入ViewRootImpl.scheduleTraversals()。
void scheduleTraversals() {
if (!mTraversalScheduled) {
//在下一段代码处会置回false
//表示在排好这次绘制请求前,不再排其它的绘制请求
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 我们来解读下这行代码,我们发现在这里调用了一个重要的变量 mChoreographer,它是 Choreographer 类型的,这个对象会请求 Vsync 信号来控制绘制的进行,实现了按帧进行绘制的机制。该方法对于绘制的请求经过了 Choreographer 的编排后,最终会调用回 ViewRootImpl.doTraversal() 方法。
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
根据源码我们发现最后调用的是 performTraversals 方法,自此 ListView 的 measure、layout 和 draw 过程就开始了。
本文只是解读 setAdapter 方法,至于 ListView 的绘制过程和 recycleBin 机制会在后文解答。