Android和iOS上的View

更新时间:2017-11-09 10:01:07点击次数:244次

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

结论

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

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息