Android View measure (一) 流程分析

本篇模拟三个角色:Android 架构师-小福、Android  控件开发工程师-小黑、 Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程。

小福负责分享

  • measure的本质 - ok
  • measure代码流程 - 分析FrameLayout.onMeasure
  • onMeasure方法与MeasureSpec - ok
  • 提出问题

Android 架构师-小福的分享

一、Measure本质

小福:我今天分享是的measure架构设计相关的,先问一个问题,measure的本质是什么?

小黑:这个我知道,是Android系统创建UI界面的measure、layout、draw三步骤的第一步,主要用于测量视图大小,更详细点说是把“相对值”(WRAP_CONTENT, FILL_PARENT, MATCH_PARENT)转换为具体指的过程。

小福:小黑说的对,再问一个问题,视图大小指的是什么?

小白:视图大小是在视图在屏幕上显示的大小,也就是开发的时候通过layout_width与layout_heigh设置的?

小福:小白说的只是其中一个作为开发人员的角度。Android系统设计中Canvas是无穷大的,假如一个屏幕的大小是320 * 480 ,但是layout_width="480px" , layout_heigh="800px",很明显视图的宽高大于实际屏幕大小。

问题来了,视图的大小到底是屏幕上显示的大小,还是视图的实际大小(即使是超过了屏幕大小)?

小黑:具体视图显示大小是由开发人员设置,之后由我控件开发工程师在onMeasure中决定,如果向小福说的尺寸,即使超过屏幕我可以决定是width= 320, heigh = 480 还是widt= 480, heigh = 800 ,决定权在我这里,一会在我分享的时候会写一个Demo来演示。

(视图根据绘制大小不同分类:内容型视图、图形型视图)

小白:Canvas是什么?

小福:这个在之后分享draw过程的时候在详细讨论,可以笼统的理解为画画时使用的画布。

二、Measure代码流程

小福:先从源码看下measure执行流程,看看这些过程中都做了些什么。以下都是android.view.ViewRootImpl.java类中的源码

public final class ViewRootImpl extends Handler implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {

    // 1 所有子视图的requestLayout方法,最总都会触发根视图此方法
    public void requestLayout() {
        checkThread();
        // 需要重新布局
        mLayoutRequested = true;

        scheduleTraversals();
    }

    // 调度遍历
    public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;

            .....

            // 当前类继承自Handler,发出一个空消息,目的是加入Message队列
            sendEmptyMessage(DO_TRAVERSAL);
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {

        ...

        case DO_TRAVERSAL:
            ...
            // 处理DO_TRAVERSAL消息
            performTraversals();
            ...
            break;

			.....
        }
    }

    // 执行遍历
    private void performTraversals() {

        final View host = mView;

        int desiredWindowWidth;
        int desiredWindowHeight;
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        ......

        if (mLayoutRequested && !mStopped) {
            ......
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            ......
            // host是一个View对象
            host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            ......
        }

        ......
    }
}

注意:以上代码中getRootMeasureSpec方法可以或者跟视图中childWidthMeasureSpec与childHeightMeasureSpec,感兴趣的可以自己看下desiredWindowWidth变量的赋值其获取的是窗口的宽高。

上面的代码一共分为5个步骤

1 requestLayout() -> 2 scheduleTraversals() -> 3 handleMessage() -> 4 performTraversals() -> 5 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

  1. 界面中所有视图执行requestLayout,重新布局请求会逐步向上传递,最终传执行当前ViewRootImpl的requestLaout()
  2. 步骤1中会执行scheduleTraversals,其中发送一个空的消息,把重新布局的请求通过Handler发送到主线程的MeassQueue等待执行(具体可以学习Handler)。
  3. 因为当前ViewRootImpl是继承自Handler,所以直接查找覆写的handleMessage方法,因为传递的消息是DO_TRAVERSAL,分支调用performTraversals
  4. performTraversals方法中调用host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  5. 因为host是View对象所以接下来需要查看View.measure方法,才能进一步分析measure流程

接着上面的measure流程的第五步走下去,以下是android.view.View.java文件中的源码:

public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
        AccessibilityEventSource {

    // 方法是final类型,说明不能被覆写或者重载
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        // 如果有重新请求标志,或者宽高发生改变
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            ......

            // 真正执行测量视图大小操作
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            ......

            // 添加重新请求子视图布局标志
            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        ......
    }

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree.
     */
    public void requestLayout() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
        }

        // 添加重新请求布局标志
        mPrivateFlags |= FORCE_LAYOUT;
        mPrivateFlags |= INVALIDATED;

        if (mParent != null) {
            if (mLayoutParams != null) {
                mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
            }
            if (!mParent.isLayoutRequested()) {
                mParent.requestLayout();
            }
        }
    }	

}

上面代码的measure流程可以分为4个步骤

1 measure与requestLayout -> 2 onMeasure

  1. measure方法是final类型,说明此方法不能被修改。其中判断条件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 进行赋值的。只要测量的宽高等发生改变都会触发第二步。
  2. 执行当前的onMeasure方法,通过Hierarchy Viewer等工具可以获知根视图是FrameLayout类型(这里就不从源码验证了)

紧接着看下android.widget.FrameLayout类的onMeasure总都做了什么?

