Flutter 繪製3D效果動畫
theme: v-green highlight: atom-one-dark
攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第3天,點選檢視活動詳情
前言
本篇我們繼續介紹 Flutter
繪圖的 Path
的應用。Flutter 的 Path
類提供了一個三維空間的變換方法,可以實現路徑在三維空間的平移、旋轉等操作,從而可以實現3D 繪製的效果。通過本篇你將瞭解到:
Path
的三維轉換方法transform
的使用。- 繞三維空間某一點的旋轉實現。
- 卡片3D 旋轉動效。
- 類似日曆的三維翻頁效果。
Path 的 transform 方法
Path
類的 transform
方法 將給定的Path
通過一個Float64List
的物件進行三維變換,然後返回變換後的 Path
物件,方法定義如下。
dart
Path transform(Float64List matrix4) {
assert(_matrix4IsValid(matrix4));
final Path path = Path._();
_transform(path, matrix4);
return path;
}
其中 Float64List
一般都是通過 Matrix4
物件的 storage
得到,例如我們在 x 方向平移5.0,可以按如下方式得到對應的 Float64List
物件。
dart
var transform = (Matrix4.identity()
..translate(5.0, 0.0, 0.0)).storage;
Matrix4
提供了平移、旋轉、逆矩陣等多種方法,有興趣的可以看一下 Matrix4
的原始碼,實際上就是大學線性代數課(這門課還挺難的😂)的矩陣乘法內容。
繞任意點旋轉
網上關於繞任意點的旋轉推導很多,這裡就不再贅述,結論就是實際上三個矩陣,先按給定點的(x,y,z)平移,再按給定的角度旋轉,再按給定點的反向(-x,-y,-z)平移。比如下面是圍繞 point 點,在 X 軸方向旋轉 angle 角度的變換程式碼。
dart
var transform = Matrix4.identity()
..translate(point.dx, point.dy, point.dz)
..rotateX(angle)
..translate(-point.dx, -point.dy, -point.dz);
卡片3D 旋轉實現
有了上面的基礎,我們就可以實現卡片的3D旋轉效果了。 這個實際就是用 Path 繪製了一個實心的正方形,然後繞中心點同時在 X 軸和 Y 軸旋轉,旋轉的角度由動畫來控制。然後在動畫值的中間的變更顏色,就看起來像是兩面了。具體實現的程式碼如下。 ```dart var paint = Paint() ..style = PaintingStyle.fill ..color = Colors.blue[400]! ..strokeWidth = 4.0;
var center = Offset(size.width / 2, size.height / 2); var path = Path(); final rectSize = 100.0; path.addRect(Rect.fromCenter( center: Offset(center.dx, center.dy), width: rectSize, height: rectSize)); var transform = Matrix4.identity() ..translate(center.dx, center.dy, 0.0) ..rotateX(pi * animationValue) ..rotateY(pi * animationValue) ..translate(-center.dx, -center.dy, 0.0);
var transformedPath = path.transform(transform.storage); if (animationValue < 0.5) { paint.color = Colors.blue[400]!; } else { paint.color = Colors.red; } canvas.drawPath(transformedPath, paint); ``` 我們還可以繞 Z 軸旋轉來看看效果。
日曆翻頁效果
老的日曆通常是掛在牆上,過了一天就把這一天的翻上去。 觀察上面的圖,下面的部分是矩形,上面翻上去的會有一個曲度,這個我們可以通過貝塞爾曲線來實現。然後,翻頁過程其實就是從下面繞中間位置旋轉島上面的過程,只是在旋轉過程中需要同時更改繪製的路徑,逐步從矩形過渡到帶有曲度的形狀。
- 下半部分繪製
下半部分繪製比較簡單,我們為了體現日曆的厚度,可以繪製多個高度錯開的矩形,並且顏色有點偏差,看起來就像有厚度了。
繪製程式碼如下,這裡有兩個關鍵點,一個是每次繪製的矩形會往下偏和往右偏移一定的位置,另一個是更改繪製顏色的透明度,這樣就會有厚度的感覺了。
dart
var bottomPath = Path();
for (var i = 0; i < 10; ++i) {
bottomPath.addRect(Rect.fromCenter(
center: Offset(
size.width / 2 + i / 1.5, center.dy + rectSize / 2 + i * 1.5),
width: rectSize,
height: rectSize));
paint.color = Colors.white70.withAlpha(240 + 10 * i);
canvas.drawPath(bottomPath, paint);
- 上半部分的繪製
上半部分我們的側邊繪製一定的曲度,這樣看著像翻過後捲起來的感覺。因為有部分捲起來了,因此高度會比下半部分低一些,曲度我們通過貝塞爾曲線控制,繪製的程式碼如下,這裡有兩個常量,一個是 topHeight
代表上半部分的高度,一個是 flippedSize
,用於控制貝塞爾曲線的曲度。
```dart
final topHeight = 90.0;
final flippedSize = -10.0;
var topPath = Path(); topPath.moveTo(center.dx - rectSize / 2, center.dy); topPath.lineTo(center.dx + rectSize / 2, center.dy); topPath.quadraticBezierTo( center.dx + rectSize / 2 + flippedSize, center.dy - topHeight / 2, center.dx + rectSize / 2, center.dy - topHeight); topPath.lineTo(center.dx - rectSize / 2, center.dy - topHeight); topPath.quadraticBezierTo(center.dx - rectSize / 2 + flippedSize, center.dy - topHeight / 2, center.dx - rectSize / 2, center.dy); canvas.drawPath(topPath, paint); ``` 繪製的效果如下,看起來就有日曆的感覺了。
- 翻頁動效繪製
翻頁動效實際上就是再畫一個 Path,這個物件在動畫過程中逐步從矩形轉換為上半部分的圖形,同時通過旋轉動效翻轉上去 —— 也就是其實我們繪製的是下半部分,只是通過旋轉翻上去實現翻頁的動效。實現的程式碼如下,主要的邏輯為:
- 下邊緣的Y 軸位置在
animationValue = 0.0
的時候等於下半部分的下邊緣Y 軸的位置(rectSize
),在animationValue = 1.0
的時候等於上半部分的上邊緣Y 軸相對中心點對稱位置的,即center.dy + topHeight
,因此得到高度變化的計算程式碼如下面第2行程式碼所示。這裡增加了一些小的偏移,主要是為了和上下部分有點偏移量,這樣能夠將翻頁和其他部分割槽分開。 - 左右兩側的曲度一開始是0,直到翻到中間位置後才顯示,這個通過第3到第6行控制,當
animationValue < 0.5
的時候,aniamtedFlippedSize
一直是0,即貝塞爾的控制點和起止點在同一條直線上,這樣就不會有曲度了,等到animationValue > 0.5
後,曲度跟隨animationValue
變化,最終和上半部分的曲度保持一致,這樣旋轉上去後能夠重合。 - 旋轉採用上面我們說的繞任意一點旋轉的方式實現,這裡我們是繞螢幕的中心,繞 X軸旋轉,角度範圍是
0-180
度。 - 最後是我們更改了翻頁的顏色,這個主要是能夠通過顏色區分,如果是相同的顏色的話就分不太出來了。 ```dart var flippedPath = Path(); var endY = rectSize - 2 + (topHeight - 1 - rectSize) * animationValue; var animatedFlippedSize = 0.0; if (animationValue > 0.5) { animatedFlippedSize = flippedSize * animationValue; } var offsetX = (1 - animationValue) * 4.0; flippedPath.moveTo(center.dx - rectSize / 2, center.dy); flippedPath.lineTo(center.dx + rectSize / 2, center.dy); flippedPath.quadraticBezierTo( center.dx + rectSize / 2 + animatedFlippedSize - offsetX, center.dy + endY / 2, center.dx + rectSize / 2 - offsetX, center.dy + endY);
flippedPath.lineTo(center.dx - rectSize / 2 - offsetX, center.dy + endY); flippedPath.quadraticBezierTo( center.dx - rectSize / 2 + animatedFlippedSize, center.dy + endY / 2, center.dx - rectSize / 2, center.dy); var transform = Matrix4.identity() ..translate(center.dx, center.dy, 0.0) ..rotateX(pi * animationValue) ..translate(-center.dx, -center.dy, 0.0); var transformedPath = flippedPath.transform(transform.storage); if (animationValue < 0.5) { paint.color = Colors.white; } else { paint.color = Colors.green[300]!; } canvas.drawPath(transformedPath, paint); ``` 最終的實現效果如下所示。
總結
本篇介紹了Flutter 繪圖中的 Path
類的三維空間變換方法和應用,可以看到,基於三維變換可以實現3D效果圖形的繪製和實現3D 動效,這在有些特殊繪製的場景中或增添趣味性十分有用。
本篇原始碼已上傳至:繪圖相關程式碼,檔名為:path_matrix_demo.dart
。
我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,提供體系化的 Flutter 學習文章。對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼。如有問題可以加本人微信交流,微訊號:
island-coder
。👍🏻:覺得有收穫請點個贊鼓勵一下!
🌟:收藏文章,方便回看哦!
💬:評論交流,互相進步!
- 屌炸天!國外同行這樣用Chat GPT提高Flutter開發的效率!
- Flutter 增強版的頁面懸浮按鈕(FloatingActionButton)
- 介紹一個令強迫症討厭的小紅點元件
- 我用了幾行程式碼就搞定了介面變灰效果
- 不就是一個空白頁,有必要那麼講究嗎?
- 花裡胡哨的文字特效,你學會了嗎?
- 這一篇讓你搞定 Flutter 的資料表格
- 用 Flutter 輕鬆做個紅包介面
- 沉浸式彈層越來越多,開發該怎麼做好彈層體驗?
- 列表的載入過程很重要的!
- 這個表單打死我也不填!
- 例項講述開發中的圖片使用者體驗要點
- C 位出道按鈕的自我獨白
- Flutter 繪製3D效果動畫
- 封裝一個有趣的 Loading 元件
- 普通的載入千篇一律,有趣的 loading 萬里挑一
- 由點匯聚成字的動效炫極了
- 給滅霸點顏色看看
- 來看光影流動之美
- Flutter 實現背景圖片毛玻璃效果