转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/38426471(来自singwhatiwanna的csdn博客)

Android View系统解析系列:

Android View系统解析(上)

介绍View的基础知识、View的滑动、弹性滑动、滑动冲突解决方案、事件分发等

Android View系统解析(下)

介绍View的Framework层原理、View的measure / layout / draw三大流程和一些高级技巧

本次主要介绍下半部分,提纲例如以下

View的绘制过程

measure/layout/draw 工作流程

识别 MeasureSpec 并能够 make 合适的 MeasureSpec

在渲染前获取 View 的宽高

构造特殊的 View

自己定义View

自己定义View分类

自己定义 View 须知

一 View的绘制过程

初识 ViewRoot

ViewRoot
相应于 ViewRootImpl 类,是连接 WindowManager 和 DecorView 的纽带。

ActivityThread 中当 activity 对象被创建好后,会将 DecorView 增加到 Window中同一时候完毕 ViewRootImpl 的创建并建立和 DecorView 的联系。

root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
view 的绘制流程从 ViewRoot 的 performTraversals 開始,代码流程是这种:
performMeasure -> measure -> onMeasure
performLayout -> layout -> onLayout
performDraw -> draw -> onDraw

activity 界面的组成

由下图可知,DecorView作为顶级View。普通情况下它有上下两部分组成(详细情况会和api版本号以及Theme有关)。上面是title。以下是content,在activity中我们调用setContentView所设置的view事实上就是被加到content中,而怎样得到content呢,能够这样:ViewGroup content= (ViewGroup)findViewById(android.R.id.content),怎样得到我们所设置的view呢,能够这样:content.getChildAt(0)。同一时候,通过源代码我们能够知道,DecorView事实上是一个FrameLayout。这里要说明的一点是View层的大部分事件都是从DecorView传递到我们的view中的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luZ3doYXRpd2FubmE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

MeasureSpec

MeasureSpec 
封装了父容器对 view 的布局上的限制。内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的參考尺寸,当中SpecMode 有例如以下三种:
UNSPECIFIED
父容器不正确 view 有不论什么限制。要多大给多大
EXACTLY
父容器已经检測出 view 所须要的大小
AT_MOST
父容器指定了一个大小, view 的大小不能大于这个值
MeasureSpecs 的意义
通过将 SpecMode 和 SpecSize 打包成一个 int 值能够避免过多的对象内存分配,为了方便操作,其提供了打包 / 解包方法

MeasureSpec 的实现

MeasureSpec

代表一个 32 位 int 值
高 2 位代表 SpecMode ,低 30 位代表 SpecSize

以下先看一下MeasureSpec 内部的一些常量的定义,通过以下的代码,应该不难理解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; public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
} public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
} public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

MeasureSpec 与 LayoutParams

对于 DecorView ,其 MeasureSpec 由窗体的尺寸和其自身的LayoutParams 来共同确定
对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定
MeasureSpec 一旦确定后, onMeasure 中就能够确定自身的宽高

MeasureSpec-DecorView

这里分析下顶级容器DecorView的MeasureSpec的产生过程

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

上述代码描写叙述了DecorView的MeasureSpec的产生过程,为了更清晰地了解,我们继续看下去

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;
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的MeasureSpec的产生过程就非常明白了,详细来说其遵守例如以下规则:

依据它的LayoutParams中的宽高的參数来分。

LayoutParams.MATCH_PARENT:其模式为精确模式,大小就是窗体的大小

LayoutParams.WRAP_CONTENT:其模式为最大模式。大小不定。可是不能超过窗体的大小

固定大小(比方100dp):其模式为精确模式。大小为LayoutParams中指定的大小

MeasureSpec- 应用层 View

关于应用层View,这里是指我们布局中的view。其MeasureSpec的创建遵循下表中的规则

针对上表,这里再做一下详细的说明。前面已经提到。对于应用层 View 。其 MeasureSpec 由父容器的 MeasureSpec 和自
身的 LayoutParams 来共同决定,那么针对不同的父容器和view本身不同的LayoutParams。view就能够有多种MeasureSpec。

