view是如何被加载到界面上的?

创建一个普通的activity

//1.MainActivity.javapublic class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //添加布局入口        setContentView(R.layout.activity_main);    }}//2.Activity.javapublic void setContentView(@LayoutRes int layoutResID) {        getWindow().setContentView(layoutResID);        initWindowDecorActionBar();    }//getWindow()获取的window对象是mWindow,Window类是个抽象类,拥有唯一的实例android.view.PhoneWindow/* 

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window. */ public abstract class Window { ... }

//3.PhoneWindow.java//因此到PhoneWindow找到setContentView(int layoutResID)@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();        } 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);        }        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }    }//4.  installDecor();    //5.  mLayoutInflater.inflate(layoutResID, mContentParent);//解析传进来的layoutResID和mContentParent, 实际上就是把传进来的mContentParent加载到mContentParent上,mContentParent是一个frameLayout
//4.  installDecor();//4.1创建DevorView mDecor = generateDecor(); //4.2创建mContentParent mContentParent = generateLayout(mDecor); 
//4.2创建mContentParent//解析基础控件layoutResourceView in = mLayoutInflater.inflate(layoutResource, null);//DecorView添加基础控件indecor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));mContentRoot = (ViewGroup) in;//添加ID_ANDROID_CONTENT 并且直接返回contentParentViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// layoutResource是一个基础控件ViewGroup//xref: /frameworks/base/core/res/res/layout/screen_title.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"23    android:orientation="vertical"24    android:fitsSystemWindows="true">25    <!-- Popout bar for action modes -->26    <ViewStub android:id="@+id/action_mode_bar_stub"27              android:inflatedId="@+id/action_mode_bar"28              android:layout="@layout/action_mode_bar"29              android:layout_width="match_parent"30              android:layout_height="wrap_content"31              android:theme="?attr/actionBarTheme" />32    <FrameLayout33        android:layout_width="match_parent" 34        android:layout_height="?android:attr/windowTitleSize"35        style="?android:attr/windowTitleBackgroundStyle">36        <TextView android:id="@android:id/title" 37            style="?android:attr/windowTitleStyle"38            android:background="@null"39            android:fadingEdge="horizontal"40            android:gravity="center_vertical"41            android:layout_width="match_parent"42            android:layout_height="match_parent" />43    </FrameLayout>44    <FrameLayout android:id="@android:id/content"45        android:layout_width="match_parent" 46        android:layout_height="0dip"47        android:layout_weight="1"48        android:foregroundGravity="fill_horizontal|top"49        android:foreground="?android:attr/windowContentOverlay" />50</LinearLayout>

View就这样被加载到界面上了

总结:

* 创建顶层布局容器DecorView* 在顶层布局中添加基础布局ViewGroup* 将contentView添加到基础布局容器的FrameLayout中
view是如何绘制的?
    1. ActivityThread.class
public void handleMessage(Message msg) {            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));            switch (msg.what) {                case LAUNCH_ACTIVITY: {                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;                    r.packageInfo = getPackageInfoNoCheck(                            r.activityInfo.applicationInfo, r.compatInfo);                    handleLaunchActivity(r, null);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                } break;                ...}
  • 2.handleLaunchActivity
  • 3.handleResumeActivity
  • 4.wm.addView(decor, l);
// 4.wm.addView(decor, l); ViewManager wm = a.getWindowManager(); -> mWindowManager = mWindow.getWindowManager(); //mWindow唯一的实例是 PhoneWindow,因此 -> mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); //因此wm是WindowManagerImpl的实例,在addView中调用了mGlobal.addView() -> mGlobal.addView(view, params, mDisplay, mParentWindow); //ViewRootImpl关联view, params, panelParentView,root是ViewRootImpl实例化对象 -> root.setView(view, wparams, panelParentView); -> requestLayout() -> checkThread(); -> scheduleTraversals(); -> TraversalRunnable{} -> doTraversal(); -> performTraversals(); -> performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); //1890 测量 -> performLayout(lp, desiredWindowWidth, desiredWindowHeight);  //1931 布局 -> performDraw(); // 2067 绘制

view的绘制流程就是以上这些步骤,现在具体来看下performMeasure(),performLayout(),performDraw()这三步关键的步骤。

  • 测量:performMeasure()
    • 先引入一个概念MeasureSpec,view的测量规格,是一个32位的二进制整数类型。view的测量按照一定的规则,以下是MeasureSpec的一些规则:
