[View] - View绘制复习
2017-01-17 05:21
176 查看
测量measure:
MeasureSpecMode
EXACTLY 代表的是父布局已经检查出子View所需要的精确大小 == specsize
AT_MOST 代表的是父布局未能检查出子View所需的精确大小, 但是给出了一个参考值 <= specsize
UNSOECIFIED 父布局不对子View做限制
在measure测量子View的时候,父View影响着子View的MeasureSpec的生成
测量子View主要有4个步骤
获得子View的LayoutParams
获得子View的宽的MeasureSpec
获得子View的高的MeasureSpec
测量子View
子View的MeasureSpec是依赖父View的MeasureSpec
子View的MeasureSpec是由父View的MeasureSpec和子View的LayoutParams共同决定的
由父容器MeasureSpec和子View本身的LayoutParams给出的子View的MeasureSpec是一个期望值(参考值)
我们可以摒弃这个参考值,直接调用setMeasureDimension()来设置子View的宽或者高
测量子View的可以调用的方法有两种:
measureChild()
measureChildWithMargins()
measureChildWithMargins() 和 measureChild()有什么区别:
都是用来计算测量子View的大小
两者调用getChildMeasureSpec() 时均要先计算父View已占空间
measureChild() 计算父View已占空间为 mPaddingLeft + mPaddingRight,即父容器左右两边padding值之和
measureChildWithMargins() 计算父View已占空间为mPaddingLeft + mPaddingRight + lp. leftMargin + lp.rightMagin + widthUsed
此处除了父容器左右两边的padding值之和,还包括了子View左右两边的margin值之和
View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)流程
调用setMeasureDimension()设置View的宽和高
在setMeasureDimension()中调用getDefaultSize()来获取View的宽和高
在getDefaultSize()中调用了getSuggestMinnumWidth()或者getSuggestMinnumHeight()来获取View宽和高的最小值
getDefaultSize()中根据View是否有设置背景mBackgroud来判断返回宽和高为: 最小值 或者 最小值与mBackgroud.getMinnumXXX()中的最大值
对于子View,如果在xml布局中对于大小设置为wrap_content,那么不管父View的SpecMode是EXACTLY还是AT_MOST,子View的SpecMode都会被赋值为AT_MOST
并且子View的SpecSize被赋值为父View剩余的空间,这时子Vie的wrap_content就失去了原本的意义,变得和match_parent一样了
所以,在自定义View的重写onMeasure()过程中需要手动的处理View的宽或者高为wrap_content的情况
对于子View被设置为wrap_content时的处理方法:
如果xml中的宽或高均采用wrap_content,那么需要设置View的宽和高为mWidth和mHeight
如果xml的宽或高中的其中一个采用wrap_content,那么就将该值设置为mWidth或者mHeight
另外一个值采用系统测量的SpecSize(也就是根据父View的Spec和子View的LayoutParams计算)
对于子View的SpecMode为AT_MOST的情况
结论1
条件:当子View布局大小宽或者高任意一项为match_parent且父View的SpecMode为AT_MOST的时候
结论:子View的对应SpecMode也为AT_MOST,且SpecSize为parentLeftSize(也就是父View剩余空间)
待证现象:
按照修改的代码逻辑,当子View的大小为match_parent且SpecMode为AT_MOST时,子View的SpecSize会被设置为指定的默认值与期望的parentLeftSize不符。
上面结论1的先决条件是父View的SpecMode为AT_MOST
按照规则,父View的大小为wrap_content和match_parent时,其SpecMode都有可能被设置为AT_MOST
假设1:
当父View的大小为wrap_ content时
结合结论1:子View期望大小为match_ parent,但是父View的大小又设置为wrap_content
很明显这是不合理的
假设2:
当父View的大小为match_ parent
结合结论1:子View的大小为match_ parent,父View的大小为match_parent
好像暂时合理了,而且是唯一合理的
那么得到结论2(结论1的隐含结论):
条件:当子View布局大小宽或者高任意一项为match_parent且父View的SpecMode为AT_MOST的时候
结论:子View的对应SpecMode也为AT_MOST且SpecSize为parentLeftSize(也就是父View剩余空间),且父View的大小必为match_parent
那么父View的上一级View(爷容器)是怎么样的呢?
按照假设1,爷容器不能为wrap_ content
按照原则,如果父(子View)容器的SpecMode要为AT_MOST且大小为match_paren,爷容器(父)的SpecMode不可能为EXACTLY,即爷容器大小不能为具体值(如100px)
那么,爷容器的大小只能是 match_parent,且SpecMode为AT_ MOST
得到结论3:
条件:当子View的大小为match_parent且SpecMode为AT_MOST时
结果:父View的大小为match_parent且SpecMode为AT_MOST
abstract ViewGroup extends View
ViewGroup是个抽象类,没有重写View的onMeasure()方法,但是提供了measureChildren()测量所有的子View
那么ViewGroup的实现类就应该根据自身的要求和特点重写onMeasure()方法
ViewGroup测量子类的流程:measureChildren() ——》 measureChild() ——》child.measure() ——》child.onMeasure()
具体可以参见ViewGroup的子类Linearlayout的onMeasure()方法
布局layout:
layout(int l,int t,int r,int b),其中 l、t、r、b 分别表示子View在相对于父View的左上右下的坐标
该方法根据l、t、r、b 和旧的mLeft、mTop、mRight、mBottom比较,如果其中任意一项发生变化,就判定该View的位置发生了变化
如果判定该View发生了变化,则调用onLayout()方法
onLayout(boolean changed, int left, int top, int right, int bottom)
该方法用于指定子View的大小和位置,只有ViewGroup才有子View
View中的onLayout()方法是空实现,因为View没有子View
ViewGroup中的onLayout()方法是一个抽象方法
所以ViewGroup的子类都需要重写onLayout()方法,依据各自的布局规则确定子View的位置
ViewGroup首先调用了layout()方法确定了自己本身在父View中的位置
然后ViewGroup调用onLayout()确定了每个子View的位置
每个子View又会调用View的layout()方法确定自己在ViewGroup中的位置
ViewGroup自定义简单示例
measure和layout过程中需要注意的
获取View测量大小measureWidth和measureHeight的时机
在某些复杂或者极端情况下系统会多次执行measure过程
所以在onMeasure()中获取View的测量大小是一个不准确的值
最好是在onMeasure()之后的下一阶段onLayout()中去获取
getMeasureWith() 和 getWidth()的区别
绝大多数情况下,两者的返回值是相同的,但是使用方法有所不同
getMeasureWidth()方法在measure过程结束后使用,获取的是View的测量大小,是setMeasureDimension()中设置的测量大小
getWidth()方法在layout过程结束后使用,获取的是View的实际大小,是View的右坐标减去作坐标(right - left)的值
直接继承ViewGroup的弊端
在项目的实际开发中情况比较复杂,可能包含较多子View或者嵌套
在这样的情况下,我们在onMeasure()和onLayout()方法中需要花费大量的精力来处理
所以在一般情况下,我们可以选择继承LineaLayout、RelativeLayout等系统已有的布局来简化这两部分的处理
>
绘制draw:
该阶段对视图进行绘制
主要是View.draw(Canvas canvas)方法
绘制背景,drawBackground(canvas)
保存当前画布的堆栈并在画布上创建Layer用于绘制View在滑动时的边框渐变效果,一般情况下不需要处理这一步
绘制View的内容,onDraw(canvas)
绘制View的子View,dispatchDraw(canvas)
绘制当前视图在滑动时的边框渐变效果,如上面所述,一般情况下不需要处理这一步
绘制View的滚动条,任何一个View都是有滚动条,只是一般情况下我们都没有将他显示出来而已
onDraw(Canvas can
4000
vas)
View.onDraw(Canvas canvas)方法是一个空方法,只有一个Canvas参数
每一个View绘制的内容都是不同的,所以需要由具体的子View去实现各自不同的需求
在绘图时需要明确的四个核心组件(basic components)
a bitmap,用于保存所绘制的像素pixel
a drawing primitive,基本的图,eg:直线,圆,矩形、Path,text,Bitmap
a paint,用于绘制的画笔,画笔可以选择不同的颜色、大小
a canvas,用于执行绘图操作,writing into the bitmap
如上述所知,Canvas必须需要一个Bitmap对象保存像素
onDraw(Canvas canvas)方法的参数canvas是来自于ViewRootImpl的Surface,包含系统创建Bitmap对象
Canvas绘制示例
Canvas除了画各种图形,还可以做一些操作,比如旋转,裁剪、平移等等
canvas.translate ,平移
canvas.rotate ,旋转
canvas.clipRect ,裁剪
canvas.save和canvas.restore,save生成一个新的图层(Layer),restore将新图层与原来的图层合并形成一个新的图像
PorterDuffXfermode Porter和Duff的图像学
Bitmap和Matrix 像素变换
Shader 渲染图像和几何图形
PathEffect 绘制路径效果和样式
自定义View常见的四种方式:
直接继承View
使用该方式实现View时通常的核心操作都在onDraw()当中进行
需要注意在onMeasure()方法中对处理view大小为wrap_content,SpecMode为AT_MOST时的情况,否则wrap_content和match_parent大小一样
除此之外,还需要注意对于padding的处理
继承系统已有的View
使用这种方式,只需要在系统控件的基础上做出一些调整即可
不需要去自己支持wrap_content和padding
直接继承ViewGroup
需要注意2点:
onMeasure() 实现wrap_content的支持
在onMeasure()和onLayout()中处理自身的padding以及子View的margin
继承系统已有的ViewGroup
继承ViewGroup中的注意点就不用再过多考虑了
MeasureSpecMode
EXACTLY 代表的是父布局已经检查出子View所需要的精确大小 == specsize
AT_MOST 代表的是父布局未能检查出子View所需的精确大小, 但是给出了一个参考值 <= specsize
UNSOECIFIED 父布局不对子View做限制
在measure测量子View的时候,父View影响着子View的MeasureSpec的生成
测量子View主要有4个步骤
获得子View的LayoutParams
获得子View的宽的MeasureSpec
获得子View的高的MeasureSpec
测量子View
子View的MeasureSpec是依赖父View的MeasureSpec
子View的MeasureSpec是由父View的MeasureSpec和子View的LayoutParams共同决定的
由父容器MeasureSpec和子View本身的LayoutParams给出的子View的MeasureSpec是一个期望值(参考值)
我们可以摒弃这个参考值,直接调用setMeasureDimension()来设置子View的宽或者高
测量子View的可以调用的方法有两种:
measureChild()
measureChildWithMargins()
measureChildWithMargins() 和 measureChild()有什么区别:
都是用来计算测量子View的大小
两者调用getChildMeasureSpec() 时均要先计算父View已占空间
measureChild() 计算父View已占空间为 mPaddingLeft + mPaddingRight,即父容器左右两边padding值之和
measureChildWithMargins() 计算父View已占空间为mPaddingLeft + mPaddingRight + lp. leftMargin + lp.rightMagin + widthUsed
此处除了父容器左右两边的padding值之和,还包括了子View左右两边的margin值之和
View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)流程
调用setMeasureDimension()设置View的宽和高
在setMeasureDimension()中调用getDefaultSize()来获取View的宽和高
在getDefaultSize()中调用了getSuggestMinnumWidth()或者getSuggestMinnumHeight()来获取View宽和高的最小值
getDefaultSize()中根据View是否有设置背景mBackgroud来判断返回宽和高为: 最小值 或者 最小值与mBackgroud.getMinnumXXX()中的最大值
对于子View,如果在xml布局中对于大小设置为wrap_content,那么不管父View的SpecMode是EXACTLY还是AT_MOST,子View的SpecMode都会被赋值为AT_MOST
并且子View的SpecSize被赋值为父View剩余的空间,这时子Vie的wrap_content就失去了原本的意义,变得和match_parent一样了
所以,在自定义View的重写onMeasure()过程中需要手动的处理View的宽或者高为wrap_content的情况
对于子View被设置为wrap_content时的处理方法:
如果xml中的宽或高均采用wrap_content,那么需要设置View的宽和高为mWidth和mHeight
如果xml的宽或高中的其中一个采用wrap_content,那么就将该值设置为mWidth或者mHeight
另外一个值采用系统测量的SpecSize(也就是根据父View的Spec和子View的LayoutParams计算)
对于子View的SpecMode为AT_MOST的情况
结论1
条件:当子View布局大小宽或者高任意一项为match_parent且父View的SpecMode为AT_MOST的时候
结论:子View的对应SpecMode也为AT_MOST,且SpecSize为parentLeftSize(也就是父View剩余空间)
待证现象:
按照修改的代码逻辑,当子View的大小为match_parent且SpecMode为AT_MOST时,子View的SpecSize会被设置为指定的默认值与期望的parentLeftSize不符。
上面结论1的先决条件是父View的SpecMode为AT_MOST
按照规则,父View的大小为wrap_content和match_parent时,其SpecMode都有可能被设置为AT_MOST
假设1:
当父View的大小为wrap_ content时
结合结论1:子View期望大小为match_ parent,但是父View的大小又设置为wrap_content
很明显这是不合理的
假设2:
当父View的大小为match_ parent
结合结论1:子View的大小为match_ parent,父View的大小为match_parent
好像暂时合理了,而且是唯一合理的
那么得到结论2(结论1的隐含结论):
条件:当子View布局大小宽或者高任意一项为match_parent且父View的SpecMode为AT_MOST的时候
结论:子View的对应SpecMode也为AT_MOST且SpecSize为parentLeftSize(也就是父View剩余空间),且父View的大小必为match_parent
那么父View的上一级View(爷容器)是怎么样的呢?
按照假设1,爷容器不能为wrap_ content
按照原则,如果父(子View)容器的SpecMode要为AT_MOST且大小为match_paren,爷容器(父)的SpecMode不可能为EXACTLY,即爷容器大小不能为具体值(如100px)
那么,爷容器的大小只能是 match_parent,且SpecMode为AT_ MOST
得到结论3:
条件:当子View的大小为match_parent且SpecMode为AT_MOST时
结果:父View的大小为match_parent且SpecMode为AT_MOST
abstract ViewGroup extends View
ViewGroup是个抽象类,没有重写View的onMeasure()方法,但是提供了measureChildren()测量所有的子View
那么ViewGroup的实现类就应该根据自身的要求和特点重写onMeasure()方法
ViewGroup测量子类的流程:measureChildren() ——》 measureChild() ——》child.measure() ——》child.onMeasure()
具体可以参见ViewGroup的子类Linearlayout的onMeasure()方法
布局layout:
layout(int l,int t,int r,int b),其中 l、t、r、b 分别表示子View在相对于父View的左上右下的坐标
该方法根据l、t、r、b 和旧的mLeft、mTop、mRight、mBottom比较,如果其中任意一项发生变化,就判定该View的位置发生了变化
如果判定该View发生了变化,则调用onLayout()方法
onLayout(boolean changed, int left, int top, int right, int bottom)
该方法用于指定子View的大小和位置,只有ViewGroup才有子View
View中的onLayout()方法是空实现,因为View没有子View
ViewGroup中的onLayout()方法是一个抽象方法
所以ViewGroup的子类都需要重写onLayout()方法,依据各自的布局规则确定子View的位置
ViewGroup首先调用了layout()方法确定了自己本身在父View中的位置
然后ViewGroup调用onLayout()确定了每个子View的位置
每个子View又会调用View的layout()方法确定自己在ViewGroup中的位置
ViewGroup自定义简单示例
measure和layout过程中需要注意的
获取View测量大小measureWidth和measureHeight的时机
在某些复杂或者极端情况下系统会多次执行measure过程
所以在onMeasure()中获取View的测量大小是一个不准确的值
最好是在onMeasure()之后的下一阶段onLayout()中去获取
getMeasureWith() 和 getWidth()的区别
绝大多数情况下,两者的返回值是相同的,但是使用方法有所不同
getMeasureWidth()方法在measure过程结束后使用,获取的是View的测量大小,是setMeasureDimension()中设置的测量大小
getWidth()方法在layout过程结束后使用,获取的是View的实际大小,是View的右坐标减去作坐标(right - left)的值
直接继承ViewGroup的弊端
在项目的实际开发中情况比较复杂,可能包含较多子View或者嵌套
在这样的情况下,我们在onMeasure()和onLayout()方法中需要花费大量的精力来处理
所以在一般情况下,我们可以选择继承LineaLayout、RelativeLayout等系统已有的布局来简化这两部分的处理
>
绘制draw:
该阶段对视图进行绘制
主要是View.draw(Canvas canvas)方法
绘制背景,drawBackground(canvas)
保存当前画布的堆栈并在画布上创建Layer用于绘制View在滑动时的边框渐变效果,一般情况下不需要处理这一步
绘制View的内容,onDraw(canvas)
绘制View的子View,dispatchDraw(canvas)
绘制当前视图在滑动时的边框渐变效果,如上面所述,一般情况下不需要处理这一步
绘制View的滚动条,任何一个View都是有滚动条,只是一般情况下我们都没有将他显示出来而已
onDraw(Canvas can
4000
vas)
View.onDraw(Canvas canvas)方法是一个空方法,只有一个Canvas参数
每一个View绘制的内容都是不同的,所以需要由具体的子View去实现各自不同的需求
在绘图时需要明确的四个核心组件(basic components)
a bitmap,用于保存所绘制的像素pixel
a drawing primitive,基本的图,eg:直线,圆,矩形、Path,text,Bitmap
a paint,用于绘制的画笔,画笔可以选择不同的颜色、大小
a canvas,用于执行绘图操作,writing into the bitmap
如上述所知,Canvas必须需要一个Bitmap对象保存像素
onDraw(Canvas canvas)方法的参数canvas是来自于ViewRootImpl的Surface,包含系统创建Bitmap对象
Canvas绘制示例
Canvas除了画各种图形,还可以做一些操作,比如旋转,裁剪、平移等等
canvas.translate ,平移
canvas.rotate ,旋转
canvas.clipRect ,裁剪
canvas.save和canvas.restore,save生成一个新的图层(Layer),restore将新图层与原来的图层合并形成一个新的图像
PorterDuffXfermode Porter和Duff的图像学
Bitmap和Matrix 像素变换
Shader 渲染图像和几何图形
PathEffect 绘制路径效果和样式
自定义View常见的四种方式:
直接继承View
使用该方式实现View时通常的核心操作都在onDraw()当中进行
需要注意在onMeasure()方法中对处理view大小为wrap_content,SpecMode为AT_MOST时的情况,否则wrap_content和match_parent大小一样
除此之外,还需要注意对于padding的处理
继承系统已有的View
使用这种方式,只需要在系统控件的基础上做出一些调整即可
不需要去自己支持wrap_content和padding
直接继承ViewGroup
需要注意2点:
onMeasure() 实现wrap_content的支持
在onMeasure()和onLayout()中处理自身的padding以及子View的margin
继承系统已有的ViewGroup
继承ViewGroup中的注意点就不用再过多考虑了
相关文章推荐
- Activity启动流程以及View的绘制流程
- Android中View绘制流程以及invalidate()等相关方法分析
- android绘制view的过程之一---------计算view大小(measure)
- Android中View绘制流程以及invalidate()
- Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解
- View的绘制流程
- Android中View绘制流程以及invalidate()等相关方法分析
- 安卓自定义View基础-绘制饼图
- Path绘制贝塞尔曲线和波浪waveView
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView)
- Android UI 绘制过程浅析(五)自定义View
- Android自定义View【实战教程】5⃣️---Canvas详解及代码绘制安卓机器人
- 从ViewRootImpl类分析View绘制的流程
- View的绘制
- Android 中View的绘制机制源码分析 二
- Android 开发进阶:自定义 View 1-3 文字的绘制
- Android View绘制三大流程探索及常见问题
- view绘制流程
- View测量、布局及绘制原理
- Android 中View的绘制机制源码分析