原文链接 : Perspective on Flutter
Flutter 中的 Transform 可以实现许多酷炫的动画效果,在本篇文章中,将展示如何使用 Transfrom 来实现 3D 透视旋转效果,下面示例的效果用 Flutter 很容易实现,但是如果用原生组件来实现这个效果可能就相对来说要困难一点。
1、使用 Transform 实现 3D 效果
以创建 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 效果。
为了出于演示的目的,将默认的布局代码通过 _defaultApp 方法进行了封装,然后仅仅是通过 Transfrom 来实现 3D 效果。
2、Transform widget 介绍
上面代码中,通过 Transfrom 来实现透视效果,而 Transfrom 是通过 Matrix4 进行矩阵变换来实现的这个效果。
由于现在的智能手机都有用于图形计算的 GPU 单元,对于图形的计算与渲染进行了优化,因此即使是渲染 3D 图形也是非常快的。因此,基本上你看到的手机上的所有图形,都是通过 3D 的渲染方式来呈现的,即使是 2D 的图形素材。
通过设置变换矩阵,可以改变我们看到的视觉效果(甚至是 3D 效果)。通常来讲,矩阵变换包括: 平移、旋转、缩放、透视。上面代码中,我们通过 identity_matrix 创建了一个矩阵,然后应用给 Transform 。需要注意的是,矩阵变换不满足交换律,因此参数的位置要弄对,当传入矩阵之后,最后的矩阵运算结果会传递给 GPU ,然后对图像进行渲染。
矩阵运算是一门非常复杂的学科,如果想继续了解相关知识,请参考其他的资料。
3、透视效果的实现
上面代码实现了透视的效果,也就是,更远的部分,应该看起来更小一些。因此上面的参数里面,会根据距离进行 0.001 的缩放。
那么 0.001 这个参数是怎么来的?其实这个数据很随意,可以把这个数据增大或者减小看一下效果,这个数据越大,展现的效果就好像是我们越来越靠近观察对象。
Flutter 也提供了一个 makePerspectiveMatrix 方法进行透视矩阵变换,但是这个方法需要设置一下额外的参数,这些参数我们远远用不到,因此直接使用 matrix 来完成矩阵变换即可。
同时,上面的代码通过 _offset 来指定了 x 轴和 轴的旋转。
4、手势交互
直接通过 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),
),
);
}
}
复制代码
上面的手势交互只有两种:
- DoubleTap : 双击重置
- onPanUpdate : 移动手指,旋转图像。
5、进阶实战-翻页效果
接下来实现的效果相对复杂一点,类似翻页效果动画。
初步设计
第一眼看到这个效果,可能想到的就是,通过 Stack 来实现,并且每一页都分成上下两部分,每一部分可以绕 X 轴旋转,旋转之后就会看到下一个页面。
那么该如何用代码来实现呢?可以分成两部分来进行。
- 将一个页面分成两部分
- 将其中的一部分绕 X 轴旋转。
那么,在 Flutter 中,什么样的 Widget 适合我们来实现这个效果呢?ClipRect 和 Transform 。
实现
- 将一个页面分成两部分 ClipRect 这个组件有一个参数: clipper,这个参数可以定义裁剪的矩形区域的大小和位置,但是官方文档建议我们通过另一种方式来使用 ClipRect,那就是结合 Align 来使用。
接下来定义一个 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 编程开发」微信公众号 。