iOS 動畫 - 窗景篇(一)

iOS 有一種動畫,使用雖然簡單,但能實現不少有趣的效果,那就是 mask 動畫。git

若是你還不瞭解 mask 動畫,看完本系列文章後,你能夠學會這種動畫。若是你已經使用過了,本文也能幫你梳理一下,讓你使用起來更方便。github

本系列文章共3篇,做爲系列的開篇,咱們首先要搞清楚一個問題:什麼是 mask。swift

1、什麼是 mask

mask 是 UIView 或 CALayer 的一個屬性,它決定了 view 或 layer 的哪一部分能被咱們看到post

本文爲了方便講述,主要使用 view 和它的 mask 屬性。動畫

iOS 對 mask 的描述,對不少人來講不是特別直觀,因此在貼出定義以前,咱們先嚐試直觀地看一下。spa

首先,咱們來看一下這張圖:code

如圖所示,一張紙上有個圓洞,紙蓋在了左邊的圖片上,圖片的一部分經過這個洞透了過來,就像牆上開了一扇窗,讓咱們看到了一部分風景。orm

不嚴謹的說,中間的這張黑紙就是 mask,它決定了 view 的哪一部分能被咱們看到。cdn

不過這張圖會誤導咱們,讓咱們感受 mask 擋住了 view,其實並非這樣。 咱們來看一下這張圖:視頻

從這張圖中,咱們能夠看到:frontView.mask 隻影響了 frontView 哪部分能夠被咱們看到 ,對後面的 backView 沒有任何影響。 看上去,mask 更像是對 view 進行了裁剪。

上面的兩張圖並不符合 iOS 對 mask 的描述, 但經過這兩張圖,咱們應該對 「mask 決定了 view 的哪一部分能被咱們看到」 這句話,有了直觀的印象。

接下來,咱們就一塊兒來看一下 iOS 對 mask 的描述。

2、iOS 中的 mask

咱們首先看一下 iOS 對 UIView 的 mask 的定義:

var mask: UIView? { get set }
複製代碼

能夠看到,UIView 的 mask,其實就是另外一個 UIView。

再看一下這句簡要描述:

An optional view whose alpha channel is used to mask a view’s content.

這句描述指出了:用 mask 的 alpha channel(透明度通道) 去決定 view 的內容顯示,但沒說怎麼決定。

接下來再看一下詳細的描述:

Discussion

The view’s alpha channel determines how much of the view’s content and background shows through. Fully or partially opaque pixels allow the underlying content to show through but fully transparent pixels block that content.

這句 「Fully or partially opaque pixels allow the underlying content to show through」 就比較清晰了,大意是:mask 上不透明的部分(包括半透明的部分,這種狀況咱們稍後再看),可讓 view 透過來。

「不透明的部分,可讓 view 透過來」,這句話聽上去可能讓人有點困惑,咱們仍是用圖來表示一下,咱們先根據這個描述,改造一下前文的那張圖,以下:

圖中的 mask(本質上也是個 view),只有中間的圓是有顏色(黑色)的,其他部分是透明的。當它做爲左邊 view 的 mask 時,只有中間有顏色(也就是不透明)的圓,才容許 view 透過來。

這就是爲何有些人以爲 mask 的描述不是很直觀,畢竟咱們下意識裏,會以爲透明的部分,才能透事後面的東西。

其實也很好理解,mask 上的不透明的部分,只是窗戶區域的描述,而不是窗戶自己。當它做爲 view 的 mask 時,系統就會把 mask 上不透明的部分(不論是純色、圖像仍是視頻等)做爲窗戶區,真正渲染時,就會讓 此處的view 透過來。

爲了方便講述,上面的圖中,view 和 mask 的 我使用了同樣的尺寸,但其實 mask 的 frame 並不重要。view 哪些部分能顯示,只以 mask 不透明區域爲準,和 mask 的 frame 沒有關係 。

好比下面圖中的效果和上圖是同樣的:

注:mask 的 frame 基於 view 的座標系(和該 view 的 subView 的 frame 相似 )

咱們知道了 mask 的含義,那麼 mask 具體怎麼使用呢,很簡單,就是把1個 view 賦值給另外一個 view 的 mask 屬性。

好比上圖的效果,咱們大體能夠這樣寫代碼:

// backView
backView.frame = UIScreen.main.bounds
view.addSubview(backView)

// frontView 圖片景
frontView.frame = UIScreen.main.bounds
view.addSubview(frontView)

// 圓窗
let mask = CircleView()
mask.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
mask.center = CGPoint(x: frontView.bounds.midX, y: frontView.bounds.midY)
frontView.mask = mask
複製代碼

至此,咱們差很少就理解了 iOS 的 mask。 本系列文章中,爲了便於描述,用窗指代 mask,用景指代 mask 關聯的 view。

那麼接下來,咱們就簡單地看一點窗、景的簡單例子,來打開一下思路,思路一打開,後續文章中的效果就很容易實現了。

3、窗、景的簡單例子

先來看一下窗。

咱們已經知道,view 的 mask 也是個 view,既然 view 的樣式多種多樣,那窗的樣式固然也是五花八門的。

好比咱們用一個 UIButton 做爲以前圖片 view 的 mask,button 的 title 天然就成了文字窗戶,效果以下圖所示:

示意代碼以下:

