Android View系统解析(下)
转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/38426471(来自singwhatiwanna的csdn博客)
Android View系统解析系列:
介绍View的基础知识、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系统解析(下)的更多相关文章
- Android View系统解析(上)
- Android Service完全解析(下)
转载http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要内容,包括S ...
- Android GUI系统
图解Android - Android GUI 系统 (1) - 概论 图解Android - Android GUI 系统 (2) - 窗口管理系统 图解Android - Android GUI ...
- Android View 测量流程(Measure)完全解析
前言 上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而V ...
- 虾扯蛋:Android View动画 Animation不完全解析
本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...
- Android Service完全解析,关于服务你所需知道的一切(下)
转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要 ...
- 图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)
Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...
- 【转】Android Service完全解析,关于服务你所需知道的一切(下) ---- 不错
原文网址:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_bl ...
- Android ActionBar完全解析,使用官方推荐的最佳导航栏(下) .
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/25466665 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工 ...
随机推荐
- Webpack 2.0 的文档
Webpack 2.0 的文档 https://webpack.js.org/get-started/
- E20170816-mk
deque 即双端队列.是一种具有队列和栈的性质的数据结构. revert vi. 恢复; 重提; 回到…上; <律>归还; n. 归属; 恢复原来信仰的人; Indicator ...
- tp 3.1.3 动态切换模板问题
if($this->isMobile()) { C('DEFAULT_THEME', 'mobile'); // 这里定义手机模板目录 C('TMPL_CACHE_PREFIX', 'm_'); ...
- dotnet core 发布配置(测试数据库和正式数据库自动切换)
一.起源 在进行项目开发时,常常要求开发环境,测试环境及正式环境的分离,并且不同环境运行的参数都是不一样的,比如监听地址,数据库连接信息等.当然我们把配置信息保存到一个文件中,每次发布的时候,可以先修 ...
- 最简单的多线程死锁案例代码(Java语言)
package com.thread.test; public class DeadLock { private static Object firstMonitor = new Object(); ...
- Eclipse的菜单简介
在Eclipse工作台的上方提供了菜单栏,该菜单栏包含了实现Eclipse各项功能的命令,并且与编辑器相关,即菜单栏中的菜单项与当前编辑器内打开的文件是关联的.例如,编辑器内没有打开任何文件,那么,将 ...
- MySQL笔试题搜罗
一.有表如下 +------+---------+--------+ | name | subject | score | +------+---------+--------+ | 张三 | 数学 ...
- 【Linux】连接CRT
linux中出现crt连接不上多数是ip地址设置不正确. window中命令行界面(cmd进入),输入ipconfig,查看虚拟机的ip. 打开linux终端,命令行下输入:ifconfig eth0 ...
- Deutsch lernen (15)
1. unterscheiden - unterschied - unterschieden 区别,区分:(能够)分清 Die beiden Begriffe sind nur schwer ...
- (转)基于openlayers实现聚类统计展示
http://blog.csdn.net/gisshixisheng/article/details/46137015 概述: 在前面的博文中讲述过基于Arcgis for js如何实现聚类统计展示, ...