首先需要了解我们的view是如何被添加到窗口中
点击activity中setContentView会进入系统源码中
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里的geWindow()返回的事Window对象,而Window是一个抽象类,它的唯一实现类就是PhoneWindow,所以直接进入PhoneWindow的setContentView(id)方法
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor(); //1
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);//2
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
方法中一开始mContentParent是为空的,这个mContentParent是什么呢?后面会讲到,首先进入1处注释的方法installDecor(),点击进入
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//2
...
}
}
从源码中看到mDecor初始化视为空的,所以会进入1处的方法,这个方法会为我们创建一个DecorView,而这个DecorView是继承自FrameLayout,所以它是一个布局容器,其实它就是我们布局的最外层容器,是所有窗口的根view。所以1处方法的执行就是为我们创建了一个顶层布局容器。
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
...
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
再看第二处方法,首先会根据不同的style对feature进行设置,注意1处,定义了一个layoutResource,下面会根据这个feature对layoutResouce进行赋值。其实就是根据activity的theme样式去加载不同的布局样式,并把它赋值給layoutResource。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//...根据不同的style设置不同的feature ,此处省略
// Inflate the window decor.
int layoutResource;//1
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//根据不同的feature样式,加载不同的系统布局
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
...
} 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) {
...
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) {
...
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!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//2
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//3
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
在看第2处注释,点击进去继续跟踪。首先会把我们的系统布局解析成view,并把它作为基础布局容器添加到decorview之上
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);//1
if (mDecorCaptionView != null) {
...
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//2
}
...
}
回到上面generateLayout()方法,第3处,我们可以任意打开一个系统layout文件可以看到,都会有一个id为content的FrameLayout容器,在3处对它解析。并且系统布局文件中有一个viewStub控件,其实就是系统为我们添加的默认标题栏,解析完以后就把contentParent进行返回,所以上面提到的mContentParent就是它。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
再次回到我们的初始方法,总结一下,注释1主要做了两件事情
第一件:创建我们的顶层布局容器decorView,
第二件:根据activity不同的主题样式会解析不同的系统布局文件,并将它添加到decorview之上,作为基础布局容器。另外,它会解析布局容器中id为conetent的viewgroup容器,并将其返回,赋值给mContentParent。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor(); //1
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);//2
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
再看第二处,就比较容易理解了,就是把我们acticity的布局文件layoutResID解析到mContentParent之上。所以我们用户看到的界面其实就是decorView,那么我们的decorView对象是如何被添加到窗口之上的呢?
首先进入ActivityThread源码类,找到handleMessage()方法,该方法是所有activity调用都会触发的,在里面会调用handlerResumeActivity()方法。最总跟踪到windowManagerImpl ,找到addView()方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
会调用到mGlobal()的addView()方法,继续跟踪
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
}
最后调用root.setView(...)将三个参数进行关联,以上流程实现了decorView添加到窗口Window的过程,但是此时界面仍然不会显示出来,因为view的工作流程还没有执行完,还要经过measure(),layout(),draw()三步操作,才会把view绘制出来。参考下面一张图
其次需要我们了解view的绘制流程
点击进入root.setView(...)方法
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
}
}
}
点击进入requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//判断是否在主线程中
checkThread();
mLayoutRequested = true;
//1
scheduleTraversals();
}
}
点击1处方法,继续跟踪
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
发现会调用mTraversalRunnable方法,找到raversalRunnable的run方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//1
doTraversal();
}
}
点击1处继续跟踪
void doTraversal() {
if (mTraversalScheduled) {
...
//1
performTraversals();
...
}
}
进入1处方法,发现view的绘制3个方法都会在这里执行
void performTravelsals(){
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
在分析测量之前,我们首先需要了解MeasureSpec类,它决定view测量规格,封装了view的宽高,绘制一个view,首先需要确定他的测量规格。
这个测量规格分为两部分 即 view = mode + size ->MeasureSpec 表示32位的int值
mode:测量模式 取前2位 命名SpecMode
size:测量尺寸 取后30位 命名SpecSize
再看,我们的View类中提供了三个方法,分别进行打包和解包的操作
//对measureSpec进行打包操作
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//对measureSpec进行解包操作
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
另外,MeasureSpec中定义了3个静态常量
大家可能会有一个疑问,AT_MOST模式不是和EXACTLY模式一样了吗,都是给个最大值来限制View的范围?其实不一样,EXACTLY模式是一个固定的模式,也就是说它是没得商量的,你只能按照这个模式既有的规律来执行而AT_MOST模式是可以根据开发者自己的需求来定制的,我们写自定义View的时候所写的测量其实也就是在写这个模式的测量逻辑,他是根据子控件来灵活的测量尺寸的。
回到源码中继续分析:
进入performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法中,源码如下
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//1
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在注释1处,会调用mView的measure方法,其中mView就是我们的顶层view即decorView,继续进入measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//前面会做一些绘制的缓存处理,提高绘制效率
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//1
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
...
}
继续点击1处的方法进入
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
继续点击进入setMeasuredDimension()方法
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
继续点击进入setMeasuredDimensionRaw()方法
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
在这里对我们顶层view即decorView的宽高进行了赋值操作,说明我们的decorView的宽高是和measuredWidth,measuredHeight相关的。那么我们需要分析这两个值
回到我们的performMeasure(...)方法原始调用的地方,在上面找到了两个方法,进行了宽高的计算
void performTravelsals(){
...
//初始化了宽高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
进入getRootMeasureSpec()方法中,这里传入了两个参数,windowSize即窗口尺寸 ,rootDimension为lp.width或lp.height,这里的lp为我们decorView的布局属性对象。
注:所以decorView的MeasureSpec由窗口大小和自身的LayoutParams决定
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;
//固定大小,精确模式,大小由LayoutParams自身大小决定
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的测量过程,下面分析viewGroup和view的测量过程,进入FrameLayout源码类中,找到onMeasure(...)方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//1
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
...
}
方法接受了两个参数,分别表示父容器的测量规格。看到有个for循环遍历子view进行测量 ,点击1处进入方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//1
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//2
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//3
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
注释1处获取子view的布局属性对象, 2处根据父容器的测量规格和自身的布局属性, 计算自身的view测量规格,3处往后执行和decorView流程一致,最终会为我们的子view保存一个宽高。完成子view的测量
针对2处查看如何获取子view的测量规格
//从上面我们知道spec 是parent的MeasureSpec,padding是
//已被使用的大小,childDimension为child的大小
public static int getChildMeasureSpec(
int spec, int padding, int childDimension) {
//1、获取parent的specMode
int specMode = MeasureSpec.getMode(spec);
//2、获取parent的specSize
int specSize = MeasureSpec.getSize(spec);
//3、size=剩余的可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//4、通过switch语句判断parent的集中mode,分别处理
switch (specMode) {
// 5、parent为MeasureSpec.EXACTLY时
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//5.1、当childDimension大于0时,表示child的大小是
//明确指出的,如layout_width= "100dp";
// 此时child的大小= childDimension,
resultSize = childDimension;
//child的测量模式= MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//5.2、此时为LayoutParams.MATCH_PARENT
//也就是 android:layout_width="match_parent"
//因为parent的大小是明确的,child要匹配parent的大小
//那么我们就直接让child=parent的大小就好
resultSize = size;
//同样,child的测量模式= MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//5.3、此时为LayoutParams.WRAP_CONTENT
//也就是 android:layout_width="wrap_content"
// 这个模式需要特别对待,child说我要的大小刚好够放
//需要展示的内容就好,而此时我们并不知道child的内容
//需要多大的地方,暂时先把parent的size给他
resultSize = size;
//自然,child的mode就是MeasureSpec.AT_MOST的了
resultMode = MeasureSpec.AT_MOST;
}
break;
// 5、parent为AT_MOST,此时child最大不能超过parent
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//同样child大小明确时,
//大小直接时指定的childDimension
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// child要跟parent一样大,resultSize=可用大小
resultSize = size;
//因为parent是AT_MOST,child的大小也还是未定的,
//所以也是MeasureSpec.AT_MOST
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//又是特殊情况,先给child可用的大小
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 这种模式是很少用的,我们也看下吧
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 与前面同样的处理
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;
}
//通过传入resultSize和resultMode生成一个MeasureSpec.返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
总结一下:
注:子View的MeasureSpec由子view的LayoutParams和父容器的MeasureSpec确定,如下图:
回到FrameLayout类中onMeasure()方法中
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//1
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//2
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
注释2处会根据子view占据的最大宽高计算出父容器的宽高。继而完成viewGroup的绘制,view的绘制可以进入view的measure()方法继续跟踪
大致流程如下:
ViewGroup measure --> onMeasure(测量子控件的宽高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
注意:在进行子view进行测量时会调用如下方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
进入getDefaultSize()方法,发现view的测量过程中MeasureSpec.AT_MOST和MeasureSpec.EXACTLY下,view的大小都赋值为specSize 大小,即父容器的剩余大小,所以当我们自定义view的时候如果没有重写onMeasure方法,无论设置成wrap_content还是match_parent,效果是一样的。也就是说当我们是自定义viewGroup时需要确定子view的宽高,而我们自定义View的时候需要在onMeasure中重新确定自己的宽高
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;
}
进入performLayout(lp, mWidth, mHeight);方法中,其中第一个参数为我们的顶层view即decorView的布局属性,第二,三个参数为它的宽高。源码如下
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
//1
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//2
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
...
}
1处将我们的decorView赋值给host ,2处调用layout(...)方法,点击进入,会进入我们view类中
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//1
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);//2
...
}
...
}
调用1处setFrame(...)方法实际上就是为我们的view进行上下左右的赋值,确定其位置。2处调用onLayout()方法,点击进入
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
发现该方法是一个方法,针对于viewGroup来说,就需要在这个方法中确定子view的位置,比如FrameLayout,我们找到这个onLayout()方法,对于view来说不需要重写
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
继续进入layoutChildren(...)方法
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();
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;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
方法中使用循环,找到每个子view的位置。最后会调用child.layout()方法,进行递归操作,实现子view的摆放。
进入performDraw(...)方法中
private void performDraw() {
...
try {
boolean canUseAsync = draw(fullRedrawNeeded);
...
} finally {
...
}
...
}
}
找到draw()方法,点进去
private boolean draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
...
return useAsyncReport;
}
找到drawSoftware()方法,点进去
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);
...
return true;
}
找到mView.draw(canvas);其中mView就是我们的decorView,再次点进draw(...)方法
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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
* 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);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
可以看到decorView的绘制经过了6个步骤。
这里着重分析几个关键点
(1)绘制View的内容
protected void onDraw(Canvas canvas) {
}
发现是一个空方法,需要我们自定义view自己实现具体的绘制内容
(2)绘制子view
protected void dispatchDraw(Canvas canvas) {
}
发现也是一个空方法,对于自定义view来说,不需要重写改法,对于自定义viewGroup就需要在里面进行子view的绘制,进入它的dispatchDraw(...)方法
@Override
protected void dispatchDraw(Canvas canvas) {
...
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
...
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);//1
}
}
...
}
该方法对子view进行了遍历,并且调用了1处的drawChild(...)方法,继续跟踪
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
继续进入child.draw(...)方法,会发现又回到view的draw方法,执行6个步骤。实现view的递归绘制。
以上就完成了view的绘制流程。总结一下:
执行view的绘制三大步开始的地方是ViewRootImpl类中的performIntervals()方法,里面进行了performMeasure(),performLayout()和performDraw()。
view的measure:
view的layout:
view的draw: