热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

view的绘制流程复习

androidview绘制流程前言今天复习了view的绘制流程,看了几篇博客,百度搜索的前几篇写的大志差不多,沿着源码比,有点读不下去,然后又搜到了这篇全面升级Andro

android view 绘制流程


前言

今天复习了view的绘制流程,看了几篇博客,百度搜索的前几篇写的大志差不多,沿着源码比,有点读不下去,然后又搜到了这篇全面升级Android面试之View的绘制流程,看到这篇基本就把这个流程弄明白了。但是只是停留在基本明白,如果回过头来再想想,感觉还是很难组织起来,这就需要做个笔记和实践来加深印象了,所以写博客自己屡一下思路。那么我就通过自己的回忆写一写简单的流程。

读完一遍脑中的流程
  1. 首先映入脑海的应该是那张经典结构图,在从外到里结构是Window、Activity、DecorView、Actionbar、ContentView,ViewGroup、View。
  2. 然后是在哪启动绘制的呢?RootViewImpl里的performTraversals方法(),方法中调用view.measure(),layout(),draw()方法。
  3. measure方法中调用的是onMeasure方法。measure是不能重载的,onMeasure可以重载。onMeasure方法里调用setMeasuredDimension(),这个setMeasuredDimension可以自己定义尺寸,但是不推荐这样做,可能会影响子view的measure以及onlayout等。
  4. 上边是view里调用measure,ViewGroup调用measureChildren方法来测量它的子view。调用过程是measureChildren调用measureChild,measureChild里边先调用getChildMeasureSpec,然后获取child的宽高,再调用child的measure。将宽高传给child的measure,child的measure再调用onMeasure。这里边重点是getChildMeasureSpec的判断。
  5. 说起判断就要说下MeasureSpec,它一共32位,前2位(高2位)是表示Mode,后30位(低30位)表示size。getChildMeasureSpec方法里的判断主要就是判断父view的MeasureSpec的Mode。
  6. 那我们就主要看下getChildMreasureSpec()方法,代码如下

getChildMreasureSpec方法源码

 
 
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
		//首先将父view的Measure分解Mode和Size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
		//定义size取0和specSize-padding的最大值
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
		//判断父view的Mode为哪种类型
        switch (specMode) {
        //假如父view的Mode类型为EXACYLY(说明父view的大小是确切的数值,不管你子view多大不影响我)
        case MeasureSpec.EXACTLY:
			//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
		    //Mode为EXACTLY
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
				//如果child的params里width/height的值是MATCH_PARENT,则返回的size大小就是0和specSize-padding的最大值
				//怎么理解上边这句话呢?因为父view是EXACTLY大小不变的,所以specSize是个固定值,然后子view是
				//MATCH_PARENT,则给你子view的实际尺寸就是我父view的尺寸减去你设置的padding值。
				//MODE为EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
				//如果child的params里width/height的值是WRAP_CONTENT,则返回的size大小就是0和specSize-padding的最大值
				//Mode为AT_MOST,这种情况就是子view自己宽高不确定,所以父view说,你别超过specSize-padding的尺寸就行了。
				//我们注意到size是Math.max(0, specSize - padding),这里就有一个问题,你的子view会填充父容器。
            } 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 has imposed a maximum size on us
		//假如父view的Mode类型为AT_MOST,说明父view的大小现在是不确定的,最大值为specSize
        case MeasureSpec.AT_MOST:
			//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
		    //Mode为EXACTLY
            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
		//就是父view的size未被指定,子view随便吧,
        case MeasureSpec.UNSPECIFIED:
			//如果子view给出了确切的宽高,那子view的mode保证是EXACTLY的。
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
				//子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,
				//没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0
            } 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);
    }

