161

我所理解的Android和iOS上的View

 6 years ago
source link: https://mp.weixin.qq.com/s/W9lRR4K3XRNTPvXf64qdhA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我所理解的Android和iOS上的View

Pony 技术视界 2017-11-09 00:15 Posted on

本文由全栈大佬Pony出品,小弟我只负责打打杂。对本文有任何意见或建议,非常欢迎留言交流。

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的渲染层,其实是照抄H5中的Canvas。

比如,我要在 View 上画一个黑色的 backgroundColor,实际上会在 void onDraw(Canvas canvas)方法中执行以下代码。

    Paint paint = new Paint();
    paint.color = Color.BLACK;
    canvas.drawRect(x, y, width, height);

又例如,我想要让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 > 0hidden == falseuserInteractionEnabled == 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中重写touchesBegantouchesMoved touchesEndedtouchesCancelled方法,加以验证。在Android中,重写public boolean onTouchEvent(MotionEvent event)验证。

iOS VS Android

在触摸事件的处理上,iOS与Android差异较大。iOS除了hitTest和向上递归外,还封装了不少GestureRecognizer,使得开发者几乎可以忽略原理就可以使用起来。而Android开发者,并没有那么幸运,遇到难题时,还是需要从触摸事件 原理入手去解决问题。

这篇文章整篇都是结论的样子,如果有什么地方写错了,还希望大家能够指出。

Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK