我所理解的 View

View,幾乎是所有界面系統(tǒng)中的基類,在 iOS 里面是 UIView,在 Android 里是 View。?那么,到底 View 是什么東西,他做了些什么,?他是怎么做到的,在這篇文章中,希望能帶給大家一些啟發(fā)。

抽象

View 實際上是一個抽象類,他負責對渲染、布局以及觸摸事件進行抽象。

渲染抽象

我們知道,不管是 iOS 還是 Android,他們的渲染引擎都是 OpenGL,OpenGL 是面向 C 語言的(當然,在 Objective-C 和 Java 中都有封裝)。而作為前端開發(fā)者,要直接使用 OpenGL 編寫界面,真是不(Tai)現(xiàn)(Nan)實(Le)。
于是,我們有了界面庫,這種界面庫,在 iOS 上,我們稱之為 UIKit,在 Android 上,我們使用 android.view.* 包。不管是 iOS 還是 Android,界面庫要做的事情,目標都是一致的,那就是將界面渲染從具體變成抽象。

布局抽象

布局,是 View 最重要的特性,諸如層級控制、矩形大小、Matrix 變換都屬于布局抽象的范疇,布局是渲染、觸摸兩者的基礎,缺少布局,渲染和觸摸便無法繼續(xù)。

觸摸事件抽象

除了渲染、布局以外,View 還需要承擔另外一個職責 ———— 觸控。
所謂觸控,有觸才有控,一方面 View 要負責接收觸摸事件,另一方面 View 要負責反饋接收到的觸摸事件,至于具體的觸控實現(xiàn),下文會詳細描述。

渲染

一般來說 View 不會直接面向 OpenGL 進行封裝,而是通過中間層,在 iOS 上,使用的是 CALayer(CoreGraphics),而在 Android 上,使用的是 Canvas (Skia)。

iOS

在 iOS 上,每個 UIView 都會有一個相對應的 CALayer,我們稱之為 Layer-Back,也就是說,所有的 UIView 屬性,最終,都會設置到 CALayer 身上。

為什么要使用 CALayer 這個中間層呢?很重要的一點是,CoreGraphcis 框架,這個框架,在 NEXT 系統(tǒng)創(chuàng)建之初就存在了,并且是整個 macOS 系統(tǒng)的核心框架。這么 6 的框架,為毛不讓他移植到 iOS 上呢?于是,CALayer 就順理成章地成為了 UIView 背后的賢內(nèi)助。

UIView 會將以下屬性 proxy 到 CALayer 上

  • alpha
  • frame
  • backgroundColor
  • clipsToBounds
  • hidden

為毛這么少屬性?嗯,因為其它屬性需要你自己去設置到 CALayer 上。什么?你要問 CALayer 是怎么渲染到屏幕上的?你自己查吧,據(jù)說,專門有一本書是寫這個的??偟膩碚f,UIView 在渲染上,并沒有做什么神奇的事情,CALayer 才是一直默默耕耘的那個。

Android

Android,實際上,是個草根系統(tǒng),出生的時候,并沒有一個有錢的爸爸……

所以呢? Android View 的渲染層,其實是照抄 Canvas 的。

比如,我要在 View 上畫一個黑色的 backgroundColor,實際上會在 void onDraw(Canvas canvas) 方法中執(zhí)行以下代碼。

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,只有知道這些參數(shù),你才能畫出一個背景色,不然……你畫個卵?View 還管理著 alpha / backgroundColor 等屬性,這些屬性,你都能在 Canvas.Paint 類中找到相關的參數(shù)。

iOS VS Android

?這個,沒什么好對比的,無非就是渲染層的抽象不一樣而已。???就渲染性能而言,iOS 是更勝一籌的,自 Android 4.x 引入 Skia 以后,特別是 Skia ?在 Google 黃油計劃以后,???Android 的渲染性能也差不了去哪里了。

?如果,你要死摳對比的話,我只能說一個是 CALayer,一個是 Canvas,CALayer ?更抽象而已了。

布局

