View,幾乎是所有界面系統(tǒng)中的基類,在iOS里面是UIView,在Android里是View。 那么,到底View是什么東西,他做了些什么,他是怎么做到的,在這篇文章中,希望能帶給大家一些啟發(fā)。
View實(shí)際上是一個(gè)抽象類,他負(fù)責(zé)對(duì)渲染、布局以及觸摸事件進(jìn)行抽象。
渲染抽象
我們知道,不管是 iOS 還是 Android,他們的渲染引擎都是 OpenGL,OpenGL是面向C語(yǔ)言的(當(dāng)然,在Objective-C和Java中都有封裝)。而作為前端開發(fā)者,要直接使用OpenGL編寫界面,真是不(Tai)現(xiàn)(Nan)實(shí)(Le)。于是,我們有了界面庫(kù),這種界面庫(kù),在iOS上,我們稱之為UIKit,在Android上,我們使用android.view.*包。不管是iOS還是Android,界面庫(kù)要做的事情,目標(biāo)都是一致的,那就是將界面渲染從具體變成抽象。
布局抽象
布局,是View重要的特性,諸如層級(jí)控制、矩形大小、Matrix變換都屬于布局抽象的范疇,布局是渲染、觸摸兩者的基礎(chǔ),缺少布局,渲染和觸摸便無法繼續(xù)。
觸摸事件抽象
除了渲染、布局以外,View還需要承擔(dān)另外一個(gè)職責(zé)————觸控。所謂觸控,有觸才有控,一方面View要負(fù)責(zé)接收觸摸事件,另一方面View要負(fù)責(zé)反饋接收到的觸摸事件,至于具體的觸控實(shí)現(xiàn),下文會(huì)詳細(xì)描述。
一般來說View不會(huì)直接面向OpenGL進(jìn)行封裝,而是通過中間層,在iOS上,使用的是CALayer(CoreGraphics),而在Android上,使用的是 Canvas(Skia)。
在iOS上,每個(gè)UIView都會(huì)有一個(gè)相對(duì)應(yīng)的CALayer,我們稱之為L(zhǎng)ayer-Back,也就是說,所有的UIView屬性,終,都會(huì)設(shè)置到CALayer身上。
為什么要使用CALayer這個(gè)中間層呢?很重要的一點(diǎn)是,CoreGraphcis框架,這個(gè)框架,在NEXT系統(tǒng)創(chuàng)建之初就存在了,并且是整個(gè)macOS系統(tǒng)的核心框架。這么6的框架,為毛不讓他移植到iOS上呢?于是,CALayer就順理成章地成為了UIView背后的賢內(nèi)助。UIView會(huì)將以下屬性proxy到CALayer上
alpha
frame
backgroundColor
clipsToBounds
hidden
為毛這么少屬性?嗯,因?yàn)槠渌鼘傩孕枰阕约喝ピO(shè)置到CALayer上。什么?你要問CALayer是怎么渲染到屏幕上的?你自己查吧,據(jù)說,專門有一本書是寫這個(gè)的。總的來說,UIView在渲染上,并沒有做什么神奇的事情,CALayer才是一直默默耕耘的那個(gè)。
Android實(shí)際上是個(gè)草根系統(tǒng),出生的時(shí)候,并沒有一個(gè)有錢的爸爸…所以呢? Android View的渲染層,其實(shí)是照抄H5中的Canvas。
比如,我要在 View 上畫一個(gè)黑色的 backgroundColor,實(shí)際上會(huì)在 void onDraw(Canvas canvas)方法中執(zhí)行以下代碼。
Paint paint = new Paint();
paint.color = Color.BLACK;
canvas.drawRect(x, y, width, height);
又例如,我想要讓View有圓角裁剪的效果,怎么辦呢?實(shí)際上,會(huì)這么做。
canvas.save();
canvas.clipPath(...); // 畫一個(gè)圓角的路徑,然后 clip。 // draw contents... canvas.restore();
醬紫,在這個(gè)View中所繪制的所有圖案(包括子View)都會(huì)被某個(gè)路徑裁剪掉。
那么,View干了啥? View實(shí)際上會(huì)定義好x, y, width, height只有知道這些參數(shù),你才能畫出一個(gè)背景色,不然……你畫個(gè)卵?View還管理著 alpha/backgroundColor等屬性,這些屬性,你都能在Canvas、Paint類中找到相關(guān)的參數(shù)。
iOS VS Android
這個(gè),沒什么好對(duì)比的,無非就是渲染層的抽象不一樣而已。就渲染性能而言,iOS是更勝一籌的,自Android 4.x引入Skia以后,特別是Skia在 Google黃油計(jì)劃以后,Android的渲染性能也差不了去哪里了。如果,你要死摳對(duì)比的話,我只能說一個(gè)是CALayer,一個(gè)是Canvas,CALayer更抽象而已了。
我說過View重要的事情,就是布局。布局,對(duì)于開發(fā)者來說,簡(jiǎn)單的理解就是x, y, width, height。再?gòu)?fù)雜一點(diǎn)的話,就是層級(jí)、變換。
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)
就這樣,我們?cè)趘iew中,就能在(44, 44, 44, 44) 這個(gè)區(qū)域中,渲染一片黑色。 噢,這已經(jīng)說明了布局的用途了,確定位置,確定大小。
層級(jí)
我們要在上面的代碼上,加點(diǎn)改進(jìn),在黑色區(qū)域的右下象限,添加一片紅色。
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)
就是這么簡(jiǎn)單,因?yàn)椋辛藢蛹?jí),我們可以很輕松地完成這件事情。我們可以不關(guān)心終的界面是如何渲染出來的,我們只需要關(guān)心當(dāng)前的一小區(qū)域即可。這就是層級(jí)的魔力 ———— 分而治之。
變換
如果想要我渲染出來的東西,旋轉(zhuǎn)一下,那你好使用 Matrix 變換。變換,在 View 里面,也屬于布局的范疇。具體,不在這里展開討論。
布局系統(tǒng)
上述的例子,是使用iOS作示例的,在Android上同樣可以使用FrameLayout做到這件事情。
Q: 老師!我有問題!為什么你直接寫x, y, width, height,我使用RelativeLayout/LinerLayout/AutoLayout不是更好嗎?
A: 同學(xué),你說得對(duì),那是更高級(jí)的布局系統(tǒng),是更高級(jí)的抽象!到終,還是會(huì)變成x,y,width,height的,不信?你自己去探究一下。
如果你以為View只是渲染一下這么簡(jiǎn)單,那真是圖森破圖樣了。一個(gè)常規(guī)的View類,必須做的事情,那就是觸摸事件處理。常見的觸摸事件處理,主要有兩個(gè)過程,冒泡、向上遞歸。
冒泡
冒泡的主要作用是為了找出觸摸點(diǎn)所在的 View,我們有個(gè)術(shù)語(yǔ)描述這個(gè)冒泡的過程————hitTest。hitTest一般是由頂層的View開始進(jìn)行的,在iOS里面是 UIWinodw,在Android里面是Window,因?yàn)樗麄兪窍冉邮盏竭@個(gè)觸摸事件的響應(yīng)者。接著,View使用 hitTest詢問自己能否成為響應(yīng)者,成為響應(yīng)者有幾個(gè)條件,alpha > 0、hidden == false、userInteractionEnabled == true以及 x, y 是否在 x, y, width, height矩形內(nèi)。如果可以,則繼續(xù)向自己的Subviews詢問hitTest,直至找到終的響應(yīng)者為止。
從我們看到的界面來說,響應(yīng)者就是你所點(diǎn)中的那個(gè)View,響應(yīng)鏈就是你所點(diǎn)中的那個(gè)View向上的superview->superview->superview…的這個(gè)路徑。
任何一個(gè)觸摸事件響應(yīng)系統(tǒng)中,響應(yīng)者和響應(yīng)鏈都是必須的,一旦確定好響應(yīng)者和響應(yīng)鏈,觸摸的過程就開始了。一般來說,hitTest只需要在TouchStart的時(shí)候進(jìn)行。
你可以在iOS UIView中重寫hitTest方法,加以驗(yàn)證。在Android中,重寫public boolean dispatchTouchEvent(MotionEvent event)驗(yàn)證。
向上遞歸
冒泡過程完成后,我們會(huì)得到響應(yīng)者A,緊接著touchstart、touchmove、touchend、touchcancel事件就會(huì)分發(fā)到這個(gè)響應(yīng)者身上。響應(yīng)者要做的事情,就是要識(shí)別這個(gè)觸摸是不是他想要的,并且往superview繼續(xù)傳遞這個(gè)事件。傳遞這個(gè)操作,十分重要,這意味著,當(dāng)深的 View 無法處理這個(gè)事件時(shí),上一級(jí)的View可以收到這個(gè)事件并處理。你可以在iOS UIView中重寫touchesBegan、touchesMoved touchesEnded、touchesCancelled方法,加以驗(yàn)證。在Android中,重寫public boolean onTouchEvent(MotionEvent event)驗(yàn)證。
iOS VS Android
在觸摸事件的處理上,iOS與Android差異較大。iOS除了hitTest和向上遞歸外,還封裝了不少GestureRecognizer,使得開發(fā)者幾乎可以忽略原理就可以使用起來。而Android開發(fā)者,并沒有那么幸運(yùn),遇到難題時(shí),還是需要從觸摸事件 原理入手去解決問題。
這篇文章整篇都是結(jié)論的樣子,如果有什么地方寫錯(cuò)了,還希望大家能夠指出。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。