原文连接 : 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 编程开发」微信公众号 。