三、onMeasure方法与MeasureSpec

上面显示的代码中参数int widthMeasureSpec, int heightMeasureSpec都是通过MeasureSpec类进行统一处理。

MeasureSpec是一个android.view.View的内部类,封装了从父类传送到子类的布局要求信息。每个MeasureSpec对象描述了空间的高度或宽度。 MeasureSpec由size和mode组成。

1. MeasureSpec的方法介绍:

类名.方法名 解释
MeasureSpec.getMode(int measureSpec) 根据提供的测量值(格式)提取模式(上述三个模式之一)
MeasureSpec.getSize(int measureSpec) 根据提供的测量值(格式)提取大小值
MeasureSpec.makeMeasureSpec(int size,int mode) 根据提供的大小值和模式创建一个测量值(格式)

2. MeasureSpec有三种mode,分别说明并描述模式与layout参数值的对应关系

模式 翻译 模式与Layout参数对应关系  模式描述
UNSPECIFIED 无限制   parent view不约束child view的大小
AT_MOST 最多的 wrap_content child view可以在parent view范围内取值
EXACTLY 准确的 fill_parent(例如50dip) parent view为child view指定固定大小

3. MeasureSpec通过位运行从int类型的值中获取mode与sieze

更详细的分析请查看《android中onMeasure初看,深入理解布局之一!

四、提出问题

为什么布局需要父视图与子视图共同决定? 为什么不直接设置宽和高?

如果不这样就像HTML那样指定固定大小,这样会造成一个过大把另外一个挤出去。因为HTML是具有整个页面的控制权。

而Android是拆分开的,这样做保证了最末端的(界面开发)影响其他层级的布局,需要按照(控件开发)的规则来。

view.setWidth, view.setHeight ? 不是

需要通过measureChilde(view, width, height), 或者childView.measure();

还有哪些?

View.getWidth(), View.getHight()

View.getMeasureWidth(), View.getMeasureHiegh()

一个未添加到视图中的? 但是有时getMesureHeight 依然是返回0。为什么?

时间: 2024-12-21 23:08:57

Android View measure (一) 流程分析的相关文章

Android View measure流程详解

Android View measure流程详解 Android中View绘制的流程包括:measure(测量)->layout(布局)->draw(绘制). 因为Android中每个View都占据了一块矩形的空间,当我们要在屏幕上显示这个矩形的View的时候 首先我们需要知道这个矩形的大小(宽和高)这就对应了View的measure流程. 有了View的宽和高,我们还需要知道View左上角的起点在哪里,右下角的终点在哪里,这就对应了View的layout流程. 当矩形的区域在屏幕上确定之后,

Android View的绘制流程三部曲 —— Measure

在刚开始学习Java的时候,我看的是Mars老师的视频.Mars老师说过的一句话让我印象很深刻:要有一颗面向对象的心. 如果我们用面向对象的思维方式来思考,就会觉的View的绘制机制是很合理,很科学的.我们要在一张纸上画一幅画,首先要测量一下这幅画有多大吧,然后确定在这张纸的哪个地方画会显得比较美观,最后才是用画笔工具将画绘制在纸上. 在Android中也是一样的.View的绘制流程主要是指measure,layout,draw这三步,即测量,布局,绘制.首先是要测量View的宽高,然后布局确定

Android View measure (二) 自定义UI控件measure相关

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 measure代码流程 onMeasure方法与MeasureSpec 提出问题 小黑负责分享: 布局控件开发中覆写Measure例子 - ok 从遇到的一个异常说起 什么时候需要覆写onMeaure? - ok view.getWidth与view.getMeasureWidth区别

Android View 的测量流程详解

概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最后讲到了 ViewRootImpl 开始绘制的逻辑.本文接着上篇,继续往下讲,开始分析 view 的绘制流程. 上文说到了调用 performTraversals 进行绘制,由于 performTraversals 方法比较长,看一个简化版: // ViewRootImpl 类 private vo

Android View measure (五) 支持margin属性,从一个异常说起

先来看下代码 一.查看夏目 1. 自定义控件 public class CustomViewGroup extends ViewGroup { ...... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 遍历所有子视图,进行measure操作 for (int i =

Cocos2d-x3.3RC0的Android编译Activity启动流程分析

本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXV4aWt1b18x/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > watermark/2/text/aHR0cDov

Android View measure (三) 常用方法

ViewGroup.measureChildren() ViewGroup.measureChild() ViewGroup.measureChildWithMargins() /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins

Android 4.4KitKat AudioRecord 流程分析

Android是架构分为三层: 底层      Linux Kernel 中间层  主要由C++实现 (Android 60%源码都是C++实现) 应用层  主要由JAVA开发的应用程序 应用程序执行过程大致如下: JAVA应用程序产生操作(播放音乐或停止),然后通过JNI调用进入中间层执行C++代码,中间层处理后可能需要硬件产生动作的,会继续将操作传到Linux Kernel,Kernel ,不需要硬件产生操作的可能在中间层做一些处理就直接返回.需要硬件产生操作的动作则需通过Kernel调用相

Android View measure (三) 经常用法

ViewGroup.measureChildren() ViewGroup.measureChild() ViewGroup.measureChildWithMargins() /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins