iOS 動畫 - 窗景篇(三·完結)

這篇文章是系列文章的第三篇。git

看過上一篇文章的朋友,已經知道標題中的「景」指代 view,「窗」指代 view.mask,窗景篇就是在梳理 mask 及 mask 動畫。若是你還不熟悉 iOS 的 mask,建議先看一下第一篇github

前兩篇咱們介紹了 mask、mask 動畫的一些用法。swift

這一篇做爲收尾,咱們來實現一個效果練練手, 也借這個效果,讓你們回憶起一個簡單的道理:複雜的效果,能夠等價於簡單效果的組合。oop

1、效果

這個效果以下面的動圖所示:post

咱們截取比較有表明性的一幀,以下圖所示:性能

從圖中能夠看到,波浪由兩種顏色組成,各部分顏色不一樣。動畫

這個效果看上去有點複雜,若是不熟悉 mask,可能一時半會兒沒有思路。 但看過前兩篇的朋友,可能已經暗暗在想,是窗在動?仍是景在動?會不會有多套窗景?ui

那麼接下來,咱們先經過一個簡單的效果來看一下原理。spa

注:波浪動畫的實現和本文關係不大,本文不會講述。 網上有成熟的波浪動畫的教程,本文 demo 中 WaveView 類也有簡要的註釋。code

2、一個簡單的效果

這個效果以下圖所示:

從圖中能夠看到,一張黑白圖片上有一部分是彩色的。 咱們固然能夠經過圖像處理來實現這個效果,但在本文中,咱們仍是使用 mask 的方式來實現。

咱們回憶一下前文中的一張圖:

經過對frontView 添加一個圓 mask,就造成了圖中的效果。

也許有的朋友已經想到,把上圖中 backView 的圖換成和 frontView 同樣的黑白圖片,不就是本例的效果嗎,以下圖所示:

也就是說,這個效果看上去是黑白圖片上有一部分變成了彩色, 但其實只是兩張內容同樣的圖片重疊,黑白圖片在後,彩色圖片在前,而前方的彩色圖片,被施加了圓形的 mask。

這個效果很簡單,但能讓咱們意識到一件事:看上去是一張圖,其實多是多張圖組合而成。

既然如此,那本文的波浪動畫中,各部分顏色不一樣的波浪,真的只是一個波浪嗎?

沒錯,本例中的波浪,也是多個波浪組合而成的,接下來,咱們就詳細的看一看。

3、多景合一

和黑白、彩色圖片重疊效果同樣,多色波浪也是由一組重疊的波浪 view 組合而成。

每層 view 的波浪只有一種顏色,各層 view 的波浪動畫都一致,對於每一幀,全部波浪都是徹底重合的。

每一個波浪 view 都有本身的 mask,在 mask 們 的控制下,每層波浪 view 只顯示了波浪的一部分,咱們看到的多色波浪,就是各層波浪 view 可見部分的組合。

咱們取兩層來示意一下,以下圖所示:

從圖中能夠看到,白底紅波浪 view 有個上半圓 mask、黑底藍波浪 view 有個下半圓 mask,兩個 view 的波浪進度徹底一致,組合以後就成了最右邊的的效果。

捅破了這層窗戶紙後,其實原理就是如此簡單。

知道了原理,其實你們能夠本身去動手去實現效果了, 固然,若是不着急的話,那我們一塊把流程走一遍。

4、建立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)
}
複製代碼

5、爲 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
}

複製代碼

6、讓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 庫 裏都有完整的代碼。

本系列至此完結,感謝您的閱讀。

傳送門

相關文章
相關標籤/搜索