Flutter 實現 “真” 3D 動畫效果,用純程式碼實現立體 Dash 和 3D 掘金 Logo

語言: CN / TW / HK

theme: smartblue

我正在參加「創意開發 投稿大賽」詳情請看:掘金創意開發大賽來了!

本篇將給你帶來更加炫酷動畫效果,最後教你如何通過純程式碼實現一隻立體的 Flutter 的吉祥物 Dash 和 3D 的掘金 logo 動畫

❤️ 本文正在參加徵文投稿活動,還請看官們走過路過來個點贊一鍵三連,感激不盡~

在之前的 《炫酷的 3D 卡片和帥氣的 360° 展示效果》 裡,我們使用手勢程式碼和角度切換,在 2D 畫板裡實現了“偽” 3D 的視覺效果,就在我覺得效果還不錯時, 有一位掘友提出了一個關鍵性的問題:卡片缺少厚度,也就是沒有 3D 的質感

| | | | -------------------------------------------- | ------------------------------------------------------------ |

確實,如下圖所示,在之前的實現裡,隨著卡片角度的傾斜,有兩個問題特別明顯:

  • 當卡片旋轉到側邊時,卡片的缺少“厚度”的質感,甚至出現了消失的情況
  • 卡片上的文字雖然做了類似凹凸的視覺效果,但是從側面看時也是缺少立體質感

而為了在 2D 平面實現三唯的質感,在查閱相關資料時我發現了前端的 Zdog 框架,Zdog 是一個使用 Canvas 實現的偽 3D 引擎, 它支援通過 2D 的 Canvas API渲染出類似 3D 的效果

Zdog 作為一個 js 框架,它大概只有 2800 多行程式碼,並且其最小體積為 28KB ,可以說十分輕量級。

雖然 Zdog 是一個純 js 框架, 但既然它是通過 Canvas 實現的邏輯,那就完全可以 “輕鬆” 遷移到 Flutter ,畢竟 Flutter 本身就是一個重度依賴於 Canvas 的框架,而恰巧在 Flutter 社群就有針對 Zdog 的移植版本: zflutter

雖然這個 package 作者已經兩年不維護,也沒有釋出 null-safety 的 pub 支援,但是既然是開源專案,自己動手風衣足食,在經過一番“簡單”的遷移適配之後, zflutter 再次在 Flutter 3.0 下“煥發新春”

我們先看效果,在結合 zflutter 的實現之後,可以看到卡片的立體效果得到了全面的提升:

  • 首先卡片有了厚度的質感,旋轉到側邊也不會“消失”
  • 卡片上的字型在傾斜時也有了立體的效果

那在講解實現之前,我們要解決一個疑惑: zflutter 究竟是如何在 2D 畫板上實現 3D 的質感 ?而其實這個問題的關鍵就在於:通過手勢產生的矩陣變換是作用於畫板還是作用於路徑

我們首先看一個例子,如下程式碼所示,我們建立了一個 CustomPaint ,然後在程式碼裡繪製了 4 條相同紅色直線,接著對其中 3 條直線的 Canvas 進行不同程度的矩陣旋轉,如下圖 2 可以看到有兩條紅線消失不見了:

  • 當紅線繞 Y 軸旋轉 pi / 2(90°)時,因為此時畫板恰好和我們呈垂直狀態,所以會出現看不到的情況
  • 當紅線繞 XY 軸旋轉 pi / 4 時,可以看到畫板此時和我們視覺成 45° 的情況
  • 當紅線繞 XY 軸旋轉 pi / 2(90°) 時,因為此時畫板還是和我們呈垂直狀態,所以出現看不到的情況

| | | | :----------------------------------------------------------- | ------------------------------------------------------------ |

如果覺得上面的描述太抽象,那麼結合下面動圖,可以看到當紅線在圍繞 XY 軸做旋轉時,如果畫布(Canavas)和我們呈 90° 垂直的時候,此時就會出現消失不見的情況,因為畫布是 2D 的平面,這也是為什麼之前實現的卡片沒有“厚度”的原因

| | | | --------------------------------------------- | ------------------------------------------------------------ |

那如果不對 Canavs ,而是對繪製路徑 Path 進行矩陣變換呢 ?不對畫布進行旋轉,不就不會出現消失的情況了嗎?

