android minheight的作用,Android 自定义 View 最少必要知识

1. 什么是自定义 View?

1.1 定义

在 Android 系统中,界面中所有能看到的元素都是 View。默认情况下,Android 系统为开发者提供了很多 View,比如用于展示文本信息的 TextView,用于展示图片的 ImageView 等等。但有时,这并不能满足开发者的需求,例如,开发者想要用一个饼状图来展示一组数据,这时如果用系统提供的 View 就不能实现了,只能通过自定义 View 来实现。那到底什么是自定义 View 呢?

自定义 View 就是通过继承 View 或者 View 的子类,并在新的类里面实现相应的处理逻辑(重写相应的方法),以达到自己想要的效果。

1.2 继承结构

Android 中的所有 UI 元素都是 View 的子类:

e8f9ed87b725

image

PS:由于涉及的类太多,如果将所有涉及到的类全部加到类图里面,类图将十分大,所以此处只列出了 View 的直接子类。

1.3 视图体系用到的设计模式

Android View 体系如下:

e8f9ed87b725

image

仔细观察,你会发现,Android View 的体系结构和设计模式中的组合模式的结构如出一辙:

e8f9ed87b725

image

Android View 体系结构中的 ViewGroup 对应于组合模式中抽象构件(Component 和 Composite),Android View 体系结构中的 View 对应于组合模式中的叶子构件(Leaf):

Android View 构件

Composite Pattern 构件

ViewGroup

Component、Composite

View

Leaf

2. 为什么要自定义 View?

大多数情况下,开发者常常会因为下面四个原因去自定义 View:

让界面有特定的显示风格、效果;

让控件具有特殊的交互方式;

优化布局;

封装;

2.1 让界面有特定的显示风格、效果

默认情况下,Android 系统为开发者提供了很多控件,但有时,这并不能满足开发者的需求。例如,开发者想要用一个饼状图来展示一组数据,这时如果用系统提供的 View 就不能实现了,只能通过自定义 View 来实现。

If none of the prebuilt widgets or layouts meets your needs, you can create your own View subclass.

2.2 让控件具有特殊的交互方式

默认情况下,Android 系统为开发者提供的控件都有属于它们自己的特定的交互方式,但有时,控件的默认交互方式并不能满足开发者的需求。例如,开发者想要缩放 ImageView 中的图片内容,这时如果用系统提供的 ImageView 就不能实现了,只能通过自定义 ImageView 来实现。

2.3 优化布局

有时,有些布局如果用系统提供的控件实现起来相当复杂,需要各种嵌套,虽然最终也能实现了想要的效果,但性能极差,此时就可以通过自定义 View 来减少嵌套层级、优化布局。

2.4 封装

有些控件可能在多个地方使用,如大多数 App 里面的底部 Tab,像这样的经常被用到的控件就可以通过自定义 View 将它们封装起来,以便在多个地方使用。

3. 如何自定义 View?

在说「如何自定义 View?」之前,我们需要知道「自定义 View 都包括哪些内容」?

自定义 View 包括三部分内容:

布局(Layout)

绘制(Drawing)

触摸反馈(Event Handling)

布局阶段:确定 View 的位置和尺寸。

绘制阶段:绘制 View 的内容。

触摸反馈:确定用户点击了哪里。

其中布局阶段包括测量(measure)和布局(layout)两个过程,另外,布局阶段是为绘制和触摸反馈阶段做支持的,它并没有什么直接作用。正是因为在布局阶段确定了 View 的尺寸和位置,绘制阶段才知道往哪里绘制,触摸反馈阶段才知道用户点的是哪里。

另外,由于触摸反馈是一个大的话题,限于篇幅,就不在这里讲解了,后面有机会的话,我会再补上一篇关于触摸反馈的文章。

在自定义 View 和自定义 ViewGroup 中,布局和绘制流程虽然整体上都是一样的,但在细节方面,自定义 View 和自定义 ViewGroup 还是不一样的,所以,接下来分两类进行讨论:

自定义 View 布局、绘制流程

自定义 ViewGroup 布局、绘制流程

3.1 自定义 View 布局、绘制流程

「自定义 View 布局、绘制」主要包括三个阶段:

测量阶段(measure)

布局阶段(layout)

绘制阶段(draw)

3.1.1 自定义 View 测量阶段

在 View 的测量阶段会执行两个方法(在测量阶段,View 的父 View 会通过调用 View 的 measure() 方法将父 View 对 View 尺寸要求传进来。紧接着 View 的 measure() 方法会做一些前置和优化工作,然后调用 View 的 onMeasure() 方法,并通过 onMeasure() 方法将父 View 对 View 的尺寸要求传入。在自定义 View 中,只有需要修改 View 的尺寸的时候才需要重写 onMeasure() 方法。在 onMeasure() 方法中根据业务需求进行相应的逻辑处理,并在最后通过调用 setMeasuredDimension() 方法告知父 View 自己的期望尺寸):

measure()

onMeasure()

measure() : 调度方法,主要做一些前置和优化工作,并最终会调用 onMeasure() 方法执行实际的测量工作;

onMeasure() : 实际执行测量任务的方法,主要用与测量 View 尺寸和位置。在自定义 View 的 onMeasure() 方法中,View 根据自己的特性和父 View 对自己的尺寸要求算出自己的期望尺寸,并通过 setMeasuredDimension() 方法告知父 View 自己的期望尺寸。

onMeasure() 计算 View 期望尺寸方法如下:

参考父 View 的对 View 的尺寸要求和实际业务需求计算出 View 的期望尺寸:

解析 widthMeasureSpec;