// 圖片景
frontView.frame = UIScreen.main.bounds
view.addSubview(frontView)

// 文字窗
let mask = UIButton(type: .custom)
mask.setTitle("柯爛", for: .normal)
mask.titleLabel?.font = UIFont.systemFont(ofSize: 100)
mask.frame = CGRect(x: 0, y: 0, width: 300, height: 200)
mask.center = CGPoint(x: frontView.bounds.midX, y: frontView.bounds.midY)
frontView.mask = mask
複製代碼

接下來再看一下景。

景也是 view,能夠是純色、圖片,也能夠是動圖、視頻等。 本例中,咱們用一個漸變更畫的 view 做爲景,用一個圓形窗,效果以下圖所示:

因爲背景是漸變更畫,下面這張動圖能更好地展現效果:

示意代碼以下:

// 漸變更畫景
frontView.frame = UIScreen.main.bounds
view.addSubview(frontView)
// 執行動畫
frontView.start()

// 圓窗
let mask = CircleView()
mask.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
mask.center = CGPoint(x: frontView.bounds.midX, y: frontView.bounds.midY)
frontView.mask = mask
複製代碼

也許有的同窗已經想到了,上面的文字窗、漸變景一結合,不就是個不錯的效果嗎, 是啊,這就造成了動態漸變的文字效果,以下面的動圖所示:

示意代碼以下:

// 漸變更畫景
frontView.frame = UIScreen.main.bounds
view.addSubview(frontView)
// 執行動畫
frontView.start()

// 文字窗
let mask = UIButton(type: .custom)
mask.setTitle("柯爛", for: .normal)
mask.titleLabel?.font = UIFont.systemFont(ofSize: 100)
mask.frame = CGRect(x: 0, y: 0, width: 300, height: 200)
mask.center = CGPoint(x: frontView.bounds.midX, y: frontView.bounds.midY)
frontView.mask = mask
複製代碼

例子舉到這裏,你們就明白了,只要選窗用景的思路開闊一些,mask 動畫效果是多種多樣的。

細心的同窗還記得,前面咱們留了個尾巴,我引用一下前文的話:

這句 「Fully or partially opaque pixels allow the underlying content to show through」 就比較清晰了,mask 上不透明的部分(包括半透明的部分,這種狀況咱們稍後再看),可讓 view 透過來。

因爲徹底不透明的 mask 比較好理解(就是把不透明的區域,摳掉當作窗戶),因此前文都以徹底不透明的 mask 做爲示例。

讀到了這裏,咱們對半透明的 mask 理解已經沒有障礙了,因此咱們補充一下半透明 mask。

4、半透明 mask

如今你們已經瞭解了,mask 上的不透明區至關於描述了窗戶的區域,而 mask 的半透明度,至關於描述了窗戶的通透程度。

mask 的區域徹底不透明,那窗戶就是全透明的,view 能徹底透過來;而 mask 的區域半透明,窗戶就是半透明的,view 能模模糊糊的透過來。 mask的透明度和窗戶的透明度成反比。

咱們用一個實踐中經常使用的例子來看一下。

例子是這樣的, 有時候,咱們有一個半屏的 tableView,它的頂部再也不屏幕的頂部,而是在屏幕的中央(好比直播間裏的聊天區)。 這種狀況下,cell 向上滑出 tableView,滑到一半時,因爲只顯示半個 cell,tableView 的邊緣會顯得很明顯。以下圖所示:

咱們想讓它的邊緣不那麼明顯,有個相似淡出的效果,若是用 mask 來實現,只要有一個豎直漸變的 view 做爲 mask 就能夠了。

注:此處 mask 要做爲 tableView 的 superView 的 mask(能夠建立一個和 tableView 大小相等的 view 做爲它的 superView)

mask 頂部逐漸過渡到了透明,相應地,窗戶就漸漸過渡到了不透明,tableView 的頂部看上去就像是淡出了,效果以下圖所示:

示意代碼以下:

// table 景(tableContainer 來輔助)
let tableContainer = UIView()
let bounds = UIScreen.main.bounds
let gradientHeight: CGFloat = 20.0
tableContainer.frame = CGRect(x: 0, y: bounds.midY, width: bounds.width / 2, height: bounds.height / 2)
view.addSubview(tableContainer)
tableView.frame = tableContainer.bounds
tableContainer.addSubview(tableView)

// 半透明漸變窗
let mask = GradientView()
mask.frame = tableContainer.bounds
mask.gradientLayer.startPoint = CGPoint(x: 0, y: 1)
mask.gradientLayer.endPoint = CGPoint(x: 0, y: 0)
mask.colors = [.white, .white, UIColor.white.withAlphaComponent(0)]
mask.locations = [0, 1 - Double(gradientHeight / tableContainer.bounds.height), 1]
// 做爲 tableContainer 的 mask,而不是 tableView
tableContainer.mask = mask
複製代碼

半透明 mask 咱們就先說到這,後面的文章,咱們主要仍是以不透明 mask 爲主。

尾聲

至此,咱們對 mask 就有了足夠的瞭解,也打開了一點窗與景的思路;接下來的文章,咱們就一塊兒來看一下 mask 的各類玩法。

本文全部示例,在 GitHub 庫 裏都有完整的代碼。

感謝您的閱讀,咱們下篇文章見。

傳送門

相關文章
相關標籤/搜索