您的位置:首页 > 其它

[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中的注意点就不用再过多考虑了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: