自定义View(二)、自定义View的分类及流程

自定义View(二)、自定义View的分类及流程

原文 GcsSloop大神的自定义View系列
在这个基础上简单整理学习。java

1、前言,

上一篇对View的坐标位置等一些基础概念进行了介绍,这篇开始对自定义View的流程进行分析,后面再经过一个简单的实战来巩固。android

首先看这么一个图,基本概述了整个自定view的流程:git

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKPhXzf3-1585814037311)(https://evanzch.oss-cn-beijing.aliyuncs.com/img/20200318193000.png)]github

2、View的分类

视图View主要分为两类web

类别 解释 特色
单个View 即一个View,如TextView 不包含子View
多个View 即多个View组成的ViewGroup,如LinearLayout 包含子View

2.一、自定义ViewGroup

自定义ViewGroup通常是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各类Layout,包含有子View。canvas

例如:应用底部导航条中的条目,通常都是上面图标(ImageView),下面文字(TextView),那么这两个就能够用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。c#

2.二、自定义View

在没有现成的View,须要本身实现的时候,就使用自定义View,通常继承自View,SurfaceView或其余的View,不包含子View。网络

例如:制做一个支持自动加载网络图片的ImageView,制做图表等。app

3、流程详细介绍

上面给了总的自定义View流程图,下面会对每一个流程进行介绍,部分重要的流程也会在后面的文章中重点介绍。ide

3.一、构造函数

在讲构造函数以前,咱们先看View类

View类简介

  • View类是Android中各类组件的基类,如View是ViewGroup基类
  • View表现为显示在屏幕上的各类视图

咱们在自定义View或者自定义ViewGroup的时候,须要继承View或者ViewGroup,通常要重写四个构造方法,好比我这里定义了MyViewGroup继承至ViewGroup,重写的构造方法以下:

  • 当咱们直接New一个对象的时候,就会调第一个构造方法A,以下:
MyViewGroup myViewGroup = new MyViewGroup(this);
  • 当咱们在Xml文件里面引用的时候,就会回调第二个构造方法B,以下:
<com.evan.androidnote.ui.MyViewGroup
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3.二、AttributeSet与自定义属性

在第二个构造函数里面咱们看到了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上了,根据具体的逻辑来实现。

4、onMeasure()

测量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()方法中有widthMeasureSpecheightMeasureSpec两个参数,看名字确定跟宽高有关系,可是他们并非表示宽高具体的值,要获取到具体的值须要使用MeasureSpec这个类来获取,咱们看到对应宽高,MeasureSpec分别经过getModegetSize两个方法来获取具体的数值,getSize能够知道是获取具体的值,那getMode又是干啥的?

  • int getMode(int measureSpec)
    当咱们想了解这个方法,最好的办法是点进去,先看官方介绍。
/** * 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);
}

经过传入的widthMeasureSpecheightMeasureSpec获取对应的模式,那模式又是啥?咱们看到返回值有UNSPECIFIEDAT_MOSTEXACTLY三种,它们大体区别以下,能够先大概了解,后面后面也会具体介绍。

模式 描述
UNSPECIFIED 默认值,父控件没有给子view任何限制,子View能够设置为任意大小。
EXACTLY 表示父控件已经确切的指定了子View的大小。
AT_MOST 表示子View具体大小没有尺寸限制,可是存在上限,上限通常为父View大小。

注意
若是在onMeasure()方法中咱们对View的宽高进行了修改,就不须要在调用super.onMeasure(widthMeasureSpec, heightMeasureSpec)这个方法,直接经过setMeasuredDimension(widthSize, heightSize)来设置。

5、onSizeChanged()

肯定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就能够拿到对应的宽高。

6、onLayout()

  • 肯定子View布局

肯定子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();

图示以下:

7、onDrow()

咱们自定义View真正绘制的地方,全部的绘制都在这个方法下进行,后面会重点介绍。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}
  • 参数 Canvas : “画布”
  • 后续还会提到 Path : “画笔”

经过调用各类方法,能实如今画布上画出各类想要的View。

8、自定义ViewGroup实战

自定义ViewGroup,实现效果图以下:

源码地址,注释基本都写了。

总结

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