View,几乎是所有界面系统中的基类,在 iOS 里面是 UIView,在 Android 里是 View。�那么,到底 View 是什么东西,他做了些什么,�他是怎么做到的,在这篇文章中,希望能带给大家一些启发。
抽象
View 实际上是一个抽象类,他负责对渲染、布局以及触摸事件进行抽象。
渲染抽象
我们知道,不管是 iOS 还是 Android,他们的渲染引擎都是 OpenGL,OpenGL 是面向 C 语言的(当然,在 Objective-C 和 Java 中都有封装)。而作为前端开发者,要直接使用 OpenGL 编写界面,真是不(Tai)现(Nan)实(Le)。
于是,我们有了界面库,这种界面库,在 iOS 上,我们称之为 UIKit,在 Android 上,我们使用 android.view.* 包。不管是 iOS 还是 Android,界面库要做的事情,目标都是一致的,那就是将界面渲染从具体变成抽象。
布局抽象
布局,是 View 最重要的特性,诸如层级控制、矩形大小、Matrix 变换都属于布局抽象的范畴,布局是渲染、触摸两者的基础,缺少布局,渲染和触摸便无法继续。
触摸事件抽象
除了渲染、布局以外,View 还需要承担另外一个职责 ———— 触控。
所谓触控,有触才有控,一方面 View 要负责接收触摸事件,另一方面 View 要负责反馈接收到的触摸事件,至于具体的触控实现,下文会详细描述。
渲染
一般来说 View 不会直接面向 OpenGL 进行封装,而是通过中间层,在 iOS 上,使用的是 CALayer(CoreGraphics),而在 Android 上,使用的是 Canvas (Skia)。
iOS
在 iOS 上,每个 UIView 都会有一个相对应的 CALayer,我们称之为 Layer-Back,也就是说,所有的 UIView 属性,最终,都会设置到 CALayer 身上。
为什么要使用 CALayer 这个中间层呢?很重要的一点是,CoreGraphcis 框架,这个框架,在 NEXT 系统创建之初就存在了,并且是整个 macOS 系统的核心框架。这么 6 的框架,为毛不让他移植到 iOS 上呢?于是,CALayer 就顺理成章地成为了 UIView 背后的贤内助。
UIView 会将以下属性 proxy 到 CALayer 上
- alpha
- frame
- backgroundColor
- clipsToBounds
- hidden
为毛这么少属性?嗯,因为其它属性需要你自己去设置到 CALayer 上。什么?你要问 CALayer 是怎么渲染到屏幕上的?你自己查吧,据说,专门有一本书是写这个的。总的来说,UIView 在渲染上,并没有做什么神奇的事情,CALayer 才是一直默默耕耘的那个。
Android
Android,实际上,是个草根系统,出生的时候,并没有一个有钱的爸爸……
所以呢? Android View 的渲染层,其实是照抄 Canvas 的。
比如,我要在 View 上画一个黑色的 backgroundColor,实际上会在 void onDraw(Canvas canvas)
方法中执行以下代码。
Paint paint = new Paint();
paint.color = Color.BLACK;
canvas.drawRect(x, y, width, height, paint);
又例如,我想要让 View 有圆角裁剪的效果,怎么办呢?实际上,会这么做。
canvas.save();
canvas.clipPath(...); // 画一个圆角的路径,然后 clip。
// draw contents...
canvas.restore();
酱紫,在这个 View 中所绘制的所有图案(包括子 View)都会被某个路径裁剪掉。
那么,View 干了啥? View 实际上会定义好 x, y, width, height,只有知道这些参数,你才能画出一个背景色,不然……你画个卵?View 还管理着 alpha / backgroundColor 等属性,这些属性,你都能在 Canvas.Paint 类中找到相关的参数。
iOS VS Android
�这个,没什么好对比的,无非就是渲染层的抽象不一样而已。���就渲染性能而言,iOS 是更胜一筹的,自 Android 4.x 引入 Skia 以后,特别是 Skia �在 Google 黄油计划以后,���Android 的渲染性能也差不了去哪里了。
�如果,你要死抠对比的话,我只能说一个是 CALayer,一个是 Canvas,CALayer �更抽象而已了。
布局
我说过 View 最重要的事情,就是布局。布局,对于开发者来说,最简单的理解就是 x, y, width, height。再复杂一点的话,就是层级、变换。
x, y, width, height
一例胜千言
let fooView = UIView()
fooView.frame = CGRect(x: 44.0, y: 44.0, width: 44.0, height: 44.0)
fooView.backgroundColor = UIColor.black
self.view.addSubview(fooView)
就这样,我们在 view 中,就能在 (44, 44, 44, 44) 这个区域中,渲染一片黑色。�噢,这已经说明了布局的用途了,确定位置,确定大小。
层级
我们要在上面的代码上,加点改进,在黑色区域的右下象限,添加一片红色。
let fooView = UIView()
fooView.frame = CGRect(x: 44.0, y: 44.0, width: 44.0, height: 44.0)
fooView.backgroundColor = UIColor.black
let redView = UIView()
redView.frame = CGRect(x: 22.0, y: 22.0, width: 22.0, height: 22.0)
fooView.addSubview(redView)
self.view.addSubview(fooView)
就是这么简单,因为,有了层级,我们可以很轻松地完成这件事情。我们可以不关心最终的界面是如何渲染出来的,我们只需要关心当前的一小区域即可。这就是层级的魔力 ———— 分而治之。
变换
如果想要我渲染出来的东西,旋转一下,那你最好使用 Matrix 变换。变换,在 View 里面,也属于布局的范畴。具体,不在这里展开讨论。
布局系统
上述的例子,是使用 iOS 作示例的,在 Android 上同样可以使用 FrameLayout 做到这件事情。
Q: 老师!我有问题!为什么你直接写 x, y, width, height,我使用 RelativeLayout / LinerLayout / AutoLayout 不是更好吗?
A: 同学,你说得对,那是更高级的布局系统,是更高级的抽象!到最终,还是会变成 x, y, width, height的,不信?你自己去探究一下。
触摸事件处理
如果,你以为 View 只是渲染一下这么简单,那真是图森破图样了。一个常规的 View 类,必须做的事情,那就是触摸事件处理。常见的触摸事件处理,主要有两个过程,冒泡、向上递归。
冒泡
冒泡的主要作用是为了找出触摸点所在的 View,我们有个术语描述这个冒泡的过程 ———— hitTest。
hitTest 一般是由最顶层的 View 开始进行的,在 iOS 里面是 UIWinodw,在 Android 里面是 Window,因为他们是最先接收到这个触摸事件的响应者。
接着,View 使用 hitTest
询问自己能否成为响应者,成为响应者有几个条件, alpha > 0
hidden == false
userInteractionEnabled == true
以及 x, y 是否在 x, y, width, height 矩形内。如果可以,则继续向自己的 Subviews 询问 hitTest
,直至找到最终的响应者为止。
从我们看到的界面来说,响应者,就是你所点中的那个 View,响应链,就是你所点中的那个 View 向上的 superview >> superview >> superview ... 的这个路径。
任何一个触摸事件响应系统中,响应者和响应链都是必须的,一旦确定好响应者和响应链,触摸的过程就开始了。一般来说,hitTest 只需要在 TouchStart 的时候进行。
你可以在 iOS UIView 中重写 hitTest
方法,加以验证。在 Android 中,重写 public boolean dispatchTouchEvent(MotionEvent event)
验证。
向上递归
冒泡过程完成后,我们会得到响应者 A,紧接着 touchstart / touchmove / touchend / touchcancel 事件就会分发到这个响应者身上。
响应者要做的事情,就是要识别这个触摸是不是他想要的,并且往 superview 继续传递这个事件。传递这个操作,十分重要,这意味着,当最深的 View 无法处理这个事件时,上一级的 View 可以收到这个事件,并处理。
你可以在 iOS UIView 中重写 touchesBegan
touchesMoved
touchesEnded
touchesCancelled
�方法,加以验证。在 Android 中,重写 public boolean onTouchEvent(MotionEvent event)
验证。
iOS VS Android
�在�触摸事件的处理上,iOS 与 Android �差异较大。�iOS 除了 hitTest 和向上递归外,还封�装了不少 GestureRecognizer,�使得开发者几乎可以忽略原理就可以使用起来。而 Android 开发者,并没有那么幸运,遇到难题时,还是需要从触摸事件�原理入手去解决问题。
结论
这篇�文章,并没有什么结论......
说不定还有些�论点是错的,�要不,你发一篇文章来反驳一下!
网友评论