Flutter 3D 动画

原文连接 : Perspective on Fluttergit

Flutter 中的 Transform 能够实现许多酷炫的动画效果,在本篇文章中,将展现如何使用 Transfrom 来实现 3D 透视旋转效果,下面示例的效果用 Flutter 很容易实现,可是若是用原生组件来实现这个效果可能就相对来讲要困难一点。github

一、使用 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 效果。bash

为了出于演示的目的,将默认的布局代码经过 _defaultApp 方法进行了封装,而后仅仅是经过 Transfrom 来实现 3D 效果。微信

二、Transform widget 介绍

上面代码中,经过 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),
      ),
    );
  }

}
复制代码

上面的手势交互只有两种:

  • DoubleTap : 双击重置
  • onPanUpdate : 移动手指,旋转图像。

五、进阶实战-翻页效果

接下来实现的效果相对复杂一点,相似翻页效果动画。

初步设计

第一眼看到这个效果,可能想到的就是,经过 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
复制代码

最终实现效果:


github


最后

欢迎关注「Flutter 编程开发」微信公众号 。

相关文章
相关标签/搜索