因为调用getChildMeasureSpec方法是在measureChild方法里,代用代码如下:

    /**
     * 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. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        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);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

我们看出getChildMeasureSpec方法参数spec就是child父view的MeasureSpec,padding就是child的左右或者上下padding,childDimension就是child.getLayoutParams(),LayoutParams返回的宽高只会有三种值,一个确定的值,MATCH_PARENT或WRAP_CONTENT。

知道这些就开始分析getChildMeasureSpec这个方法,根据自己的理解已经把注释写在源码里了,

其实概况起来就是,看child的LayoutParams是三种中的哪种,假如是确定的值,那么child的MeasureSpec就是确定的值+EXACTLY,如果child是MATCH_PARENT或WRAP_CONTENT,则需要看父view的MODE,如果父view的MODE是AT_MOST,说明子view也都是AT_MOST,size就是父view要求的最大size,如果父view的MODE是UNDEFINED,由于这种不确定性,子view也无法确定宽高,只能设为0;

接下来就是调用child的measure方法,然后measure调用onMeasure方法。基本测量就结束了。

关于layout和draw的理解,layout是不能被重载的,一般都是重写onLayout和onDraw方法,自定义ViewGroup一般重写onLayout方法,而view是没有子view的所以不需要重写onLayout方法,onDraw方法一般自定义ViewGroup和自定义View都会重写,ViewGroup一般重写onDraw来画子view间的分割线,View重写onDraw除了分隔线还会绘制其他。

上边的流程我是自己脑海想的,然后用as辅助查看了一下源码。写出来的。


思考

经过自己脑海中屡了一遍,然后再回过头来看网上文章做的分析。就弥补了许多残留的疑问。 
总体思路还需要再揣摩。 
好办法就是分析系统的自定义控件如Button,LinearLayout等控件,看系统是怎么写的。这样能更加体会view绘制流程在自定义控件里的应用。加油吧。



参考

Android View的绘制流程 
Android应用层View绘制流程与源码分析 
全面升级Android面试之View的绘制流程 
Android View面试难点解析


推荐阅读
  • 解决vCenter vSphere HA初始化失败的问题
    本文探讨了在集群中遇到的所有vSphere HA主机状态显示‘无法正确安装或配置vSphere HA代理’错误的情况,并详细介绍了排查与解决步骤,包括检查HA初始化错误及安装HA代理的常见故障排除方法。 ... [详细]
  • 如何检查电脑显卡的显存类型
    本文旨在指导用户如何轻松识别自己的电脑或新购显卡的显存类型,无论是GDDR3还是更为先进的GDDR5。通过简单的步骤,即使是计算机新手也能快速掌握方法。 ... [详细]
  • 微信小程序中实现位置获取的全面指南
    本文详细介绍了如何在微信小程序中实现地理位置的获取,包括通过微信官方API和腾讯地图API两种方式。文中不仅涵盖了必要的准备工作,如申请开发者密钥、下载并配置SDK等,还提供了处理用户授权及位置信息获取的具体代码示例。 ... [详细]
  • LCUI 2.1.0 版本现已推出,这是一个用 C 语言编写的图形用户界面开发库,适合创建轻量级的桌面应用程序。此次更新包括多项修复和功能增强,并正式宣布将启动 Android 支持的开发计划。 ... [详细]
  • 本文探讨了如何通过WebBrowser控件在用户点击输入框时自动显示图片验证码。该过程可能涉及JavaScript事件的触发与响应。 ... [详细]
  • 本文详细介绍了如何在现有的Android Studio项目中集成JNI(Java Native Interface),包括下载必要的NDK和构建工具,配置CMakeLists.txt文件,以及编写和调用JNI函数的具体步骤。 ... [详细]
  • 本文详细介绍了如何在Android应用中使用GridView组件以网格形式展示数据(如文本和图像)。通过行列布局,实现类似矩阵的数据展示效果。 ... [详细]
  • 本文针对Android 6.0平台的输入子系统进行了详细探讨,特别关注了智能电视开发中常见的KeyEvent事件处理机制。通过分析InputManagerService的启动过程、应用程序如何注册键盘消息监听、InputReader读取键盘消息的方式、InputDispatcher分发键盘消息的过程以及Java层的键盘消息分发机制,为开发者提供了一个全面的视角。 ... [详细]
  • 本文介绍了在Android项目中实现时间轴效果的方法,通过自定义ListView的Item布局和适配器逻辑,实现了动态显示和隐藏时间标签的功能。文中详细描述了布局文件、适配器代码以及时间格式化工具类的具体实现。 ... [详细]
  • 深入分析十大PHP开发框架
    随着PHP技术的发展,各类开发框架层出不穷,成为了开发者们热议的话题。本文将详细介绍并对比十款主流的PHP开发框架,旨在帮助开发者根据自身需求选择最合适的工具。 ... [详细]
  • 本文将指导如何在JFinal框架中快速搭建一个简易的登录系统,包括环境配置、数据库设计、项目结构规划及核心代码实现等环节。 ... [详细]
  • Android中实现复合旋转动画效果
    本文将探讨如何在Android应用中实现动态且吸引人的旋转动画。通过结合多种动画类型,如透明度变化、旋转、缩放和位移,可以创造出更为复杂的视觉效果。我们将从XML布局和Java代码两个方面进行详细介绍。 ... [详细]
  • Android商城应用开发指南(第二部分):创建启动欢迎页
    大多数商城应用程序在启动时会显示一个欢迎页面,以提升用户体验。本文将指导您如何实现一个基本的欢迎页,该页面会在用户打开应用后短暂展示,随后自动跳转至主界面。 ... [详细]
  • 智慧城市建设现状及未来趋势
    随着新基建政策的推进及‘十四五’规划的实施,我国正步入以5G、人工智能等先进技术引领的智慧经济新时代。规划强调加速数字化转型,促进数字政府建设,新基建政策亦倡导城市基础设施的全面数字化。本文探讨了智慧城市的发展背景、全球及国内进展、市场规模、架构设计,以及百度、阿里、腾讯、华为等领军企业在该领域的布局策略。 ... [详细]
  • VMware Horizon View 5.0桌面虚拟化部署实践与心得
    在近期的研究中,我花费了大约两天时间成功部署了桌面虚拟化环境,并在此过程中积累了一些宝贵的经验。本文将分享这些经验和部署细节,希望能对同样关注桌面虚拟化的同行有所帮助。 ... [详细]
author-avatar
琴瑟_0203
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有