解析 heightMeasureSpec;

将「根据实际业务需求计算出 View 的尺寸」根据「父 View 的对 View 的尺寸要求」进行相应的修正得出 View 的期望尺寸(通过调用 resolveSize() 方法);

通过 setMeasuredDimension() 保存 View 的期望尺寸(实际上是通过 setMeasuredDimension() 告知父 View 自己的期望尺寸);

注意:

多数情况下,这里的期望尺寸就是 View 的最终尺寸。不过最终 View 的期望尺寸和实际尺寸是不是一样还要看它的父 View 会不会同意。View 的父 View 最终会通过调用 View 的 layout() 方法告知 View 的实际尺寸,并且在 layout() 方法中 View 需要将这个实际尺寸保存下来,以便绘制阶段和触摸反馈阶段使用,这也是 View 需要在 layout() 方法中保存自己实际尺寸的原因——因为绘制阶段和触摸反馈阶段要使用啊!

3.1.2 自定义 View 布局阶段

在 View 的布局阶段会执行两个方法(在布局阶段,View 的父 View 会通过调用 View 的 layout() 方法将 View 的实际尺寸(父 View 根据 View 的期望尺寸确定的 View 的实际尺寸)传给 View,View 需要在 layout() 方法中将自己的实际尺寸保存(通过调用 View 的 setFrame() 方法保存,在 setFrame() 方法中,又会通过调用 onSizeChanged() 方法告知开发者 View 的尺寸修改了)以便在绘制和触摸反馈阶段使用。保存 View 的实际尺寸之后,View 的 layout() 方法又会调用 View 的 onLayout() 方法,不过 View 的 onLayout() 方法是一个空实现,因为它没有子 View):

layout()

onLayout()

layout() : 保存 View 的实际尺寸。调用 setFrame() 方法保存 View 的实际尺寸,调用 onSizeChanged() 通知开发者 View 的尺寸更改了,并最终会调用 onLayout() 方法让子 View 布局(如果有子 View 的话。因为自定义 View 中没有子 View,所以自定义 View 的 onLayout() 方法是一个空实现);

onLayout() : 空实现,什么也不做,因为它没有子 View。如果是 ViewGroup 的话,在 onLayout() 方法中需要调用子 View 的 layout() 方法,将子 View 的实际尺寸传给它们,让子 View 保存自己的实际尺寸。因此,在自定义 View 中,不需重写此方法,在自定义 ViewGroup 中,需重写此方法。

注意:

layout() & onLayout() 并不是「调度」与「实际做事」的关系,layout() 和 onLayout() 均做事,只不过职责不同。

3.1.3 自定义 View 绘制阶段

在 View 的绘制阶段会执行一个方法——draw(),draw() 是绘制阶段的总调度方法,在其中会调用绘制背景的方法 drawBackground()、绘制主体的方法 onDraw()、绘制子 View 的方法 dispatchDraw() 和 绘制前景的方法 onDrawForeground():

draw()

draw() : 绘制阶段的总调度方法,在其中会调用绘制背景的方法 drawBackground()、绘制主体的方法 onDraw()、绘制子 View 的方法 dispatchDraw() 和 绘制前景的方法 onDrawForeground();

drawBackground() : 绘制背景的方法,不能重写,只能通过 xml 布局文件或者 setBackground() 来设置或修改背景;

onDraw() : 绘制 View 主体内容的方法,通常情况下,在自定义 View 的时候,只用实现该方法即可;

dispatchDraw() : 绘制子 View 的方法。同 onLayout() 方法一样,在自定义 View 中它是空实现,什么也不做。但在自定义 ViewGroup 中,它会调用 ViewGroup.drawChild() 方法,在 ViewGroup.drawChild() 方法中又会调用每一个子 View 的 View.draw() 让子 View 进行自我绘制;

onDrawForeground() : 绘制 View 前景的方法,也就是说,想要在主体内容之上绘制东西的时候就可以在该方法中实现。

注意:

Android 里面的绘制都是按顺序的,先绘制的内容会被后绘制的盖住。如,你在重叠的位置「先画圆再画方」和「先画方再画圆」所呈现出来的结果是不同的,具体表现为下表:

e8f9ed87b725

image

e8f9ed87b725

image

3.1.4 自定义 View 布局、绘制流程时序图

e8f9ed87b725

image

3.2 自定义 ViewGroup 布局、绘制流程

「自定义 ViewGroup 布局、绘制」主要包括三个阶段:

测量阶段(measure)

布局阶段(layout)

绘制阶段(draw)

3.2.1 自定义 ViewGroup 测量阶段

同自定义 View 一样,在自定义 ViewGroup 的测量阶段会执行两个方法:

measure()

onMeasure()

measure() : 调度方法,主要做一些前置和优化工作,并最终会调用 onMeasure() 方法执行实际的测量工作;

onMeasure() : 实际执行测量任务的方法,与自定义 View 不同,在自定义 ViewGroup 的 onMeasure() 方法中,ViewGroup 会递归调用子 View 的 measure() 方法,并通过 measure() 将 ViewGroup 对子 View 的尺寸要求(ViewGroup 会根据开发者对子 View 的尺寸要求、自己的父 View(ViewGroup 的父 View) 对自己的尺寸要求和自己的可用空间计算出自己对子 View 的尺寸要求)传入,对子 View 进行测量,并把测量结果临时保存,以便在布局阶段使用。测量出子 View 的实际尺寸之后,ViewGroup 会根据子 View 的实际尺寸计算出自己的期望尺寸,并通过 setMeasuredDimension() 方法告知父 View(ViewGroup 的父 View) 自己的期

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值