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

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

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

一、前言,

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

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

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

二、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,如下:
MyViewGroup myViewGroup = new MyViewGroup(this);
  • 当我们在Xml文件里面引用的时候,就会回调第二个构造方法B,如下:
<com.evan.androidnote.ui.MyViewGroup
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3.2、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

那我们怎么自定义属性呢?一般要遵循下面四个步骤:

  • 1、在attar.xml中定义declare-styleable

    这里简单了解以下,具体format这些属性的含义后面会有文章单独介绍。

  • 2、在布局文件中使用:

  • 3、定义好属性后,我们就可以通过上面的构造函数拿到我们设置的值。
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);
}

打印结果:

  • 4、获取到具体的属性值后,在后面的应用中,就可以直接用在View上了,根据具体的逻辑来实现。

四、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)来设置。

五、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就可以拿到对应的宽高。

六、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();

图示如下:

七、onDrow()

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

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

通过调用各种方法,能实现在画布上画出各种想要的View。

八、自定义ViewGroup实战

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

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

总结

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章