//MeasureSpec.java  public static int makeMeasureSpec(int size, int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }UNSPECIFIED: 00 0000 0000 0000 0000 0000 0000 0000 00-> 父容器不对view做任何限制,系统内部使用  EXACTLY: 01 0000 0000 0000 0000 0000 0000 0000 00-> 父容器检测view的大小,view的大小就是SpecSize ,对应子控件的 LayoutParams.match_parent / 固定大小  AT_MOST: 10 0000 0000 0000 0000 0000 0000 0000 00-> 父容器指定一个可用大小,view不能超过这个值, LayoutParams.wrap_contentMODE_MASK = 11 0000 0000 0000 0000 0000 0000 0000 00 ~MODE_MASK = 00 1111 1111 1111 1111 1111 1111 1111 11 size &  ~MODE_MASK = 00 后三十位 model & MODE_MASK = 前两位 0000 0000 0000 0000 0000 0000 0000 00(size & ~MODE_MASK) | (mode & MODE_MASK);  == model(前两位) size(后三十位)
  • 继续回到 ViewRootImpl 的1890行 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);-> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);-> onMeasure(widthMeasureSpec, heightMeasureSpec);-> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));                //保存自身的宽和高-> setMeasuredDimensionRaw(measuredWidth, measuredHeight);//此处即测量结束,赋值给宽高,并设置标记flagsprivate void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }//以上来看view的测量就结束了。但是view是如何测量的,就需要了解传进来的参数childWidthMeasureSpec,childHeightMeasureSpec是怎么得来的
  • int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //1854 ViewRootImpl.java
//mWidth : 窗口容器的宽度//lp.width: 顶层容器的宽度getRootMeasureSpec(mWidth, lp.width)// 因此 DecorView的MeasureSpec由窗口大小和自身LayoutParams决定,遵守以下规则://  LayoutParams.MATCH_PARENT: 精确模式,窗口大小//  LayoutParams.WRAP_CONTENT: 最大模式,最大为窗口大小//  固定大小:精确模式,大小为LayoutParams的大小//实际上是调用DecorView的measure()方法 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //在view的measure()方法内部还调用了 onMeasure(widthMeasureSpec, heightMeasureSpec); //DecorView的父类是FrameLayout,在FrameLayout的onMeasure中调用了 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//因为是容器viewGroup,所以测量了自身需要在测量child控件

总结: viewGroup : measure -> onMeasure(子控件的宽高) -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
view : measure -> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
自定义view不重写onMeasure方法,使用match_parent 和 wrap_content的效果是一样的

  • 布局performLayout() ,回到performLayout(lp, desiredWindowWidth, desiredWindowHeight); //1931 ViewRootImpl.java
->   host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());-> setFrame(l, t, r, b);//调用view.layout确定自身的位置,即确定mLeft,mTop,mRight,mBottom的值

总结:
viewGroup: layout(确定自己的位置,4个点的位置)->onLayout(进行子view的布局)
view: layout(确定自己的位置,4个点的位置)

  • performDraw绘制 //2067 ViewRootImpl.java
-> performDraw()-> draw()-> drawSoftware()-> mView.draw(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         *      5. If necessary, draw the fading edges and restore layers         *      6. Draw decorations (scrollbars for instance)         */

总结:
viewGroup:

  1. 绘制背景drawBackground()
  2. 绘制自己onDraw()
  3. 绘制子view dispatchDraw()
  4. 绘制前景,滚动条等装饰onDrawForeground()

view:

  1. 绘制背景drawBackground()
  2. 绘制自己onDraw()
  3. 绘制前景,滚动条等装饰onDrawForeground()

更多相关文章

  1. Android camera预览参数以及实际图像大小设置
  2. Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Dr
  3. Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Dr
  4. android apk大小限制,进程大小限制。
  5. android视图组件容器组件与布局管理器LinearLayout
  6. Android - Android Studio修改字体(font)大小(size)
  7. Android: android自适应屏幕方向和大小
  8. Android搜索TextView显示关键字标红(忽略大小写)
  9. Android 修改Camera默认preview size预览大小为4:3

随机推荐

  1. MSSQL分页存储过程完整示例(支持多表分页
  2. SQL Server 2012 FileTable 新特性详解
  3. SQL查询服务器硬盘剩余空间
  4. 高并发系统数据幂等的解决方案
  5. Activiti-Explorer使用sql server数据库
  6. SQL Server数据表字段自定义自增数据格式
  7. SQLServer批量更新两个关联表数据的方法
  8. 使用SQL批量替换语句修改、增加、删除字
  9. SQL Server 存储过程遇到“表 &#39;&#39;
  10. 浅谈数据库优化方案