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 編程開發」微信公衆號 。

相關文章
相關標籤/搜索