这里简单说下,当view採用固定宽高的时候。无论父容器的MeasureSpec是什么。view的MeasureSpec都是精确模式而且其大小遵循Layoutparams中的大小;当view的宽高是match_parent时,这个时候假设父容器的模式是精准模式,那么view也是精准模式而且其大小是父容器的剩余空间。假设父容器是最大模式,那么view也是最大模式而且其大小不会超过父容器的剩余空间。当view的宽高是wrap_content时,无论父容器的模式是精准还是最大化,view的模式总是最大化而且大小不能超过父容器的剩余空间。

可能大家会发现,在我们的分析中漏掉了Unspecified模式。这个模式主要用于系统内部多次measure的情况下,一般来说,我们不须要关注此模式。

支持 View 的 wrap_content

view#onMeasure 的默认实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), 
    widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

注意:
通过 onDraw 派生的 View ,须要重写 onMeasure 并设置 wrap_content 时的自
身大小,否则使用 wrap_content 就相当于用 match_parent 。

原因分析:见上面的表格

那么怎样重写onMeasure从而让view支持wrap_content呢?请參看以下的典型代码,须要注意的是,代码中的mWidth和mHeight指的是view在wrap_content下的内部规格。而这一规格(宽高)应该由自己定义view内部来指定。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}

View的measure 流程

view 的 measure 流程
简单,直接完毕
ViewGroup 的 measure 流程
除了完毕自己的 measure ,还会遍历去调用全部 child 的measure 方法,各个 child 再递归去运行这个流程
measure 的直接结果
getMeasuredWidth/Height 能够正确地获取到
注:某些情况下,系统可能须要多次 measure 才干确定大小

在渲染前获取 View 的宽高

这是一个比較有意义的问题。或者说有难度的问题,问题的背景为:有时候我们须要在view渲染前去获取其宽高。典型的情形是,我们想在onCreate、onStart、onResume中去获取view的宽高。

假设大家尝试过,会发现。这个时候view还没有measure好,宽高都为0。那究竟该怎么做才干正确获取其宽高呢,以下给出三种方法

Activity/View#onWindowFocusChanged :这种方法表明,view已经初始化完毕了,宽高已经准备好了
view.post(runnable) :通过post能够将一个runnable投递到消息队列的尾部,然后等待looper调用此runnable的时候。view也已经初始化好了
view.measure(int widthMeasureSpec, int heightMeasureSpec) :通过手动去measure来视图得到view的宽高

前两种方法都比較好理解也比較简单,这里主要介绍下第三种方法的详细使用方法:

採用 view.measure 去提前获取 view 的宽高。依据 view 的 layoutParams 来分
match_parent
直接放弃。无法 measure 出详细的宽高
详细的数值( dp/px )
比方宽高都是 100px ,例如以下 measure :
    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
    view.measure(widthMeasureSpec, heightMeasureSpec);
wrap_content
例如以下 measure :
    int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
    view.measure(widthMeasureSpec, heightMeasureSpec);

注意到(1 << 30) - 1,通过分析MeasureSpec的实现能够知道,view的尺寸使用30位二进制表示的。也就是说最大是30个1即 2^30 - 1,也就是(1 << 30) - 1,在最大化模式下,我们用view理论上能支持的最大值去构造MeasureSpec是合理的。

关于view的measure。网络上有两个错误的使用方法。例如以下,为什么说是错误的,首先违背了系统的内部实现规范(由于无法通过错误的MeasureSpec去得出合法的SpecMode从而导致measure出错),其次不能保证一定能 measure 出正确的结果。

第一种错误使用方法
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);

另外一种错误使用方法
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)

View 的 layout 过程

layout 的主要作用
ViewGroup 用来确定子元素的位置。

流程
当 viewgroup 的位置被确定后。它在 onLayout 会遍历全部的 child 并调用其 layout 。在 layout 中 onLayout 会被调用。
关键方法
public void layout(int l, int t, int r, int b) 
onLayout(changed, l, t, r, b)

构造特殊的 View

问题:怎样让 getWidth 和 getMeasuredWidth 返回的值不一样?
private void setChildFrame(View child, int left, int top, int measuredWidth, int measureHeight) { 
child.layout(left, top, left + measuredWidth, top + measureHeight);
}
int width = right - left;
int height = bottom - top
方法
在父容器的 onLayout 中通过 child.layout 来放置 view 到任何位置
在自己的 onLayout 中改动 mLeft/mRight/mTop/mBottom

View 的 draw 过程

