安卓开发艺术笔记 | View的工作原理(绘制流程和自定义View)

目录

一.MeasureSpec

  1. MeasureSpec基础
  2. MeasureSpec和LayoutParams关系

二.View的绘制流程

  1. onMeasure()
  2. onLayout()
  3. onDraw()

三.自定义View

  1. 自定义view分类
  2. 自定义view步骤
  3. 自定义view的注意事项

一.MeasureSpec

1.MeasureSpec基础

(1)组成:MeasureSpec是一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize( 某种测量模式下的规格大小)。

 

(2)三种模式:

  • UNSPECIFIED:父容器不对View有任何限制,要多大有多大。常用于系统内部。
  • EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
  • AT_MOST(最大模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。

(图片来自厘米姑娘)

 

2.MeasureSpec和LayoutParams关系

顶级View(即DecorView):其MeasureSpec由窗口的尺寸和自身的LayoutParams决定

子View:MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。

 

二.View的绘制流程

1.总的流程:measure->layout->draw

  • measure确定View的测量宽高
  • layout确定View的最终宽高和四个顶点的位置
  • draw将View 绘制到屏幕
  • 对应onMeasure()、onLayout()、onDraw()三个方法。

具体过程:

  • ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带。
  • View的绘制流程是从ViewRoot和performTraversals开始。
  • performTraversals()依次调用performMeasure()、performLayout()和performDraw()三个方法,分别完成顶级 View的绘制。
  • 其中,performMeasure()会调用measure(),measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。过程图如下:

 

2.onMeasure:确定测量宽高,但是不是最终的宽高(layout中可以设置宽高,但是一般onMeasure中的测量宽高就是最终宽高)

(1)子View:通过measure()即可完成测量

 

注意:

  • 继承自View的自定义控件设置为wrap_content时,其SpecMode=AT_MOST模式,根据上表可知其MeasureSpec=AT_MOST+parentSize。所以展示出来的效果为match_parent。
  • 继承自View的自定义控件需要重写onMeasure()设置wrap_content时的默认大小(防止展示效果为match_parent),重写过程如下
@Override
    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);
        }
    }

 

(2)ViewGroup的measure:除了完成ViewGroup自身的测量外,还回去遍历调用子view的neasure方法

ViewGroup中没有重写onMeasure(),而是提供measureChildren()。

 

3.layout过程:确定View的最终宽高和四个顶点的位置

大致流程:从顶级View开始依次调用layout(),其中子View的layout()会调用setFrame()来设定自己的四个顶点(mLeft、mRight、mTop、mBottom),接着调用onLayout()来确定其坐标,注意该方法是空方法,因为不同的ViewGroup对其子View的布局是不相同的。

最终宽高:一般情况下为上一步的测量宽高,但是用户可以重写onLayout中确定最终宽高。如下代码会使最终宽高比测量宽高大100px

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

 

4.draw过程:绘制到屏幕

绘制顺序:

  • 绘制背景:background.draw(canvas)
  • 绘制自己:onDraw(canvas)
  • 绘制children:dispatchDraw(canvas)
  • 绘制装饰:onDrawScrollBars(canvas)

注意:

  • Vew有一个特殊的方法setWillNotDraw(),该方法用于设置 WILL_NOT_DRAW 标记位(其作用是当一个View不需要绘制内容时,系统可进行相应优化)。
  • 默认情况下View是没有这个优化标志的(设为true)。
  • 默认情况下ViewGroup有这个优化标志的(设为false)。所以当ViewGroup却需要绘制内容时,该标志位不起作用,需要显示的关闭WILL_NOT_DRAW (设为true)

 

三.自定义View

1.自定义View分类

注:

继承View需要重写onMeasure,使得其支持wrap_content

重写onDraw,使得其支持padding

 

2.自定义View步骤

(1)自定义属性,xml文件中指定,View的构造函数中得到属性组,在得到具体的属性做操作。

(2)重写onMeasure和onDraw对wrap_content和padding做特殊处理

 

3.注意事项

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值