我们为什么要自定义View()

前言 或许你掌握了 measure的细节 ,layout机制 ,事件传递机制 ,canvas各种API ,但是,你们想过这个问题吗?
这一篇,不仅仅是对一个面试必会题的解析,更是透过这个问题的思考,寻找最佳实践 ,拓展思维角度 ,少走弯路。
三思系列是我最新的学习、总结形式,着重于:问题分析、技术积累、视野拓展,关于三思系列。
关于View系列 View系列旨在通过 对现实问题 的思考,建立完善的 View体系认知,极力建议读者了解一下 我为什么撰写、分享这个系列。
先给出思考这个问题的 脑图 ,文章内容会按照思考过程展开。
我们为什么要自定义View()
文章图片

思考这类问题,为什么要这样干 是最基本,作为三思系列的成员,本篇还将对以下内容点进行展开论述:
? 怎么干 -- How to do
? 是否一定要这样干 -- 适用场景
? 如果不这样干,还可以怎么干 -- Best Practice
? 各种干法的 注意事项
从View体系出现的目的说起
作为 GUI Graphical User Interface,图形用户接口 类型的程序 framework,View体系是其 必不可少 的一部分。参与了两件重要的事情:
? 描述、呈现 界面
? 参与 人机交互

笼统的讲,当 现有 的 View体系内的 控件簇 无法满足合理需求时,可以在遵从 framework 内在 的 规则 、机制 ,进行扩展,以满足需求。
从这个角度看,扩展可以有两个方面:
? 扩展 显示 功能
? 扩展 交互 功能
扩展显示功能
我们知道,这又分为3种:
1. 通过一组控件,共同完成特定的功能。
【我们为什么要自定义View()】2. 扩展布局规则。
3. 扩展内容显示。
最简单的,一组控件完成特定功能
举个例子: 输入框 右侧加一个 模态的图片,输入框有内容时显示,无内容时隐藏。图片显示一个?,点击时清除输入框的内容。
经过简单的封装,我们可以很快的完成这样的功能。
Android的UI描述并不那么方便,为了方便,往往会定义一个ViewGroup的子类,来描述这个 控件组。但是 组合优于继承,这样的做法让人有点 膈应,不能算作是最佳实践。-- 这一点对应了脑图中的 扩展类簇1。
相比于这样干,我更建议使用 Facade模式 进行逻辑封装,采用xml方式 声明这个控件组,或者封装 命令式构建函数构建这个控件组。
继承ViewGroup,扩展布局规则
Android中ViewGroup来封装布局规则,并提供了一套Layout。
当这些布局规则 无法满足 我们的需求时,我们可以通过 自定义ViewGroup 的方式来实现 自定义布局规则。
当然,Android发展到如今,已经 很难 找到一个相对抽象的布局规则,却没有被官方支持。
若确有必要,扩展布局规则时需要处理:
1. 封装规则描述,并实现 契约式编程设计。
? 定义LayoutParams,封装规则的细节点描述。
? 覆写 checkLayoutParams 以实现规则校验,契约式编程设计。
? 覆写 generateLayoutParams(AttributeSet attrs) 以实现 从xml属性生成LayoutParams。
? 覆写 generateLayoutParams(ViewGroup.LayoutParams p) 以实现 当规则不满足契约时,生成一个满足契约的LayoutParams,注意:可以从原LayoutParams中 采纳一些内容。
? 覆写 generateDefaultLayoutParams 以实现生成符合契约的 默认布局规则,如果返回null,在addView(View) 时,会引起运行时异常。
2. 在 onMeasure 方法中处理测量的逻辑,以实现 确定自身大小 和 触发子View测量。
? 接受 Parent 给到自身的 尺寸测量信息,如果测量模式是 EXACTLY,即可直接确定自身对应维度的尺寸;如果是 AT_MOST 或者 UNSPECIFIED, 则需要先测量子View,再确定自身。
? 按照布局特性,自身的 尺寸测量信息,和子View的布局规则属性值,确定 子View 的 尺寸测量信息,调用 子View 的 measure 方法触发测量。
3. 在 onLayout 方法中,处理布局,使用子View的 尺寸测量值 和 LayoutParams规则值,计算子View 的布局位置,并调用 子View 的 layout 方法触发子View布局。
4. 如果有特定需求,可以在 onDraw 中进行绘制,例如绘制分隔线
继承View,扩展内容显示能力
一般来说,少数情况下,继承View 或者 特定的Widget 是为了扩展 布局尺寸上的特性,这基本是从 measure机制 上入手。除此之外,一些场景下, 可以通过 继承View 实现 自定义内容绘制。
例如,显示图表的View。
这种场景下,一般需要处理:
? 尺寸测量流程中,Content的尺寸测量,并在 onMeasure 中实现:测量模式为 AT_MOST 或 UNSPECIFIED 时,利用Content的大小确定显示尺寸。
? 绘制流程中,onDraw 中实现内容的绘制。
注意: 如果并不牵涉到 交互,这并不是唯一方案,自定义Drawable的方案,也是很棒的方案。
借用 PhotoView 举个例子,如果交互局限为:双指缩放,拖拽,单击,双击。
那么通过 OnTouchListener + GestureDetector + 自定义Drawable, 对于绝大多数场景,都可以胜任。
4.扩展交互功能
在这个方向上,主要还是和 事件处理 体系有关。在 View体系 中,存在三个方法 和这个过程直接相关:
? dispatchTouchEvent
? onInterceptTouchEvent
? onTouchEvent
对于 onInterceptTouchEvent,非ViewGroup 的 View子类 是不参与的,因为这部分View,已经是事件处理的末端。
话分两头。
对于ViewGroup
扩展的目的一般有二:
? 在恰当的场景下,拦截事件并自身处理,处理逻辑在 onTouch 中实现。
? 处理可能存在的 事件处理冲突,当然,按照Android的规则,利用 requestDisallowInterceptTouchEvent 可以要求 直系的 所有 Parent 不拦截事件。但难免有意外,可以通过 onInterceptTouchEvent 来决定是否自身拦截处理事件,或者更加复杂的场景。
对于View而言
扩展的目的在于 定义事件的含义。
举个例子,继承View实现一个字母表导航控件,点击、滑动 被定义为切换到 对应的字母 进行导航。
我们需要在 onTouchEvent 中进行处理。
在前面,我们提到了 PhotoView 的例子,如果:事件 的含义 足够抽象,例如,对View 进行了:
  • 单击
  • 双击
  • 拖拽
  • 缩放