draw 的大致流程
a. 画背景  background.draw(canvas)
b. 绘制自己( onDraw )
c. 绘制 children ( dispatchDraw )
d. 绘制装饰( onDrawScrollBars )
备注:
dispatchDraw 会遍历调用全部 child 的 draw ,如此 draw 事件就一层层地传递了下去

二 自己定义 View

自己定义View类型

继承 View 重写 onDraw
继承 ViewGroup 派生特定的 Layout
继承特定的 View (比方 TextView , ListView )
继承特定的 Layout (比方 LinearLayout )

自己定义View须知

让 view 支持 wrap_content
假设有必要,让你的 view 支持 padding
尽量不要在 view 中使用 Handler ,不是必需
view 中假设有线程或者动画。须要及时停止,參考View#onDetachedFromWindow
view 带有滑动嵌套情形时。须要处理好滑动冲突

很多其它资料

http://blog.csdn.net/singwhatiwanna
https://github.com/singwhatiwanna

Android View系统解析(下)的更多相关文章

  1. Android View系统解析(上)

  2. Android Service完全解析(下)

    转载http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要内容,包括S ...

  3. Android GUI系统

    图解Android - Android GUI 系统 (1) - 概论 图解Android - Android GUI 系统 (2) - 窗口管理系统 图解Android - Android GUI ...

  4. Android View 测量流程(Measure)完全解析

    前言 上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而V ...

  5. 虾扯蛋:Android View动画 Animation不完全解析

    本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...

  6. Android Service完全解析,关于服务你所需知道的一切(下)

    转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要 ...

  7. 图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)

    Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...

  8. 【转】Android Service完全解析,关于服务你所需知道的一切(下) ---- 不错

    原文网址:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_bl ...

  9. Android ActionBar完全解析,使用官方推荐的最佳导航栏(下) .

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/25466665 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工 ...

随机推荐

  1. C Looooops(扩展欧几里得+模线性方程)

    http://poj.org/problem?id=2115 题意:给出A,B,C和k(k表示变量是在k位机下的无符号整数),判断循环次数,不能终止输出"FOREVER". 即转化 ...

  2. codevs1222 信与信封问题

    1222 信与信封问题  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond     题目描述 Description John先生晚上写了n封信,并相应地写了 ...

  3. C - Stones on the Table

    Problem description There are n stones on the table in a row, each of them can be red, green or blue ...

  4. P2241 统计方形(数据加强版)

    题目背景 1997年普及组第一题 题目描述 有一个n*m方格的棋盘,求其方格包含多少正方形.长方形 输入输出格式 输入格式: n,m因为原来数据太弱,现规定m小于等于5000,n小于等于5000(原来 ...

  5. 计算机图形学课件pdf版

    为方便大家学习,我将自己计算机图形学的课件分享. 下载链接:http://pan.baidu.com/s/1kV5BW8n 密码:eqg4 注:本课件与教材配套PPT有所不同.教材配套PPT是编写教材 ...

  6. 《计算机图形学基础(OpenGL版)》使用院校(更新)

    从清华大学出版社责任编辑处获悉,很多高等院校选用了我们这本教材,读者反应不错! 另外,编辑提供了一份详细的使用院校名单如下: 河南科技学院 中原工学院 河北工程大学 防空兵学院 伊犁师院电信学院 吉林 ...

  7. Visual Studio 推荐插件以及一些设置

    Microsoft Visual Studio 作为微软自家的 IDE 集成 NuGet 包管理器,是的引用一些不同版本组件DLL十分方便. 集成编译环境,强大的 Debug 断点.附加功能 项目模板 ...

  8. Appium Android 获取包名和 Activity 的几种方法 (转)

    本文档主要记录“获取包名和 Activity 的方法”,用于自动化测试时启动APP.以下方法主要来源于网络和社区同学的贡献,特此感谢! 1. 方法一: pm list package查看包名 adb ...

  9. 用了那么多项目管理工具,还是CORNERSTONE这款最好用

    在与软件开发有关的项目,往往会出现很难管理情况.许多事情都需提前计划.控制与管理,所以许多项目经理很容易迷失在计划的过程中.幸运的是,市场上提供了各种各样的项目管理工具.但不幸的是,工具实在是太多了. ...

  10. 【转】虚拟化(五):vsphere高可用群集与容错

    vsphere高级功能需要vcenter server和共享存储的支持才能实现.vsphere的高级功能有 vmotion.storage vmotion.vsphere HA.vsphere DRS ...