我所理解的 View

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 开发者,并没有那么幸运,遇到难题时,还是需要从触摸事件�原理入手去解决问题。

结论

这篇�文章,并没有什么结论......

说不定还有些�论点是错的,�要不,你发一篇文章来反驳一下!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容