首先需要了解我们的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: