文档章节

棒棒糖之——Android中视图的绘制流程

MrXI
 MrXI
发布于 2017/04/29 11:08
字数 1768
阅读 349
收藏 34
点赞 0
评论 1

我的稀土掘金博客同步发布更新: 棒棒糖之——Android中视图的绘制流程

一、前言

    Android中Activity是作为应用程序的载体存在的,它代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图,这个内容视图就是用户看到的界面,在Android中View存在的两种形式:一种是单一的View控件 ,另一种就是可以包含其他View的ViewGroup容器,前面的内容视图就是以ViewGroup的形式存在的,先看一个Android的UI管理系统的层级管理图:

    

    PhoneWindow是Android系统中最基本的窗口系统,每个Activity都会创建一个。PhoneWindow是Activity和View系统交互的接口。DecorView本质上是一个FrameLayout,是Activity中所有View的祖先。

二、绘制的整体流程

    当应用启动时会启动一个主Activity,Android系统会根据Activity的布局来对它进行绘制。绘制会从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而VewGroup还需要负责通知自己的子View进行绘制操作,视图绘制的过程可以分为三个步骤,分别是测量(Measure)、布局(layout)和绘制(Draw),以下我们看一下performTraversals()的关键代码,performTraversals()在ViewRootImpl类中,ViewRootImpl类在\sdk\sources\android-23\android\view包下面,是一个隐藏类,在as中看不到 直接启动连击两次Shift键搜索,代码:           

  

private void performTraversals() {
                    ...
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                   //执行测量流程
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
                    //执行布局流程
                    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
                    ...      
                    //执行绘制流程
                    performDraw();
            
    }

三、MeasureSpec

    MeasureSpec表示的是一个32位的整型值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规则大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明应该如何测量这个View,代码如下:

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    // 不指定测量模式
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    // 精确测量模式
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    //最大值测量模式
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //根据指定的大小和模式创建一个MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

     
 
    //微调某个MeasureSpec的大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
}

我们需要关注的三种测量模式,在后面的Measure阶段会用到

  • UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
  • EXACTLY:精确测量模式,当该视图的layout_width或者layout_height指定为具体数值或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize值
  • AT_MOST:最大值模式,当该视图的layout_width或者parent_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。

    对DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。

四、Measure

    Measure操作用来计算View的实际大小,由前面的分析可知,页面的测量流程是从performMeasure方法开始的,核心代码:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

    具体的测量操作是分发给ViewGroup的,由ViewGroup在它的measureChild方法中传递给子View。ViewGroup通过遍历自身所有的子View,并逐个调用子View的measure方法实现测量操作。

//遍历测量ViewGroup中所有的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        //当View的可见性处于GONE状态时,不对其进行测量
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

 

//测量某个指定的View
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    //根据父容器的MeasureSpec和子View的LayoutParams等信息计算子View的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

    再看看View(ViewGroup)的measure方法,最终的测量是通过回调onMeasure方法实现的,这个通常由View的特定子类自己实现,开发者也可以通过重写这个方法实现自定义View。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...  
}
//如果需要自定义测量过程,则子类可以重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //setMeasuredDimension方法用于设置View的测量宽高
    getDefaultSize(getSuggestedMinimumHeight(),getSuggestedMinimumWidth());
}

   

//如果View没有重写onMeasure方法,则默认会直接调用getDefaultSize,来获得View的宽高
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;
}

五、layout

    Layout过程用来确定View在父容器中的布局位置,它是由父容器获取子View的位置参数后,调用子View的layout方法并将位置参数传入实现的,ViewRootImpl的performLayout代码:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...          
}

 

public final void layout(int l, int t, int r, int b) {
    
     super.layout(l, t, r, b);
    
}

 

//空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View控件布局流程
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
}

 

六、Draw

    Draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始,核心代码:

private void performDraw() {
    draw(fullRedrawNeeded);    
    ...
}
private void draw(boolean fullRedrawNeeded) {
     ...
     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
      }
      ...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {

        mView.draw(canvas);

             
}

    

       可以看到最终调用到每个View的draw方法绘制每个具体的View,绘制基本上可以分为六个步骤,代码如下:

protected void onDraw(Canvas canvas) {
    //第一:  绘制View的背景
    drawBackground(canvas);
    ...
    //第二:如果需要的话,保存canvas的图层,为fading做准备
    int saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    ...
    //第三:绘制View的内容
    onDraw(canvas);
    ...
    //第四:绘制View的子View
    dispatchDraw(canvas);

    ...
    //第五:如果需要的话,绘制View的fading边缘并恢复图层
    ...
    canvas.restoreToCount(saveCount);

    //第六:绘制View的装饰
    onDrawScrollBars(canvas);

}

七、开源代码库

最后再分享一个自己积攒很久的代码库,只有你想不到,没有用不到的,欢迎star

https://github.com/xijiufu

由于github服务器在美国,有时访问很慢,还提供了开源中国地址库,2个仓库代码均同步更新:

http://git.oschina.net/xijiufu

© 著作权归作者所有

共有 人打赏支持
MrXI
粉丝 26
博文 13
码字总数 33406
作品 0
成都
程序员
加载中

评论(1)

Ronald9
Ronald9
对DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。

是不是多写了一次?由父视图的
自定义View心法——View工作流程

前言 本文的目的有两个: 给对自定义View感兴趣的人一些入门的指引 给正在使用自定义View的人一些更深入的解析 自定义View一直都被认为是Android开发高手的必备技能,而稳中带皮的学习View的...

Alex_Payne
05/24
0
0
开源项目学习与分析系列——ArcMenu

从现在开始,我将写下由于项目而接触到的优秀的Android开源项目的学习理解。一来有助于自己的提高,方便以后的查阅;二来学习Android需要有开源的精神,和别人分享是很重要的。我现在对Andro...

墨迹天下
2014/04/03
0
1
三分钟了解Activity工作流

一、 什么是工作流 以请假为例,现在大多数公司的请假流程是这样的 员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请...

java1990
2015/08/05
0
0
欢呼吧!App Inventor for Android 使用总结

昨日我们报道了Google App Inventor for Android,它是一个基于网页的开发环境,即使是没有开发背景的人也能通过他轻松创建 Android 应用程序。这个产品已经测试了一年之久了,主要是和教育机...

LiSteven
2013/08/09
0
0
Android控件TextView的实现原理分析

在前面一个系列的文章中,我们以窗口为单位,分析了WindowManagerService服务的实现。同时,在再前面一个系列的文章中,我们又分析了窗口的组成。简单来说,窗口就是由一系列的视图按照一定的...

Luoshengyang
06/26
0
0
Android 系统性能优化(36)---显示性能指标

从 Android 诞生的那一刻起,流畅度就为众人所关注。一时之间,似乎所有人都在讨论 Android 和 iOS 谁的流畅度更好。但是,毫不夸张的说,流畅度绝对是 Android 众多性能维度中最为奇葩的一个...

zhangbijun1230
04/19
0
0
聊聊为什么在activity启动的时候获取不到View的宽高

周五一大早看到一篇鸿洋推了一篇博客讲onResume中Handler.post(Runnable)为什么获取不到宽高?,细细读了两遍,有些收获,但依然有些不明白的地方,同时对有些细节不太认同。于是自己梳理了一...

Marco黑八
06/03
0
0
Android视图绘制流程完全解析,带你一步步深入了解View(二)

原文地址:http://blog.csdn.net/guolin_blog/article/details/16330267 在此转载,以备不时之需,建议看原文,转载的排版有点问题 在上一篇文章中,我带着大家一起剖析了一下LayoutInflate...

工作日
2014/02/26
0
0
SurfaceFlinger draw/render/display流程(fps)

前言:那些年我们用过的显示性能指标 相对其他 Android 性能指标(如内存、CPU、功耗等)而言,显示性能(包括但不仅限于我们常说的“流畅度”)的概念本来就相对复杂。让我们更蛋疼的是,业...

u010164190
05/03
0
0
Android系统源码分析团体项目BeesAndroid正式上线啦

嗨,BeesAndroid开源技术小组正式成立啦,Bees,即蜜蜂,取义分享、合作与奉献的意思,这也是BeesAndroid小组的宗旨,我们第一个团体项目BeesAndroid也于2018年3月6日同步上线,该项目的前 ...

郭孝星
03/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

java集合元素的默认大小

当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使...

竹叶青出于蓝
3分钟前
0
0
Java快速开发平台,JEECG 3.7.7闪电版本发布,增加多套主流UI代码生成器模板

JEECG 3.7.7 闪电版本发布,提供5套主流UI代码生成器模板 导读 ⊙平台性能优化,速度闪电般提升 ⊙提供5套新的主流UI代码生成器模板(Bootstrap表单+BootstrapTable列表\ ElementUI列表表单)...

Jeecg
6分钟前
0
0
export 和 module.export 的区别

在浏览器端 js 里面,为了解决各模块变量冲突等问题,往往借助于 js 的闭包把左右模块相关的代码都包装在一个匿名函数里。而 Nodejs 编写模块相当的自由,开发者只需要关注 require,exports,...

孟飞阳
8分钟前
0
0
技术教育的兴起

技术教育的兴起 作者: 阮一峰 1、 有一年,我在台湾环岛旅行。 花莲的海边,我遇到一对台湾青年夫妻,带着女儿在海滩上玩。我们聊了起来。 当时,我还在高校当老师。他们问我,是否觉得台湾...

吕伯文
8分钟前
0
0
Linux服务器下的HTTP抓包分析

说到抓包分析,最简单的办法莫过于在客户端直接安装一个Wireshark或者Fiddler了,但是有时候由于客户端开发人员(可能是第三方)知识欠缺或者其它一些原因,无法顺利的在客户端进行抓包分析,...

mylxsw
13分钟前
0
0
mybatis3-javaapi

sqlSessionFactoryBuilder->sqlSessionFactory->sqlSession<-rowbound<-resultHandler myBatis uses a Java enumeration wrapper for transaction isolation levels, called TransactionIsol......

writeademo
16分钟前
0
0
Java NIO:浅析I/O模型

也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型。下面本文先从同步和异步的概念...

yzbty23
17分钟前
0
0
了解iOS消息推送一文就够:史上最全iOS Push技术详解

本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表。 1、引言 开发iOS系统中的Push推送,通常有以下3种情况: 1)在线Push:比如QQ、微信等IM界面处于前台时,聊天消息和指令都会...

JackJiang-
18分钟前
0
0
Mysql汉子转拼音

update t_app_city SET CITY_NAME_BEGIN = ELT(INTERVAL(CONV(HEX(LEFT(CONVERT(CITY_NAME USING gbk),1)),16,10), 0xB0A1,0xB0C5,0xB2C1,0xB4EE,0xB6EA,0xB7A2,0xB8C1,0xB9FE,0xBBF7, 0xBFA......

尘叙缘
20分钟前
0
0
大数据构建智慧城市“新引擎”,加速推进新旧动能转换

——“大数据与智慧城市”技术交流分享会——济南站召开 7月13日,“大数据携手智慧城市,助力山东新旧动能转换”技术交流分享会——济南站在山东信息通信技术研究院会议室成功举办,此次会议...

左手的倒影
22分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部