我說過 View 最重要的事情,就是布局。布局,對于開發(fā)者來說,最簡單的理解就是 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) 這個區(qū)域中,渲染一片黑色。?噢,這已經(jīng)說明了布局的用途了,確定位置,確定大小。

層級

我們要在上面的代碼上,加點改進,在黑色區(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)

就是這么簡單,因為,有了層級,我們可以很輕松地完成這件事情。我們可以不關心最終的界面是如何渲染出來的,我們只需要關心當前的一小區(qū)域即可。這就是層級的魔力 ———— 分而治之。

變換

如果想要我渲染出來的東西,旋轉(zhuǎn)一下,那你最好使用 Matrix 變換。變換,在 View 里面,也屬于布局的范疇。具體,不在這里展開討論。

布局系統(tǒng)

上述的例子,是使用 iOS 作示例的,在 Android 上同樣可以使用 FrameLayout 做到這件事情。

Q: 老師!我有問題!為什么你直接寫 x, y, width, height,我使用 RelativeLayout / LinerLayout / AutoLayout 不是更好嗎?

A: 同學,你說得對,那是更高級的布局系統(tǒng),是更高級的抽象!到最終,還是會變成 x, y, width, height的,不信?你自己去探究一下。

觸摸事件處理

如果,你以為 View 只是渲染一下這么簡單,那真是圖森破圖樣了。一個常規(guī)的 View 類,必須做的事情,那就是觸摸事件處理。常見的觸摸事件處理,主要有兩個過程,冒泡、向上遞歸。

冒泡

冒泡的主要作用是為了找出觸摸點所在的 View,我們有個術語描述這個冒泡的過程 ———— hitTest。

hitTest 一般是由最頂層的 View 開始進行的,在 iOS 里面是 UIWinodw,在 Android 里面是 Window,因為他們是最先接收到這個觸摸事件的響應者。

接著,View 使用 hitTest 詢問自己能否成為響應者,成為響應者有幾個條件, alpha > 0 hidden == false userInteractionEnabled == true 以及 x, y 是否在 x, y, width, height 矩形內(nèi)。如果可以,則繼續(xù)向自己的 Subviews 詢問 hitTest,直至找到最終的響應者為止。

從我們看到的界面來說,響應者,就是你所點中的那個 View,響應鏈,就是你所點中的那個 View 向上的 superview >> superview >> superview ... 的這個路徑。

任何一個觸摸事件響應系統(tǒng)中,響應者和響應鏈都是必須的,一旦確定好響應者和響應鏈,觸摸的過程就開始了。一般來說,hitTest 只需要在 TouchStart 的時候進行。

你可以在 iOS UIView 中重寫 hitTest 方法,加以驗證。在 Android 中,重寫 public boolean dispatchTouchEvent(MotionEvent event) 驗證。

向上遞歸

冒泡過程完成后,我們會得到響應者 A,緊接著 touchstart / touchmove / touchend / touchcancel 事件就會分發(fā)到這個響應者身上。

響應者要做的事情,就是要識別這個觸摸是不是他想要的,并且往 superview 繼續(xù)傳遞這個事件。傳遞這個操作,十分重要,這意味著,當最深的 View 無法處理這個事件時,上一級的 View 可以收到這個事件,并處理。

你可以在 iOS UIView 中重寫 touchesBegan touchesMoved touchesEnded touchesCancelled ?方法,加以驗證。在 Android 中,重寫 public boolean onTouchEvent(MotionEvent event) 驗證。

iOS VS Android

?在?觸摸事件的處理上,iOS 與 Android ?差異較大。?iOS 除了 hitTest 和向上遞歸外,還封?裝了不少 GestureRecognizer,?使得開發(fā)者幾乎可以忽略原理就可以使用起來。而 Android 開發(fā)者,并沒有那么幸運,遇到難題時,還是需要從觸摸事件?原理入手去解決問題。

結(jié)論

這篇?文章,并沒有什么結(jié)論......

說不定還有些?論點是錯的,?要不,你發(fā)一篇文章來反駁一下!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容