如下程式碼所示,同樣是圍繞 XY 軸進行旋轉,但是此時是直接對 Path 進行 path.transform 操作,也就是此時畫布Canvas 不會出現角度變換,出現變化的是繪製的 Path 路徑,可以看到:

  • 當紅線繞 Y 軸旋轉 pi / 2(90°)時,此時紅線成了紅點,因為它此時它是“頭正對著我們”
  • 當紅線繞 XY 軸旋轉 pi / 4 時,可以看到此時紅線整體成 45° 的情況對著我們
  • 當紅線繞 XY 軸旋轉 pi / 2(90°) 時,可以看到此時紅線是“垂直正對著我們”

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

結合下面的動圖,可以看到對 Path 進行矩陣變換的旋轉之後,整體的立體感就不一樣了,也就是一開始是調整我們和畫布之間的角度,但是現在我們是改變了“筆”在畫布上的繪製方式來產生的視差,這也是 zflutter 裡實現 3D 立體感的關鍵:對 Path 做矩陣運算而不只是對 Canvas

題外話,藉著這個機會順帶普及個小知識點:在前面的程式碼裡可以看到會對矩陣進行 leftTranslatetranslate 的操作 ,這是因為我們需要在不同位置繪製多條紅線,所以它們的位置並非都在起點,而使用 leftTranslatetranslate 來對矩陣進行平移,才能達到每次旋轉時都是以紅線的“中心”去旋轉,舉個例子:

  • 如圖 1 所示是紅線沒有繞 Z 軸旋轉的情況
  • 如圖 2 所示是紅線在繞 Z 軸旋轉 pi / 2 時沒有進行矩陣平移的情況,可以看到此時它們的中心點還在起始位置
  • 如圖 3 所示是紅線在繞 Z 軸旋轉 pi / 2 時,進行了 leftTranslatetranslate 操作的情況

| | | | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |

完整程式碼可見: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/transform_canvas_demo_page.dart

Web 體驗地址,PC 端記得開 Chrome 手機模式:https://guoshuyu.cn/home/web/#%E5%B1%95%E7%A4%BA%20canvas%20transform 。

那麼回到 zflutter 裡,在 zflutter 裡就是通過組合各類圖形和線條,然後利用對 Path 進行矩陣變換,從而實現類似 3D 立體的視覺效果 ,例如下面圖 2 的立體正方形,就符合我們對增加厚度的需要。

| | | | ----------------------------------------------- | ----------------------------------------- |

這裡先簡單介紹下 zflutter 裡常用物件的作用:

  • ZIllustration 類似於畫板的作用,可以配置 zoom 屬性來調整畫板的縮放
  • ZPositioned 用於配置位置和大小資訊,例如 scaletranslaterotate 等屬性(其實它就是在內部將接收到的矩陣引數配置到 ParentData ,然後傳遞給 child)
  • ZDragDetector 用於處理手勢相關資訊,主要是配置 ZPositionedrotate 就可以快速實現上面的 360° 拖拽效果
  • ZGroup 用於組合多個圖形的層疊
  • ZToBoxAdapter 用於巢狀普通的 Flutter 控制元件
  • ZRectZRoundedRectZCircleZEllipseZPolygonZConeZCylinderZHemisphere 等是內建的形狀,如下圖
  • ZShape 類似於 Canvas ,用於配合 ZMoveZLineZBezierZArc 等繪製自定義形狀

所以要實現卡片的 “真” 3D 效果,簡單來說我們需要做的是:

  • 新增一個 ZIllustration 畫布
  • 新增一個 ZDragDetector 配合 ZPositioned 用於處理手勢旋轉
  • 新增一個 ZGroup ,然後在裡面通過 ZToBoxAdapter 新增銀行卡的前後兩張 png 圖片
  • 在兩張圖片之間新增一個 ZRoundedRect 做邊框,配置顏色為 Color(0x8A000000); 實現厚度效果
  • 利用 ZShape 繪製數字,這樣繪製出現的數字就會有立體的感覺

| | | | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |

如上圖所示,可以看到經過 zflutter 的處理之後,不只是卡片本身有了“厚度”的質感,在傾斜也可以看到文字立體視覺,現在就算是如圖 3 一樣旋轉到 90° 的情況,依然可以看到卡片和文字之間的層次關係

完整程式碼可見: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/card_real_3d_demo_page.dart

Web 體驗地址,PC 端記得開 Chrome 手機模式: https://guoshuyu.cn/home/web/#%E7%A1%AC%E6%A0%B8%203D%20%E5%8D%A1%E7%89%87%E6%97%8B%E8%BD%AC 。

