前言:内容稍显枯燥,若有兴趣,一起交流经验
View是如何被添加到屏幕窗口上
1、创建顶层布局容器DecorView
首先,从activity的setContentView方法入手,其实是调用了 Window的setContentView方法:
public void setContentView(int layoutResID) {
//这里的getWindow就是得到一个window对象
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Window其实是一个抽象类,在顶部的注释中有这样一句话:
The only existing implementation of this abstract class is
android.policy.PhoneWindow, which you should instantiate when needing a Window.
当需要Window时,PhoneWindow是唯一的实现。
而PhoneWindow是Window的实现类,那么activity的的setContentView最终也是在PhoneWindow的setContentView方法实现一系列的逻辑。去PhoneWindow类找到对应方法,发现主要做了两件事:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null)
//①第一件事情,具体是什么下面再说...
installDecor();
} else if(){
//省略
}
if ()) {
//省略
} else {
//②第二件事情,解析了在activity传入的布局资源
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//省略many
}
我们先看installDecor()做了什么操作,发现最终直接new了一个DecorView,跟踪发现其实DecorView是集成自FrameLayout,说明它是一个容器(埋下伏笔)
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
}
//省略
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
2、在顶层布局中加载基础布局ViewGroup
protected ViewGroup generateLayout(DecorView decor) {
//省略一些通过主题设置的一些参数的代码
//下面这一段有一个共同点,都是 layoutResource = R.layout.xxx,说明是根据不同的情况设置不同不急资源
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//最后在这里,创建了一个View,然后把View添加到了decor上
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//不同的布局为文件布局都是framelayout并且ID都是 com.android.internal.R.id.content
3、将ContentView添加到基础布局的FrameLayout
//继续跟踪源码
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
/**
* 这个ID定义在Window中,不再此方法中类中
* The ID that the main layout in the XML layout file should have.主容器中一定存在的ID
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
//最后在这里直接返回了contentParent
return contentParent;
//最后在setContentView方法中,将我们的父容器添加到基础容器中
mLayoutInflater.inflate(layoutResID, mContentParent);
从一张图看整个流程
其实从上面的分析来看,在activity设置的 R.layout.xxx 最后都显示到contentParent上了,也就是基础布局的framelayout;难得码文字了,一张图小结一下:
View的绘制流程
1、绘制入口、类和方法
在ActivityThread类中找到一个handleMessage() 方法,这个方法存在于一个集成自handler的 H 的对象,是主线程处理消息的一个对象,找到case LAUNCH_ACTIVITY:
case LAUNCH_ACTIVITY:
//省略源码
这里启动一个activity
handleLaunchActivity(r, null);
//进入handleLaunchActivity()方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略源码
//这里执行了启动activity的操作,在performLaunchActivity方法中调用了activity的attach()方法,在attach()执行了并创建上一节讲到的window相关的东西
Activity a = performLaunchActivity(r, customIntent);
//继续往下看
if (a != null) {
//省略源码
//这里执行了下面这个方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
//进入这个方法
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// TODO Push resumeArgs into the activity for consideration
//这里其实就是回调了activity的onResume()方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
//继续往下看
if (r.window == null && !a.mFinished && willBeVisible) {
//获取到DecorView对象
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取ViewManager对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//将DecorView添加到ViewManager去
wm.addView(decor, l);
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
}
//a.getWindowManager()肯定返回的是WindowManager对象,追踪源码得到如下,而mWindow上面说到是Window对象并且只有PhoneWindow的唯一实现类
mWindowManager = mWindow.getWindowManager();
//然后去PhoneWindow寻找getWindowManager(),发现其实调用了其父类的getWindowManager()方法,最后在追踪到Window的setWindowManager方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//这里最终的到了mWindowManager并强转成了WindowManagerImpl
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
//继续追踪WindowManagerImpl,去寻找在handleResumeActivity()中调用的addView()方法
mGlobal.addView(view, params, mDisplay, mParentWindow);
//继续追踪mGlobal
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
//继续追踪WindowManagerGlobal的addView方法
//省略部分源码
//创建了ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
//继续跟踪ViewRootImpl的setView()方法,寻找关键的方法
requestLayout();
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//追踪checkThread()方法,表示当前线程是否在主线程
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
//追踪scheduleTraversals()方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
//省略部分源码
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//这里有个mTraversalRunnable其实是一个Runnable
}
}
//追踪mTraversalRunnable这个线程
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//继续追踪doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
//省略源码
performTraversals();
//省略源码
}
}
//继续追踪performTraversals()方法
private void performTraversals() {
//省略很多行源码
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//再省略很多行源码
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
//继续省略源码
performDraw();
}
}
其实分析到这里,从方法名就可以发现这里主要就是执行了绘制的三大步骤了,具体下节继续分析;
这里小结一下:
//①绘制入口
ActivityThread.handleResumeActivity()
->WindowManagerImpl.addView(dercorView,layoutParams)
->WindowManagerGlobal.addView()
//②绘制的类及方法
ViewRootImpl.setView(decorView, layoutParams, panelParentView)
->ViewRootImpl.requestLayout()
->scheduleTraversals()
->doTraversal()
->performTraversals()//这里执行真正的绘制流程三大步骤
2、绘制三大步骤
onMeasure(测量):确定DecorView的MeasureSpec
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//这里调用的View的measure方法,最终调用了onMeasure()方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
//继续跟踪发现执行了以下的方法调用
onMeasure(widthMeasureSpec, heightMeasureSpec)
->setMeasuredDimension(measuredWidth, measuredHeight)
->setMeasuredDimensionRaw(measuredWidth, measuredHeight)
//最终在这里确定控件的了宽高
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
//那么宽高是如何确定的,回到setMeasuredDimension()方法,其实他并不是直接传的measuredWidth和measuredHeight,而是通过一个叫getDefaultSize(getSuggestedMinimumWidth()(或getSuggestedMinimumHeight()), widthMeasureSpec(或heightMeasureSpec),getSuggestedMinimumXXX()方法返回的是默认的最小宽度(高度)的最大值和背景的最小宽度(高度)
//这里有一个MeasureSpec对象,其实是封装了大小尺寸和模式
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//MeasureSopec类部分源码
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;//mode移位数
//以下定义了三种模式,会根据不同的模式确定控件的大小,还有一个向左移位30位的操作,MeasureSpec其实是mode和一个32位的int值的封装,而要区分三种状态至少要两位所以mode就是前两位的值,而后30位表示宽高的值
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
* 未指定大小,这种情况很少见
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
* 精确模式:父容器没有施加任何约束在子控件,宽高就是传入的固定宽高(match_parent或30dp等)
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
* 最大模式:父容器已经约束了子控件最大只能达到自己的大小,那么此时控件就是自适应的大小(wrap_content)
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
}
//那么执行测量前,控件宽高的MeasureSpec对象来自哪里,回溯源码在performTraversals()方法:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
/**
* @param windowSize 窗口的宽或高
* @param rootDimension 布局的传入的宽高参数
* 根据不同的参数打包封装不同的MeasureSpec对象
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
小结一下,DecorView的MeasureSpec由窗口大小和自身的LayoutParams决定:
- LayoutParams.MATCH_PARENT:精确模式,窗口大小,
- LayoutParams.MATCH_PARENT:最大模式,最大为窗口大小,
- 固定大小(30dp/px):精确模式,大小为30dp/px;
onMeasure(测量):确定View的MeasureSpec
前面介绍到,DecorView上的基础容器和父容器都是FrameLayout,继续去看FrameLayout的**onMeasure()**方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();//这里获取子view的数量
//循环每个子View并调用measureChildWithMargins()
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
}
//继续跟踪measureChildWithMargins()
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子view的参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//设置子view宽的MeasureSpec对象
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//设置子view宽的MeasureSpec对象
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//开始子view的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* 跟踪getChildMeasureSpec()方法
*
* @param spec 父容器的MeasureSpec
* @param padding 子view设置的各种边距
* @param childDimension 子view的宽/高参数
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);//获取父容器的模式
int specSize = MeasureSpec.getSize(spec);//获取父容器的宽/高参数
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//先判断父容器的模式,因为父容器的模式会影响子view的大小测量
switch (specMode) {
// 父容器为精确模式
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子view是固定大小,大小就为固定大小,模式为精确
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//大小为父容器大小减去padding,模式为精确
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 最大不能超过父容器剩余的大小,这是view自己决定的,暂时就给父容器剩余的大小
// 模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
当一个容器测量时,会先遍历进行子view的测量,先获取子view的MeasureSpec然后调用子view的onMeasure()方法;
View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams决定,整理如下图:
当子view测量完成后,父容器要确定自己的大小:
setMeasuredDimension(int measuredWidth, int measuredHeight) //可以看出是根据子控件大大小来确定自身大小
下面通过一张图来说明父容器最终的大小,也就是需要找到所有子view中最右边的和最下边的子view的宽高
小结一下:
viewgroup的测量:
measure -> onMeasure(测量子view的宽高) -> setMeasuredDimension(根据子view确定自己的宽高) -> setMeasuredDimensionRaw(保存自身的宽高)
view的测量:
child.measure() -> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自身的宽高)
onLayout 确定布局位置
在ViewRootImpl中找到布局的performLayout()方法追踪源码:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//省略源码
//将DecorView赋值给host
final View host = mView;
host.layout(
0,
0,
host.getMeasuredWidth(), //获取测量得到的宽
host.getMeasuredHeight()); //获取测量得到的高
//省略源码
}
//继续追踪host.layout()方法
public void layout(int l, int t, int r, int b) {
//省略源码
//这里确定了控件位置setOpticalFrame()最终还是调用了setFrame()
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
//追踪setFrame(),其实这里就明确的给view分配了大小的将要绘制的位置
protected boolean setFrame(int left, int top, int right, int bottom) {
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
//在layout()方法中有一个onLayout()方法,追踪发现这是一个空方法,如果我们是一个普通view,那么不需要实现和处理,如果是一个容器,那么需要重写这个方法,确定子view的位置
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
FrameLayout是一个容器,去看一下他的onLayout()方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
//确定子view的位置
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//这里又调用了子view的layout(),实现一个递归操作
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
小结一下:
view 调用layout()确定其位置;
viewGroup 调用onLayout()确定子view位置,子view在调用layout()
View的绘制
在ViewRootImpl类找到performDraw()方法,依次寻找关键方法:
performDraw()
->draw()
->drawSoftware()
->mView.draw(canvas)这里调用了View的draw()
//追踪进入draw()
public void draw(Canvas canvas) {
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background 绘制背景
* 2. If necessary, save the canvas' layers to prepare for fading 如果需要,对绘制图层的保存(不是必须)
* 3. Draw view's content 绘制自身内容
* 4. Draw children 绘制子View
* 5. If necessary, draw the fading edges and restore layers 如果需要,恢复图层(不是必须)
* 6. Draw decorations (scrollbars for instance) 绘制装饰
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
//①、绘制背景
drawBackground(canvas);
}
if (!verticalEdges && !horizontalEdges) {
//②、绘制自身内容
if (!dirtyOpaque) onDraw(canvas);
//③、绘制子View
dispatchDraw(canvas);
//绘制前景等装饰
onDrawForeground(canvas);
}
view的draw(canvas)和dispatchDraw(canvas)都是空方法,所以自定义View需要实现draw(),如果是自定义容器就还需要实现和dispatchDraw()
小结一下:
View的绘制分一下四个步骤:
1、绘制背景drawBackground(canvas)
2、绘制自己onDraw(canvas)
3、绘制子View dispatchDraw(canvas)(当是容器时)
4、绘制前景、滚动条等装饰onDrawForekground(canvas)
以上就是从Window到最终的绘制的流程,如果需要自定义View,那么需要重写以下方法:
onMeasure() -> onLayout(是容器时必须重写,只是单纯的view可不重写) -> onDraw(当是容器时可不用重写)
后记:工作之余,偶尔深入,如有披露,望指正