這篇文章是系列文章的第三篇。
看過上一篇文章的朋友,已經知道標題中的“景”指代 view,“窗”指代 view.mask,窗景篇就是在梳理 mask 及 mask 動畫。如果你還不熟悉 iOS 的 mask,建議先看一下第一篇。
前兩篇我們介紹了 mask、mask 動畫的一些用法。
這一篇作為收尾,我們來實現一個效果練練手,
也借這個效果,讓大家回憶起一個簡單的道理:復雜的效果,可以等價于簡單效果的組合。
一、效果
這個效果如下面的動圖所示:
我們截取比較有代表性的一幀,如下圖所示:
從圖中可以看到,波浪由兩種顏色組成,各部分顏色不同。
這個效果看上去有點復雜,如果不熟悉 mask,可能一時半會兒沒有思路。
但看過前兩篇的朋友,可能已經暗暗在想,是窗在動?還是景在動?會不會有多套窗景?
那么接下來,我們先通過一個簡單的效果來看一下原理。
注:波浪動畫的實現和本文關系不大,本文不會講述。
網上有成熟的波浪動畫的教程,本文 demo 中 WaveView 類也有簡要的注釋。
二、一個簡單的效果
這個效果如下圖所示:
從圖中可以看到,一張黑白圖片上有一部分是彩色的。
我們當然可以通過圖像處理來實現這個效果,但在本文中,我們還是使用 mask 的方式來實現。
我們回憶一下前文中的一張圖:
通過對frontView 添加一個圓 mask,就形成了圖中的效果。
也許有的朋友已經想到,把上圖中 backView 的圖換成和 frontView 一樣的黑白圖片,不就是本例的效果嗎,如下圖所示:
也就是說,這個效果看上去是黑白圖片上有一部分變成了彩色,
但其實只是兩張內容一樣的圖片重疊,黑白圖片在后,彩色圖片在前,而前方的彩色圖片,被施加了圓形的 mask。
這個效果很簡單,但能讓我們意識到一件事:看上去是一張圖,其實可能是多張圖組合而成。
既然如此,那本文的波浪動畫中,各部分顏色不同的波浪,真的只是一個波浪嗎?
沒錯,本例中的波浪,也是多個波浪組合而成的,接下來,我們就詳細的看一看。
三、多景合一
和黑白、彩色圖片重疊效果一樣,多色波浪也是由一組重疊的波浪 view 組合而成。
每層 view 的波浪只有一種顏色,各層 view 的波浪動畫都一致,對于每一幀,所有波浪都是完全重合的。
每個波浪 view 都有自己的 mask,在 mask 們 的控制下,每層波浪 view 只顯示了波浪的一部分,我們看到的多色波浪,就是各層波浪 view 可見部分的組合。
我們取兩層來示意一下,如下圖所示:
從圖中可以看到,白底紅波浪 view 有個上半圓 mask、黑底藍波浪 view 有個下半圓 mask,兩個 view 的波浪進度完全一致,組合之后就成了最右邊的的效果。
捅破了這層窗戶紙后,其實原理就是如此簡單。
知道了原理,其實大家可以自己去動手去實現效果了,
當然,如果不著急的話,那咱們一塊把流程走一遍。
四、創建4層 view
這一步很簡單,創建 frame 完全一致的 4 層 view,本例中使用 WaveView 作為 view,
根據需要,設置不同的背景色(黑、白)和波浪色(紅、藍)。
這一步后,4 層波浪 view 如圖所示:
示意代碼如下:
// A3WaveViewController
private func addSubViews() {
// 上大半圓 view
view.addSubview(bigTopView)
// 上小半圓 view
view.addSubview(smallTopView)
// 下大半圓 view
view.addSubview(bigBottomView)
// 下小半圓 view
view.addSubview(smallBottomView)
}
五、為 4 層 view 設置 mask
這一步就是做出合適的 mask 。
本例中使用 HalfCircleView 作為 mask,分別為各層 view 設置兩個大半圓和兩個小半圓的 mask。
這一步后,4層 view 在 mask 的影響下如下圖所示,大家可以和前文圖中的 4個 view 對照著看:
示意代碼如下:
// A3WaveViewController
private func makeLayout() {
// 設置4個 view 的 mask
// 大上半圓
let width: CGFloat = 200
let marginX = (UIScreen.main.bounds.width - width) / 2
bigTopView.frame = CGRect(x: marginX, y: 200, width: width, height: 200)
// 大上半圓 mask
let bigTopMask = HalfCircleView()
bigTopMask.part = .top
bigTopMask.frame = CGRect(x: 0, y: 0, width: bigTopView.bounds.width, height: bigTopView.bounds.height / 2)
bigTopView.mask = bigTopMask
// 小上半圓(半徑是大半圓的一半)
smallTopView.frame = bigTopView.frame
// 小上半圓 mask
let smallTopMask = HalfCircleView()
smallTopMask.part = .top
smallTopMask.frame = CGRect(x: smallTopView.bounds.width / 4,
y: smallTopView.bounds.height / 4,
width: smallTopView.bounds.width / 2,
height: smallTopView.bounds.height / 4)
smallTopView.mask = smallTopMask
// 大下半圓
bigBottomView.frame = bigTopView.frame
// 大下半圓 mask
let bigBottomMask = HalfCircleView()
bigBottomMask.part = .bottom
bigBottomMask.frame = CGRect(x: 0,
y: bigBottomView.bounds.height / 2,
width: bigBottomView.bounds.width,
height: bigBottomView.bounds.height / 2)
bigBottomView.mask = bigBottomMask
// 小下半圓
smallBottomView.frame = bigBottomView.frame
// 小下半圓 mask
let smallBottomMask = HalfCircleView()
smallBottomMask.part = .bottom
smallBottomMask.frame = CGRect(x: smallBottomView.bounds.width / 4,
y: smallBottomView.bounds.height / 2,
width: smallBottomView.bounds.width / 2,
height: smallBottomView.bounds.height / 4)
smallBottomView.mask = smallBottomMask
}
六、讓4層 view 一致地執行動畫
為了讓各層波浪動畫完全一致,我們在外部啟動一個 CADisplayLink,來同時改變 4 個波浪 view 的 progress(前文圖中使用了 progress 為 0.5 時作為示例)。
也就實現了本篇開始的效果:
示意代碼如下:
// A3WaveViewController
func start() {
if let displayLink = displayLink {
displayLink.invalidate()
self.displayLink = nil
progress = 0
}
// 啟動 CADisplayLink
let displayLink = CADisplayLink(target: WeakProxy(self), selector: #selector(work))
displayLink.add(to: RunLoop.current, forMode: .common)
self.displayLink = displayLink
}
@objc private func work() {
if progress < 1 {
progress += 0.0025
progress = min(progress, 1)
} else {
progress = 0
}
// CADisplayLink 回調時,設置4個波浪 view 的 progress
bigTopView.progress = progress
bigBottomView.progress = progress
smallTopView.progress = progress
smallBottomView.progress = progress
}
至此,效果就完成了。
當然,你可以用自己的動畫 view 替換掉 WaveView、改變 view 的個數、使用其他的 mask等,
來實現自己想要的拼接效果。
當然,這種動畫方式的性能未評估,也許不適合用在生產環境。
這個例子更多地是想讓大家回憶起一個簡單的道理:
復雜的動畫,可以等價為簡單動畫的組合;只要還覺得復雜,就可以繼續拆分。
如果你想要拆分,覺得不知道從何處下手,那么可以這么嘗試:
只要某一部分的動畫和其他部分有區別,就可以拆分。
簡單的動畫一旦實現了,組合起來了就完成了復雜的動畫。
組合是我們常用的方法,比如下圖的雙波浪:
我們當然可以直接寫個雙波浪的類,但也可以組合兩個波浪 view 來形成雙波浪。
組合看上去有點傻,不過也有它的優勢,
比如我們想復用的效果很復雜,難以改寫,或者我們根本沒有該效果的源碼時,組合可能就是最簡單的方式。
尾聲
本系列只是展示了常見的 mask 效果,掛一漏萬,畢竟窗無限,景無限,組合無限,效果無限。
如果你發現了有意思的 mask 動畫,歡迎在評論區留言,愿我們共同進步。
本文所有示例,在 GitHub 庫 里都有完整的代碼。
本系列至此完結,感謝您的閱讀。