詳細原始碼可以直接看上方連結,那認識了 zflutter 之後,我們還能利用 zflutter做什麼呢 ?其實在官方的 Demo 裡就有一個很有典型的示例,那就是 Flutter 的吉祥物 Dash ,接下來我們看如何利用 zflutter 開始實現一隻立體質感的 Dash

首先我們利用 ZCircle 畫一個圓,用於實現 Dash 的身體

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

然後我們通過 3 個不同位置和角度的 ZEllipse 橢圓來組成 Dash 的頭髮,事實上 zflutter 裡很多效果就是通過類似這樣的圖形組合來實現的。

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

接著我們在 ZShape 裡利用 ZArc 實現不同角度的弧形組合實現尾巴,這裡的關鍵是 z 軸上需要有部分落差,如下圖展示是尾巴在 3 個不同角度的可視效果。

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

再通過調整兩個 ZEllipse 橢圓的角度來實現 Dash 的手部效果,在這一點上 zflutter 確實很考驗開發者對於圖形在平面上的空間感。

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

接著通過 ZCone 就可以快速實現 Dash 的嘴巴。

| | | | ------------------------------------------------------------ | ------------------------------------------------------------ |

然後這部分相信不用程式碼大家也知道,就是通過組合多個 ZEllipseZCircle 堆疊來實現 Dash 的眼睛。

最後,把上面的零部件組合到一起,在配置上迴圈的動畫引數,噹噹噹~一隻生動立體的 Dash 就完成了。

| | | | ----------------------------------------- | ----------------------------------------- |

完整程式碼可見: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/dash_3d_demo_page.dart

Web 體驗地址,PC 端記得開 Chrome 手機模式: https://guoshuyu.cn/home/web/#3D%20Dash 。

對比實物 Dash ,可以看到利用 zflutter 實現的 Dash ,乍看之下形似度還是蠻高的,同時 zflutter 本身也只有 82k 左右的大小,作為一個超輕量級的偽 3D 動畫框架,它在接入成本很低的情況下,儘可能做到了我們對 3D 空間所需的視覺效果,這裡面的關鍵還是在於:矩陣變換是作用於畫板還是作用於路徑

那在知道原理之後,我們接下來就可以通過三個簡單的 ZShape 組合,利用 ZMoveZLine 就能組合出具有 3D 質感的掘金 Logo ,裡面的引數直接從 SVG 的 path 對映過來就可以了

| | | | | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------- |

因為我們的矩陣旋轉改變的是 Path 而不是 Canvas ,所以 Logo 的立體效果可以通過 skroke 的粗細配合畫布 zoom 放大來體現。

完整程式碼可見: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/juejin_3d_logo_demo_page.dart

Web 體驗地址,PC 端記得開 Chrome 手機模式: https://guoshuyu.cn/home/web/#%E6%8E%98%E9%87%91%203d%20logo 。

那可能就有人要說了,這個 logo 立體感還是不夠強,因為它還是太扁平了 ~ 確實,受制於 stroke 引數的影響,在側面的立體感上確實有所缺失,而為了提升立體感,我們可以通過 zflutter 裡的 ZBoxToBoxAdapter 來實現。

在 zflutter 裡, ZBoxToBoxAdapter 可以通過配置 frontrearleftrighttopbottom 等引數來配置長方體每個面的 UI,並且它本身就會根據 widthheightdepth 引數生成一個立體長方形,如下圖 1所示。

| | | | | ------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------- |

接著我們簡單通過圖 2 的量角器確定掘金 logo 的角度,然後如下程式碼所示,利用不同位置和角度,通過 ZBoxToBoxAdapter 組合堆疊不同的長方體,從而形成如上圖 3 所示的立體掘金 logo,當然,這個組合過程很明顯是體力活

| | | | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |

完整程式碼可見: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/juejin_3d_box_logo_demo_page.dart

Web 體驗地址,PC 端記得開 Chrome 手機模式: https://guoshuyu.cn/home/web/#%E6%8E%98%E9%87%91%E6%9B%B4%203d%20logo 。

可以看到 zflutter 雖然沒有之前 用 rive 給掘金 Logo 快速新增動畫效果 來的強大和方便,但是好在它體積夠小,不需要載入任何資源,純程式碼就可以實現各種立體的 3D 動畫效果 ,這對於程式設計師來說更加可控,至少它不需要依賴於任何第三方設計工具,就是開發速度上確實不如 rive 來的高效,需要一定的空間想象力

好了,本篇動畫特效就到此為止,如果你有什麼想法,歡迎留言評論,感謝大家耐心看完,也還請看官們走過路過的來個點贊一鍵三連,感激不盡