View&ViewGroup的绘制机制
写在前面的话
一直对View&ViewGroup没有太多的了解,借此机会系统的认识下我们经常用却不了解的View
说起view绘制的起始分两种情况:
1 第一次填充ui的时候在 即setContentView()中
2 当View&ViewGroup内容改变的时候
先说Acitivity的setContentView()吧:
getWindow().setContentView(layoutResID);
这里的Window是PhoneWindow:
if (mContentParent == null) {
//如果window没有根布局,则为其创建并把这个根布局添加到DecorView之中
//或者这DecorView就是这个根布局
//布局Id: public static final int ID_ANDROID_CONTENT =
// com.android.internal.R.id.content;
//在这里将window和decorView做关联
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
//简而言之就是 mContentParent.addChildView
//可以看 setContentView(View view)方法
mLayoutInflater.inflate(layoutResID, mContentParent);
仔细研究 installDecor()就会得出结论:
image.png
接下来就到了ViewGroup的addView方法中:
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
这里是requestLayout() 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
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
但是这时候只是Activit.onCreate()方法,在这里只是建立了结构关系在requestLayout()中是过不去的 (但是当view绑定了ViewRootImpl就能成功调用parent的requestLayout方法)
不过在这里我有了一个结论,view自身并不进行requestLayout()或者说view不会自身主动调用onMeasure,onLayout,onDraw
在哪里将View和ViewRoot建立联系呢?
答案是Activity启动时,ActivityThread.handleResumeActivity()方法中建立了它们两者的关联关系,(在之前文章里我们说了 View真正显示时机是在ActivityThread的handleResumeActivity时)
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//重点
}
wm 我们知道起始是WindowManager其实现类是 WindowManagerGlobal,让我们看看这个addview方法:
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
原来 ViewRootImpl是在 WindowManager.addView()的时候创建的呀,创建后就调用了ViewRootImpl的方法啊:
这里是setView方法,这个方法干了两件特别重要的事情
//将DecorView和ViewRootImpl建立关系:
1. view.assignParent(this);
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
2. requestLayout();
1.建立了什么样的关系呢?
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
嘿嘿 原来RootViewImpl成了所有view的ParentView,这样后面关系图就解释的通了
2.这里是requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//这里用来检测是否是在主线程
mLayoutRequested = true;
scheduleTraversals();
}
}
//这个request方法来自于ViewParent这个接口(ViewGroup和ViewRootImpl都实现了这个接口)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//重点在这句话:
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//mTraversalRunnable�这里执行了这个Runnable
...
}
}
这里只有一句话 执行 doTraversal()
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//几经流转 终于到了最终的控制方法:
void doTraversal() {
...
performTraversals();
...
}
performTraversals():
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再向下看 如果layoutParams中设置的Weight则需要根据weight计算出子view的宽高再次测量
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在这里调用和ViewRootImpl绑定的View(DecorView)的绘制方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
关系如图所示:
图片.png
这时候我们必须了解下什么是DecorView了:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
当时我在View的measure方法中纠结了好久:
因为view的measre方法只是记录自身的宽高却没有调用父容器的测量方法和调用子View的测量方法
原来是考虑到不同布局测量方式都不太相同 具体实现都在其实现类里如FrameLayout的onMeasure()
接下来是当View&ViewGroup内容改变的时候就会调用当前方法的requestLayout():调用父容器的requestLayout()直到调用到ViewRootImpl的requestLayout。