从 0 到 1 Android 自定义 View(二)分类和核心函数

964 阅读7分钟

Android.jpg

一、前言

这篇主要还是介绍一些知识点,包括上一篇的知识点在内,我们都是需要理解,只有这样,才能更好的制作更多酷炫的自定义 View 。当然每一篇文章都会越来越深入,一步一个台阶,慢慢攀登。

二、自定义 View 分类

常见的 Android 自定义 View 主要有两种类型:

组合控件

通过 Android 的基础控件(TextView、ImageView、Button、ProgressBar 等)组合而成,比如下拉刷新、瀑布流控件、带左/右滑功能的控件、视频控件等,这种自定义View的难点在于程序的逻辑处理

完全自定义控件

继承自 View、TextureView 或 SurfaceView ,然后重写核心的回调方法,以View 为例,按需复写其构造、onMeasure、onLayout、onTouchEvent、onDraw、onAttachedToWindow、onDetachedFromWindow 等方法,这种自定义 View 的难点在于程序的设计、效率优化和排版,比如输入法中的手写控件、图文混排控件(现在很多都是通过webview加载网页实现了)、个性化进度条、弹幕显示控件、Markdown控件、IDE代码编辑控件等

注意:

我们需要合理的使用自定义 View ,千万不能滥用,不要动不动就自定义 View ,基础控件能完成的工能,千万别自定义 View,因为基础空间 Android,本身就有性能优化的,自定义 View 的价值在于做到基础控件无法做到的效果,为应用的表现增色;,将公用的交互效果提取成自定义控件,方便复用,减少不必要的重复劳动。

三、自定义 View 核心知识点

这部分主要是介绍自定义 View 的核心知识点,上面提到过,完全自定义 View 通常是继承 View ,TextureView,SurfaceView,所以先来了解下这三者之间的区别所在。

(1) View、SurfaceView、TextureView 的区别

View

普通的 View,与宿主窗口共享同一个绘图表面,UI 在主线程中绘制,在有无硬件加速的情况下都能工作(没有硬件加速的情况下,canvas 的有些方法会失效)

SurfaceView

继承自 View,绘制和显示效率高,因为拥有独立的绘图表面,UI 在一个独立的线程中进行绘制,不会占用主线程的资源。SurfaceView 的使用和普通的 View 不一样,需要结合 SurfaceHodler 一起使用。因为和宿主窗口不是共享同一个绘图表面的原因,对其做动画操作可能会得不到想要的效果

TextureView

继承自 View,与 SurfaceView 相比,TextureView 不会创建一个单独的绘图表面,这使得它可以像一般的 View 一样执行一些变换操作,比如移动、动画等等,但 TextureView 必须在硬件加速开启的窗口中才能正常工作;

(2) 几个重要的函数
最后通过自定义 View 的流程图来了解一下自定义 View 几个重要的函数。


自定义view的流程图.jpg

构造函数

构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性

View的构造函数有四种重载


自定义View的构造函数.png

从上面的图也可以看出,自定 View 的构造函数的参数最多有四个,而且有四个参数的构造函数只能在 API 21 以上使用,因此四个参数的构造函数先不考虑,不过我们也需要了解这四个参数具体代表什么?

Context: View 中随处都会用到
AttributeSet: XML 属性(从 XML inflate 的时候使用)
int defStyleAttr: 应用到 View 的默认风格(定义在主题中)
int defStyleRes: 如果没有使用 defStyleAttr,应用到 View 的默认风格

那么这里就有个问题了,有四种构造函数,我们该怎么选择呢?

比如上面图片显示的自定义的 MyView ,继承 View 对象,如果我们想在普通的代码中新建一个 MyView,可以直接使用一个参数的构造函数,这也是大多数选择使用的。


一个参数的构造函数.png

那么两个参数的构造函数什么时候使用呢?比如有时候在 xml 中添加一个自定义 View ,并且加了一些布局属性,宽高属性以及 margin 属性,这些属性会存放在第二个构造函数的 AttributeSet 参数里。


