自定义View基础

栏目: IOS · Android · 发布时间: 5年前

内容简介:掌握一门技术当然需要知道它的作用,不然学习起来就没有了动力。今天的主题是自定义View,既然谷歌已经提供了那么多得控件,那么为什么还要去自定义View呢?我总结一下主要有以下三大点:这种情况很普遍,比如下拉刷新吧,官方只是提供了一个默认的转圈圈,奇丑无比,当然入不了设计师的眼了,设计师将设计书一改,自定义View的需求就来了。虽然有很多的第三方大牛已经做好了各种各样的效果,但是我们也要了解其中的实现方式,万一效果有差池才能修改。

掌握一门技术当然需要知道它的作用,不然学习起来就没有了动力。今天的主题是自定义View,既然谷歌已经提供了那么多得控件,那么为什么还要去自定义View呢?我总结一下主要有以下三大点:

1.有些UI效果是官方控件没有提供的

这种情况很普遍,比如下拉刷新吧,官方只是提供了一个默认的转圈圈,奇丑无比,当然入不了设计师的眼了,设计师将设计书一改,自定义View的需求就来了。虽然有很多的第三方大牛已经做好了各种各样的效果,但是我们也要了解其中的实现方式,万一效果有差池才能修改。

2.为了提升页面性能

多层的View嵌套是很影响渲染性能的,因为会增加测量的次数

3.出于代码规范性的考虑

将一些常用可复用的效果抽离成自定义View,方便进行复用和统一维护

自定义View的核心方法

自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw();View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。其中自定义View:只需要重写onMeasure()和onDraw(),自定义ViewGroup则只需要重写onMeasure()和onLayout()

  • 测量:onMeasure()决定View的大小;

  • 布局:onLayout()决定View在ViewGroup中的位置;

  • 绘制:onDraw()决定绘制这个View。

AttributeSet与自定义属性

系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:通过

为自定义View添加属性在xml中为相应的属性声明属性值在运行时(一般为构造函数)获取属性值将获取到的属性值应用到View

View视图结构

PhoneWindow是Android系统中最基本的窗口系统,继承自Windows类,负责管理界面显示以及事件响应。它是Activity与View系统交互的接口DecorView是PhoneWindow中的起始节点View,继承于View类,作为整个视图容器来使用。用于设置窗口属性。它本质上是一个FrameLayoutViewRoot在Activtiy启动时创建,负责管理、布局、渲染窗口UI等等

自定义View基础

对于多View的视图,结构是树形结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View,如下图:

自定义View基础

注意:无论是measure过程、layout过程还是draw过程,永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),最终计算整个View树中各个View,最终确定整个View树的相关属性。为什么不从子View开始计算呢,因为子View个数太多,处理的顺序不好弄,而且子View若为wrap_content或者match_parent的情况下,就会出现不知道onMeasure时该View应该显示多大了。

Android坐标系

Android的坐标系定义为:

  • 屏幕的左上角为坐标原点

  • 向右为x轴增大方向

  • 向下为y轴增大方向

自定义View基础

自定义View基础

getY代表触摸点到控件上边缘的距离。getRawY上图有点问题,将就着看看,getRawY应该是从X轴到触摸点的距离,也就是getRawY那条白线还要再往上面延伸一些。

View树的绘制流程

View树的绘制流程是谁负责的?

view树的绘制流程是通过ViewRootImpl去负责绘制的,它的主要作用是View树的管理者,负责将DecorView和PhoneWindow“组合”起来,而View树的根节点严格意义上来说只有DecorView;每个DecorView都有一个ViewRoot与之关联,这种关联关系是由WindowManager去进行管理的;

自定义View基础

从上图可以看到,从添加View以后就一直在ViewRootImpl中做处理,最终是执行到了performTraversals方法,这个方式非常核心,它负责调用onMeasure、onLayout、onDraw这几个核心方法,是一条主线。

自定义View基础

那么Android系统为什么要有onMeasure呢?

因为Android支持非精确的大小,具体包括warp_content和match_parent,所以在显示的时候并不能只能确定出自己的实际大小,而是需要根据父控件给出的限制以及自身的属性设置来决定,所以我们需要复写onMeasure来设定自己想要的大小策略。

说到父控件给出的限制,这里想要重点讲一下,其实就是MeasureSpec,在复写onMeasure的时候系统给我们回调的参数。其中包括widthMeasureSpec和heightMeasureSpec,也就是父控件分别给的宽高限制,限制包括父容器给当前控件的测量模式,以及父容器具体的大小(当然只有确定了的时候才能给)

自定义View基础

在父容器给的MeasureSpec中,总共有32位,最高的2位存储着父容器的测量模式,其余的低30位存储着父容器具体的大小,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里

自定义View基础

在子View复写的onMeasure中,需要根据父容器给的MeasureSpec以及该子View使用时设置的宽高属性来得出该View的最终宽高。这里最好能覆盖自定能设置的所有的情况,假设只能设置固定宽高,就不能算是一个合格的自定义View了

自定义View基础

系统得出该View的具体策略如下图所示,横向代表父容器给予的测量模式,纵向代表字View自身设置的宽高属性,其中Unspecified代表不知道自己应该有多大,也就是说父容器也是设置的wrap_content的情况,这种情况优先子View确定宽高,如果子View是精确值,则直接确定为具体宽高,否则也不确定

自定义View基础

上面说的可能有些抽象,接下来就以自定义ViewGroup为例,说明一下测量过程。在自定义ViewGroup时,具体流程如下:

1.测量ViewGroup自身的尺寸

需要调用super.onMeasure来测量自身的尺寸

2.为每个子View计算测量的限制信息

获取到第一步测量好的信息,包括测量模式、测量出来的数据

3.将上一步获得到的限制信息传递给该ViewGroup的每一个子View,好让子View能做自己的onMeasure测量

4.获取子View测量完成后的尺寸

5.计算ViewGroup自身的尺寸

由于当前ViewGroup的尺寸和子View有关,所以必须等子View的尺寸获取到以后再决定自身的尺寸,这里使用switch是考虑到当前ViewGroup的xml中定义的widh和height有三种情况

6.保存ViewGroup自身的尺寸作为本次onMeasure结果,本次测量结束

自定义View基础

自定义View基础

onLayout方法

onMeasure方法确定了当前ViewGroup的大小后,onlayout方法就简单了,按照自己的想法调用每个子View的layout方法决定好子View的位置即可,注意位置是相对当前控件的相对位置

自定义View基础


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical Vim, Second Edition

Practical Vim, Second Edition

Drew Neil / The Pragmatic Bookshelf / 2015-10-31 / USD 29.00

Vim is a fast and efficient text editor that will make you a faster and more efficient developer. It’s available on almost every OS, and if you master the techniques in this book, you’ll never need an......一起来看看 《Practical Vim, Second Edition》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试