而不是 点击了View的特定区域,滑动至View的特定位置 等。我们可以利用 Android屏幕事件处理机制 中的 OnTouchListener 来获取事件信息, 并进行处理。在这种做法中,利用 GestureDetector 可以大大降低这一过程的难度。
5.总结
这一篇中,我们比较 随性 的思考了 为什么要自定义View 的问题,并展开了:
? 为什么需要这么干。
? 具体做法。
? 是否有其他方案,并简单交代了 哪种方案更适合。
这篇文章比较短,但是这部分内容的背后,还是值得继续深究、挖掘的。
最后
借此机会,在这里分享一份一线大厂Android中高级面试展开的完整面试题:《2022最新Android中高级面试题合集》这份资料总共【1932页】,文末免费领取。
第一章 Android 高频面试之必考Java基础 1,面向对象和面向过程的区别
2,面向对象的特征有哪些
3,解释下Java的编译与解释并存的现象
4,简单介绍下JVM的内存模型
5,简单介绍下Java的类加载器
6,谈一下Java的垃圾回收,以及常用的垃圾回收算法。
7,成员变量和局部变量的区别
8,Java 中的方法重写(Overriding)和方法重载(Overload)的含义
9,简单介绍下传递和引用传递
10,为什么重写 equals 时必须重写 hashCode 方法
我们为什么要自定义View()
文章图片

11,接口和抽象类的区别和相同点是什么
12,简述下HashMap
13, CurrentHashMap
14,介绍下什么是乐观锁、悲观锁
15,谈谈对Java线程的理解
16, Synchronized、volatile、Lock并发
17,锁
18,谈谈你对Java 反射的理解
19, 注解 20,单例
我们为什么要自定义View()
文章图片

第二章 Android 面试之必问Android基础 1,Activity:生命周期、启动模式、启动流程
2,Fragment:生命周期、与Activity传递数据
3, Service:启动方式、生命周期、Service不被杀死
4, BroadcastReceiver
5, ContentProvider
我们为什么要自定义View()
文章图片

6,Android View知识点:测量流程、事件分发、MotionEvent、Draw 绘制流程等等 7,Android进程:进程生命周期、多进程、多进程通信方式
8,序列化
9,Window
10,消息机制:Handler 机制、工作原理
11, RecyclerView优化:卡顿场景、其他优化策略
我们为什么要自定义View()
文章图片

第三章 Android 面试之必问高级知识点 1,编译模式:AOT优点、垃圾回收
2,类加载器:类加载器分类、双亲委托模式、Android的类加载器
3,Android Hook
4,代码混淆:Proguard、混淆规则、混淆模版
5,NDK:JNI基础、NDK开发基本流程、CMake 构建 NDK
6,动态加载:插件化、热修复
我们为什么要自定义View()
文章图片

第四章 Android 面试之必问性能优化 1,启动优化:冷启动/热启动和温启动的优化、代码方面的优化
2,UI渲染优化:CPU/GPU、过度绘制、解决自定义View的OverDraw、Hierarchy Viewer
3,内存优化:内存管理、内存泄漏、大图内存优化、线上监控
4,网络优化
5,耗电优化:优化方向、耗电监控、如何监控耗电
6,安装包优化:常用的优化策略、资源压缩、资源动态加载
我们为什么要自定义View()
文章图片

第五章 Android 面试之开源库分析 1,HTTP与缓存理论:HTTP缓存策略、强制缓存、对比缓存
2,OKHttp:OKHttp请求流程、OKHttpClient、同步请求、异步请求、CacheInterceptor网络请求缓存处理
3,Retrofit
4,Glide
我们为什么要自定义View()
文章图片

第六章 算法面试题汇总 1, 排
2, 二叉树
3,链表
4,栈 / 队列
5,二分搜索
6,哈希表
7,堆 / 优先队列
8,二叉搜索树
9,数组 / 双指针
10,贪心
11,字符串处理
12,动态规划
13,矩阵
14,二进制 / 位运算
15,其他:两个大文件中找出共同记录、ip地址与int类型的转换、整数反转、LRU缓存策略
我们为什么要自定义View()
文章图片

需要完整版《Android2021年度的高频面试真题》的朋友可以点击这里免费领取!

    推荐阅读