原文 GcsSloop大神的自定义View系列
在这个基础上简单整理学习。java
上一篇对View的坐标位置等一些基础概念进行了介绍,这篇开始对自定义View的流程进行分析,后面再经过一个简单的实战来巩固。android
首先看这么一个图,基本概述了整个自定view的流程:git
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKPhXzf3-1585814037311)(https://evanzch.oss-cn-beijing.aliyuncs.com/img/20200318193000.png)]github
视图View主要分为两类web
类别 | 解释 | 特色 |
---|---|---|
单个View | 即一个View,如TextView | 不包含子View |
多个View | 即多个View组成的ViewGroup,如LinearLayout | 包含子View |
自定义ViewGroup通常是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各类Layout,包含有子View。canvas
例如:应用底部导航条中的条目,通常都是上面图标(ImageView),下面文字(TextView),那么这两个就能够用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。c#
在没有现成的View,须要本身实现的时候,就使用自定义View,通常继承自View,SurfaceView或其余的View,不包含子View。网络
例如:制做一个支持自动加载网络图片的ImageView,制做图表等。app
上面给了总的自定义View流程图,下面会对每一个流程进行介绍,部分重要的流程也会在后面的文章中重点介绍。ide
在讲构造函数以前,咱们先看View类
View类简介
咱们在自定义View或者自定义ViewGroup的时候,须要继承View
或者ViewGroup
,通常要重写四个构造方法,好比我这里定义了MyViewGroup
继承至ViewGroup
,重写的构造方法以下:
MyViewGroup myViewGroup = new MyViewGroup(this);
<com.evan.androidnote.ui.MyViewGroup android:layout_width="match_parent" android:layout_height="match_parent" />
在第二个构造函数里面咱们看到了AttributeSet
这个参数,这里简单对这个参数介绍一下,后面会有文章具体讲到。
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="EvanZch" android:textColor="@color/colorPrimary" android:textSize="20sp" />
咱们定义一个最简单的TextView
,咱们能够看到,里面有设置了各类配置属性,好比文本内容设置使用android:text
,字体大小设置使用android:textSize
这个就是AttributeSet
那咱们怎么自定义属性呢?通常要遵循下面四个步骤:
一、在attar.xml
中定义declare-styleable
这里简单了解如下,具体format
这些属性的含义后面会有文章单独介绍。
二、在布局文件中使用:
public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyViewGroupStyle); int color = array.getColor(R.styleable.MyViewGroupStyle_color, Color.RED); String text = array.getString(R.styleable.MyViewGroupStyle_text); LogUtil.d(TAG + "--MyViewGroup color=" + color + ",text=" + text); }
打印结果:
测量View大小
onMeasure()
方法用来测量View的大小,咱们为何还要测量View的大小呢?
由于View的大小不只由自身肯定,也会受到父控件的影响,为了咱们自定义的View能更好的展现,咱们通常须要本身来进行测量,根据测量结果合理的定义View大小。
测量View的大小须要重写onMeasure()
方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 获取宽度的测量模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); // 获取宽度具体数值 int widthSize = MeasureSpec.getSize(widthMeasureSpec); // 获取高度的测量模式 int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 获取高度的具体数值 int heightSize = MeasureSpec.getSize(heightMeasureSpec); }
能够看到在onMeasure()
方法中有widthMeasureSpec
和heightMeasureSpec
两个参数,看名字确定跟宽高有关系,可是他们并非表示宽高具体的值,要获取到具体的值须要使用MeasureSpec
这个类来获取,咱们看到对应宽高,MeasureSpec
分别经过getMode
和getSize
两个方法来获取具体的数值,getSize
能够知道是获取具体的值,那getMode
又是干啥的?
/** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); }
经过传入的widthMeasureSpec
和heightMeasureSpec
获取对应的模式,那模式又是啥?咱们看到返回值有UNSPECIFIED
、AT_MOST
、EXACTLY
三种,它们大体区别以下,能够先大概了解,后面后面也会具体介绍。
模式 | 描述 |
---|---|
UNSPECIFIED | 默认值,父控件没有给子view任何限制,子View能够设置为任意大小。 |
EXACTLY | 表示父控件已经确切的指定了子View的大小。 |
AT_MOST | 表示子View具体大小没有尺寸限制,可是存在上限,上限通常为父View大小。 |
注意
若是在onMeasure()
方法中咱们对View的宽高进行了修改,就不须要在调用super.onMeasure(widthMeasureSpec, heightMeasureSpec)
这个方法,直接经过setMeasuredDimension(widthSize, heightSize)
来设置。
肯定View大小
这个方法只有在视图发生改变的时候才会回调,经过方法名字咱们也能看出来。
咱们在前面已经经过onMeasure()
方法测量了View的大小,而后经过setMeasuredDimension(widthSize, heightSize)
来进行设置, 为何还要再次确认View的大小?
缘由是View的大小一是要由自身控制,并且受父控件的影响,因此,咱们最好在onSizeChanged()
方法中肯定View的具体大小
onSizeChanged
/** * w : View的宽 * h : View的高 * oldw : View上一次的宽 * oldh : View上一次的高 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LogUtil.d(TAG + "--onSizeChanged w=" + w + ",h=" + h); }
咱们直接经过方法中的w和h就能够拿到对应的宽高。
肯定子View的布局使用onLayout()
方法,用来肯定子View的View,一般在自定义ViewGroup中会用上,调用的也是各个子View的layout
方法
在自定义ViewGroup中,onLayout通常是循环取出子View,而后通过计算得出各个子View位置的坐标值,而后用如下函数设置子View位置。
view.layout(l, t, r, b);
四个参数分别为:
名称 | 说明 | 对应的函数 |
---|---|---|
l | View左侧距父View左侧的距离 | getLeft(); |
t | View顶部距父View顶部的距离 | getTop(); |
r | View右侧距父View左侧的距离 | getRight(); |
b | View底部距父View顶部的距离 | getBottom(); |
图示以下:
咱们自定义View真正绘制的地方,全部的绘制都在这个方法下进行,后面会重点介绍。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }
经过调用各类方法,能实如今画布上画出各类想要的View。
自定义ViewGroup,实现效果图以下:
源码地址,注释基本都写了。
本篇对自定义View的总体流程大概走了一遍,对几个重要的方法简单进行了分析,这些方法在后面的自定义View的都会用上,到时候会再详细的介绍。