两个参数的构造函数.png

有三个参数的构造函数比第二个构造函数多了一个 int 型的值,名字叫 defStyleAttr ,从名称上判断,这是一个关于自定义属性的参数。第三个构造函数不会被系统默认调用,而是需要我们自己去显式调用,比如在第二个构造函数里调用调用第三个函数,并将第三个参数设为0 。defStyleAttr 指定的是在Theme style 定义的一个 attr,它的类型是 reference 主要生效在 obtainStyledAttributes 方法里,obtainStyledAttributes 方法有四个参数,第三个参数是 defStyleAttr ,第四个参数是自己指定的一个 style ,当且仅当 defStyleAttr 为 0 或者在 Theme 中找不到 defStyleAttr 指定的属性时,第四个参数才会生效,这些指的都是默认属性,当在 xml 里面定义的,就以在 xml 文件里指定的为准,所以优先级大概是:xml>style>defStyleAttr>defStyleRes>Theme 指定,当defStyleAttr 为 0 时,就跳过 defStyleAttr 指定的 reference ,所以一般用 0 就能满足一些基本开发。

onMeasure(测量 View 大小)

这个函数有什么用呢?将这个问题转化一下,就是问为什么要测量 View 的大小呢?

因为 View 的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量


onMeasure.png

MeasureSpce 的 mode 有三种:EXACTLY, AT_MOST,UNSPECIFIED,除去 UNSPECIFIED 不谈,其他两种 mode:

当父布局是 EXACTLY 时,子控件确定大小或者 match_parent,mode 都是 EXACTLY,子控件是 wrap_content 时,mode 为 AT_MOST;

当父布局是 AT_MOST 时,子控件确定大小,mode 为 EXACTLY,子控件 wrap_content 或者 match_parent 时,mode 为 AT_MOST。

所以在确定控件大小时,需要判断 MeasureSpec 的 mode,不能直接用 MeasureSpec 的 size。在进行一些逻辑处理以后,调用 setMeasureDimension() 方法,将测量得到的宽高传进去供 layout 使用。


onMeasure流程.png

在实际运用之中只需要记住有测量模式有三种,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可。如果对 View 的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要调用 setMeasuredDimension( widthsize, heightsize); 这个函数。

onSizeChanged(确定 View 大小)

那么这个函数又是什么时候调用呢?

这个函数在视图大小发生改变时调用。

那么问题又来了,上面的 onMeasure 函数中不是说对 View 的宽高进行了修改后,要调用 setMeasuredDimension 吗?调用这个方法后,View 的大小基本已经确定了啊,View 的视图大小还会发生变化吗?这是因为 View 的大小不仅仅只是由其本身来决定的,也受它的父控件影响,所以在确定 View 大小的时候最好使用系统提供的 onSizeChanged 回调函数。


onSizeChanged.png

onLayout(确定子 View 布局位置)

确定布局的函数是 onLayout ,它用于确定子 View 的位置,在自定义 ViewGroup 中会用到,他调用的是子 View 的 layout 函数。比如,有时候我们自定义 View 的时候,需要获取 View 的一些信息就需要用到这个函数。当然,如果是单纯的 View 就没与必要重写这个方法,为什么这么说呢?

因为单纯的 View ,不是一个 View 容器,没有子 View ,而 onLayout 方法里主要是具体摆放子View 的位置,水平摆放或者垂直摆放,所以在单纯的自定义 View 是不需要重写 onLayout 方法。不过需要注意的一点是,子 View 的 margin 属性是否生效就要看 parent 是否在自身的 onLayout 方法进行处理,而 View 的 padding 属性是在 onDraw 方法中生效的

onDraw(绘制内容)

重头戏,onDraw,也就是是实际绘制的部分。


onDraw.png

一般自定义控件耗费心思最多的就是这个方法了,需要在这个方法里,用 Paint 在 Canvas 上画出你想要的图案。如果是直接继承的 View,那么在重写 onDraw 的方法是时候完全可以把 super.ondraw(canvas) 删掉,因为它的默认实现是空。