Android自定义控件Android开发Android进阶之路

自定义 View - 基础

2017-12-11  本文已影响54人  Arnold_J

虽然安卓系统自带了许多功能丰富,种类多样的控件,但是要想完全实现各个场景中,UI 给出的炫酷效果,这些控件却是远远不够的。为此,自定义 View 成了每个工程师的必备技能,今天先主要看基础内容。


一、坐标系

1.屏幕坐标系

下图,画得不是很好看。。

效果图.png

2.子 View 坐标系

获取子 View 位置的方法的返回值都是相对于父容器的,相关方法如下。

getTop()        //获取 view 上边缘到父容器上端的距离
getBottom()     //获取 view 下边缘到父容器上端的距离
getLeft()       //获取 view 左边缘到父容器左端的距离
getRight()      //获取 view 右边缘到父容器左端的距离

下面以最常见的例子做图解,其中白色部分为父容器,蓝色部分为子 View。需要注意的是,由于 View 的测量方法在 onCreate 之前是不会调用的,所以上面的方法,如果在 onCreate 中调用,返回的都是

效果图-g.png

二、自定义 View 的分类

在我看来,只要对于原生的控件部分功能进行了改动(不管是测量方式的改变,暴露出数据接口,亦或是整体的自我绘制等等),就是工程师有意识的在做适合当前工程的自定义。
总体来说,自定义 View 主要可以分成这样几个种类:

以上三种,一二比较容易,可以很快上手,而第三种,在图形绘制测量和事件分发上,需要一定的理解。之后应该都会做 demo 进行练习。

三、View 的绘制流程

简单地利用 Android Studio 画了一个图。


Studio Test.png

不过上面的图,并不十分准确。在视图变化的时候,有两种可能,上面的箭头指向重新调用 onDraw() 方法,而另一种情况并非如此。
我们一共有两种方法来通知 View 树刷新视图。

其中调用 invalidate 会使 View 重新绘制,而调用 requestLayout 会使 View 重新测量。

四、自定义 View 构造器

如果现在开始着手创建一个 View 的子类,我们会发现它一共有四个构造方法:


效果图-g.png

这里最常用的是第一个和第二个构造方法,以下为两个构造器的参数和基本说明。

//一个参数的构造器在代码中动态构建的时候会被调用
public TView(Context context) {
    super(context);
}

//两个参数的构造器在初始化 xml 中的控件时会被调用
public TView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

我们以 ImageView 的构造器为例来看看自定义 View 的构造器需要做哪些事情:

public ImageView(Context context) {
    super(context);
    initImageView();
}

public ImageView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
        int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    initImageView();
    
    //...初始化布局属性
}

从上面我们可以看到,无论调用哪个构造器,都会先调用 initImageView() 方法,通常版本适配和默认属性赋值会在这里进行。后面两个参数、三个参数的构造器实际上调用的都是四个参数的构造器,这里除了会调用 initImageView() 方法外,还会利用 attrs 获取到 xml 中的属性,方便之后图形绘制和测量时更准确地表达我们的意图。

五、测量自定义 View 的大小

上面第三点的绘制流程中,我们可以看到,在调用构造器后,就到了测量方法 --- onMeasure()

onMeasure 方法给我们提供了两个参数:widthMeasureSpec heightMeasureSpec 。在了解这两个参数之前,我们需要先了解 MeasureSpec

MeasureSpec 是 View 的内部类,它把 32 位的整型数据,分为两部分,前两位用于存储 View 的填充类型(match_parent | wrap_content),后面 30 位用于存储数值大小。存储的类型和数值可以利用掩码和移位拆分,这里我们通常用 MeasureSpec.getMode 和 MeasureSpec.getSize 方法拆分获取。

文字说的总是有限,为了表示得更清晰一点,我画了一个图表,其中 ModeSize 应该是连在一起的一个数值,为方便观察才分在两个格子中。

Mode Size 备注
10 000000000000000000000000000001 val=2^31+1 ,[mode]-[wrap_content]-[MeasureSpec.AT_MOST]
01 000000000000000000000000000001 val=2^30+1 ,[mode]-[match_parent]-[MeasureSpec.EXACTLY]

对于 onMeasure 中的两个参数,我们可以通过下面的方法分别获取 ModeSize

int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
int specHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int specHeightSize = MeasureSpec.getSize(heightMeasureSpec);

注意:


感谢 :GcsSloop 自定义 View 系列

谢谢观赏
上一篇下一篇

猜你喜欢

热点阅读