原文連接 : Perspective on Fluttergit
Flutter 中的 Transform 能夠實現許多酷炫的動畫效果,在本篇文章中,將展現如何使用 Transfrom 來實現 3D 透視旋轉效果,下面示例的效果用 Flutter 很容易實現,可是若是用原生組件來實現這個效果可能就相對來講要困難一點。github
以建立 Flutter 項目默認生成的代碼爲例來展現 3D 透視效果。先經過 Transform 來實現 3D 效果。代碼以下:編程
// v1: move default app to separate function with fixed name
// Add transform widget, rotate and perspective
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Perspective',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key); // changed
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
Offset _offset = Offset(0.4, 0.7); // new
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Transform( // Transform widget
transform: Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateX(_offset.dy)
..rotateY(_offset.dx),
alignment: FractionalOffset.center,
child: _defaultApp(context),
);
}
_defaultApp(BuildContext context) { // new
return Scaffold(
appBar: AppBar(
title: Text('The Matrix 3D'), // changed
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
複製代碼
運行上面的代碼,將會呈現稍微有一些旋轉角度的 3D 效果。bash
爲了出於演示的目的,將默認的佈局代碼經過 _defaultApp 方法進行了封裝,而後僅僅是經過 Transfrom 來實現 3D 效果。微信
上面代碼中,經過 Transfrom 來實現透視效果,而 Transfrom 是經過 Matrix4 進行矩陣變換來實現的這個效果。app
因爲如今的智能手機都有用於圖形計算的 GPU 單元,對於圖形的計算與渲染進行了優化,所以即便是渲染 3D 圖形也是很是快的。所以,基本上你看到的手機上的全部圖形,都是經過 3D 的渲染方式來呈現的,即便是 2D 的圖形素材。less
經過設置變換矩陣,能夠改變咱們看到的視覺效果(甚至是 3D 效果)。一般來說,矩陣變換包括: 平移、旋轉、縮放、透視。上面代碼中,咱們經過 identity_matrix 建立了一個矩陣,而後應用給 Transform 。須要注意的是,矩陣變換不知足交換律,所以參數的位置要弄對,當傳入矩陣以後,最後的矩陣運算結果會傳遞給 GPU ,而後對圖像進行渲染。ide
矩陣運算是一門很是複雜的學科,若是想繼續瞭解相關知識,請參考其餘的資料。佈局
上面代碼實現了透視的效果,也就是,更遠的部分,應該看起來更小一些。所以上面的參數裏面,會根據距離進行 0.001 的縮放。優化
那麼 0.001 這個參數是怎麼來的?其實這個數據很隨意,能夠把這個數據增大或者減少看一下效果,這個數據越大,展示的效果就好像是咱們愈來愈靠近觀察對象。
Flutter 也提供了一個 makePerspectiveMatrix 方法進行透視矩陣變換,可是這個方法須要設置一下額外的參數,這些參數咱們遠遠用不到,所以直接使用 matrix 來完成矩陣變換便可。
同時,上面的代碼經過 _offset 來指定了 x 軸和 軸的旋轉。
直接經過 GestureDetector 來實現手勢交互。
// v2: add Gesture detector
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Perspective',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key); // changed
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
Offset _offset = Offset.zero; // changed
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Transform( // Transform widget
transform: Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateX(0.01 * _offset.dy) // changed
..rotateY(-0.01 * _offset.dx), // changed
alignment: FractionalOffset.center,
child: GestureDetector( // new
onPanUpdate: (details) => setState(() => _offset += details.delta),
onDoubleTap: () => setState(() => _offset = Offset.zero),
child: _defaultApp(context),
)
);
}
_defaultApp(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('The Matrix 3D'), // changed
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
複製代碼
上面的手勢交互只有兩種:
接下來實現的效果相對複雜一點,相似翻頁效果動畫。
第一眼看到這個效果,可能想到的就是,經過 Stack 來實現,而且每一頁都分紅上下兩部分,每一部分能夠繞 X 軸旋轉,旋轉以後就會看到下一個頁面。
那麼該如何用代碼來實現呢?能夠分紅兩部分來進行。
那麼,在 Flutter 中,什麼樣的 Widget 適合咱們來實現這個效果呢?ClipRect 和 Transform 。
接下來定義一個 Widget 來實現這個功能。
class FlipWidget extends StatelessWidget {
Widget child;
FlipWidget({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ClipRect(
child: Align(
alignment: Alignment.topCenter,
heightFactor: 0.5,
child: child,
)),
Padding(
padding: EdgeInsets.only(top: 2.0),
),
ClipRect(
child: Align(
alignment: Alignment.bottomCenter,
heightFactor: 0.5,
child: child,
)),
],
);
}
}
複製代碼
這裏面的 child 參數,能夠傳遞任意類型的 Widget(text,image 等)。 運行上面的代碼,能夠看到以下的效果。
Transform 這個 Widget 組件有一個 Matrix4 類型的參數 transform,這個參數決定了咱們將應用何種類型的矩陣變換。同時,Matrix4 提供了一個名字爲 rotationX() 的構造方法,這個彷佛正是咱們須要的,咱們把這個應用給頁面的上半部分試一下。
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Transform(
transform: Matrix4.rotationX(pi / 4),
alignment: Alignment.bottomCenter,
child: ClipRect(
child: Align(
alignment: Alignment.topCenter,
heightFactor: 0.5,
child: child,
)),
),
...
],
);
}
複製代碼
運行上面的代碼。
顯然,這個效果僅僅是把上半部分縮小了,不是咱們想要的效果。可是若是額外再指定 Matrix4 的參數,讓 row 爲 3,column 爲 2,試一下效果。
Transform(
transform: Matrix4.identity()..setEntry(3, 2, 0.006)..rotateX(pi / 4),
alignment: Alignment.bottomCenter,
child: ClipRect(
child: Align(
alignment: Alignment.topCenter,
heightFactor: 0.5,
child: child,
)),
),
...
複製代碼
看起來這個是咱們須要的效果,上面還一個參數,0.006,這個是怎麼來的?實際上是試出來的,選一個本身感受不錯的數值就好了😂。
接下來就是給翻轉加上動畫了。可是這塊可能相對複雜一點。首先,每一頁都要理解爲有兩面(正反面),可是要實現這個效果用代碼可能不是很容易,由於咱們在手機上看到的圖像在任什麼時候刻都只有一面。
咱們假設,咱們是向上翻轉的,那麼咱們的動畫能夠分紅兩部分,第一部分是咱們將下半部分向上翻轉一半時,這個過程的效果是,當前翻轉的頁面逐漸消失,而這個頁面的下一個頁面會逐漸顯示。第二部分是,將當前頁面繼續向上翻轉,這個過程的效果是,當前頁面會逐漸顯示,上半部分的當前頁面就是逐漸消失。
這個效果的實現,代碼很是多,更詳細的代碼請參考:
https://gist.github.com/hnvn/f1094fb4f6902078516cba78de9c868e
複製代碼
最終實現效果:
歡迎關注「Flutter 編程開發」微信公衆號 。