自定义View(二)、自定义View的分类及流程
原文 GcsSloop大神的自定义View系列
在这个基础上简单整理学习。
一、前言,
上一篇对View的坐标位置等一些基础概念进行了介绍,这篇开始对自定义View的流程进行分析,后面再通过一个简单的实战来巩固。
首先看这么一个图,基本概述了整个自定view的流程:

二、View的分类
视图View主要分为两类
类别 | 解释 | 特点 |
---|---|---|
单个View | 即一个View,如TextView | 不包含子View |
多个View | 即多个View组成的ViewGroup,如LinearLayout | 包含子View |
2.1、自定义ViewGroup
自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。
例如:应用底部导航条中的条目,一般都是上面图标(ImageView),下面文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。

2.2、自定义View
在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。
例如:制作一个支持自动加载网络图片的ImageView,制作图表等。

三、流程详细介绍
上面给了总的自定义View流程图,下面会对每个流程进行介绍,部分重要的流程也会在后面的文章中重点介绍。
3.1、构造函数

在讲构造函数之前,我们先看View类
View类简介
- View类是Android中各种组件的基类,如View是ViewGroup基类
- View表现为显示在屏幕上的各种视图
我们在自定义View或者自定义ViewGroup的时候,需要继承View
或者ViewGroup
,一般要重写四个构造方法,比如我这里定义了MyViewGroup
继承至ViewGroup
,重写的构造方法如下:

- 当我们直接New一个对象的时候,就会调第一个构造方法A,如下:
1MyViewGroup myViewGroup = new MyViewGroup(this);
- 当我们在Xml文件里面引用的时候,就会回调第二个构造方法B,如下:
1<com.evan.androidnote.ui.MyViewGroup
2 android:layout_width="match_parent"
3 android:layout_height="match_parent" />
3.2、AttributeSet与自定义属性
在第二个构造函数里面我们看到了AttributeSet
这个参数,这里简单对这个参数介绍一下,后面会有文章具体讲到。
1<TextView
2 android:layout_width="match_parent"
3 android:layout_height="wrap_content"
4 android:text="EvanZch"
5 android:textColor="@color/colorPrimary"
6 android:textSize="20sp" />
我们定义一个最简单的TextView
,我们可以看到,里面有设置了各种配置属性,比如文本内容设置使用android:text
,字体大小设置使用android:textSize
这个就是AttributeSet
那我们怎么自定义属性呢?一般要遵循下面四个步骤:
1、在
attar.xml
中定义declare-styleable
这里简单了解以下,具体format
这些属性的含义后面会有文章单独介绍。2、在布局文件中使用:

- 3、定义好属性后,我们就可以通过上面的构造函数拿到我们设置的值。
1public MyViewGroup(Context context, AttributeSet attrs) {
2 super(context, attrs);
3 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyViewGroupStyle);
4 int color = array.getColor(R.styleable.MyViewGroupStyle_color, Color.RED);
5 String text = array.getString(R.styleable.MyViewGroupStyle_text);
6 LogUtil.d(TAG + "--MyViewGroup color=" + color + ",text=" + text);
7}
打印结果:

- 4、获取到具体的属性值后,在后面的应用中,就可以直接用在View上了,根据具体的逻辑来实现。
四、onMeasure()
测量View大小

onMeasure()
方法用来测量View的大小,我们为什么还要测量View的大小呢?
因为View的大小不仅由自身确定,也会受到父控件的影响,为了我们自定义的View能更好的展示,我们一般需要自己来进行测量,根据测量结果合理的定义View大小。
测量View的大小需要重写onMeasure()
方法。
1@Override
2protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
4 // 获取宽度的测量模式
5 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6 // 获取宽度具体数值
7 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8
9 // 获取高度的测量模式
10 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
11 // 获取高度的具体数值
12 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
13}
可以看到在onMeasure()
方法中有widthMeasureSpec
和heightMeasureSpec
两个参数,看名字肯定跟宽高有关系,但是他们并不是表示宽高具体的值,要获取到具体的值需要使用MeasureSpec
这个类来获取,我们看到对应宽高,MeasureSpec
分别通过getMode
和getSize
两个方法来获取具体的数值,getSize
可以知道是获取具体的值,那getMode
又是干啥的?
- int getMode(int measureSpec)
当我们想了解这个方法,最好的办法是点进去,先看官方介绍。
1/**
2 * Extracts the mode from the supplied measure specification.
3 *
4 * @param measureSpec the measure specification to extract the mode from
5 * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
6 * {@link android.view.View.MeasureSpec#AT_MOST} or
7 * {@link android.view.View.MeasureSpec#EXACTLY}
8 */
9@MeasureSpecMode
10public static int getMode(int measureSpec) {
11 //noinspection ResourceType
12 return (measureSpec & MODE_MASK);
13}
通过传入的widthMeasureSpec
和heightMeasureSpec
获取对应的模式,那模式又是啥?我们看到返回值有UNSPECIFIED
、AT_MOST
、EXACTLY
三种,它们大致区别如下,可以先大概了解,后面后面也会具体介绍。
模式 | 描述 |
---|---|
UNSPECIFIED | 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。 |
EXACTLY | 表示父控件已经确切的指定了子View的大小。 |
AT_MOST | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。 |
注意
如果在onMeasure()
方法中我们对View的宽高进行了修改,就不需要在调用super.onMeasure(widthMeasureSpec, heightMeasureSpec)
这个方法,直接通过setMeasuredDimension(widthSize, heightSize)
来设置。
五、onSizeChanged()
确定View大小

这个方法只有在视图发生改变的时候才会回调,通过方法名字我们也能看出来。
我们在前面已经通过onMeasure()
方法测量了View的大小,然后通过setMeasuredDimension(widthSize, heightSize)
来进行设置, 为什么还要再次确认View的大小?
原因是View的大小一是要由自身控制,而且受父控件的影响,所以,我们最好在onSizeChanged()
方法中确定View的具体大小
onSizeChanged
1/**
2 * w : View的宽
3 * h : View的高
4 * oldw : View上一次的宽
5 * oldh : View上一次的高
6 */
7@Override
8protected void onSizeChanged(int w, int h, int oldw, int oldh) {
9 super.onSizeChanged(w, h, oldw, oldh);
10 LogUtil.d(TAG + "--onSizeChanged w=" + w + ",h=" + h);
11}
我们直接通过方法中的w和h就可以拿到对应的宽高。
六、onLayout()
- 确定子View布局
确定子View的布局使用onLayout()
方法,用来确定子View的View,通常在自定义ViewGroup中会用上,调用的也是各个子View的layout
方法
在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
1view.layout(l, t, r, b);
四个参数分别为:
名称 | 说明 | 对应的函数 |
---|---|---|
l | View左侧距父View左侧的距离 | getLeft(); |
t | View顶部距父View顶部的距离 | getTop(); |
r | View右侧距父View左侧的距离 | getRight(); |
b | View底部距父View顶部的距离 | getBottom(); |
图示如下:

七、onDrow()
我们自定义View真正绘制的地方,所有的绘制都在这个方法下进行,后面会重点介绍。
1@Override
2protected void onDraw(Canvas canvas) {
3 super.onDraw(canvas);
4}
- 参数 Canvas : "画布"
- 后续还会提到 Path : "画笔"
通过调用各种方法,能实现在画布上画出各种想要的View。
八、自定义ViewGroup实战
自定义ViewGroup,实现效果图如下:

源码地址,注释基本都写了。
总结
本篇对自定义View的整体流程大概走了一遍,对几个重要的方法简单进行了分析,这些方法在后面的自定义View的都会用上,到时候会再详细的介绍。