Flutter 動畫與打包

動畫與打包

動畫

? Flutter中的動畫系統(tǒng)基于Animation對象的,和之前的手勢不同,它不是一個Widget,這是因為Animation對象本身和UI渲染沒有任何關(guān)系。Animation是一個抽象類,就相當于一個定時器,它用于保存動畫的插值和狀態(tài),并執(zhí)行數(shù)值的變化。widget可以在build函數(shù)中讀取Animation對象的當前值, 并且可以監(jiān)聽動畫的狀態(tài)改變。

AnimationController

? AnimationController用于控制動畫,它包含動畫的啟動forward()、停止stop() 、反向播放 reverse()等方法。AnimationController會在動畫的每一幀,就會生成一個新的值。默認情況下,AnimationController在給定的時間段內(nèi)線性的生成從0.0到1.0(默認區(qū)間)的數(shù)字。

AnimationController controller = AnimationController( 
 duration: const Duration(milliseconds: 2000), //動畫時間
 lowerBound: 10.0,  //生成數(shù)字的區(qū)間 
 upperBound: 20.0,  //10.0 - 20.0
 vsync: this  //TickerProvider 動畫驅(qū)動器提供者
);

Ticker

? Ticker的作用是添加屏幕刷新回調(diào),每次屏幕刷新都會調(diào)用TickerCallback。使用Ticker來驅(qū)動動畫會防止屏幕外動畫(動畫的UI不在當前屏幕時,如鎖屏時)消耗不必要的資源。因為Flutter中屏幕刷新時會通知Ticker,鎖屏后屏幕會停止刷新,所以Ticker就不會再觸發(fā)。最簡單的做法為將SingleTickerProviderStateMixin添加到State的定義中。

import 'package:flutter/material.dart';

void main() => runApp(AnimationApp());

class AnimationApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "animation",
      home: Scaffold(
        appBar: AppBar(
          title: Text('animation'),
        ),
        body: AnimWidget(),
      ),
    );
  }
}

// 動畫是有狀態(tài)的
class AnimWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _AnimWidgetState();
  }
}

class _AnimWidgetState extends State<AnimWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  bool forward = true;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      // 動畫的時長
      duration: Duration(milliseconds: 2000),
      lowerBound: 10.0,
      upperBound: 100.0,
      // 提供 vsync 最簡單的方式,就是直接混入 SingleTickerProviderStateMixin
      // 如果有多個AnimationController,則使用TickerProviderStateMixin。
      vsync: this,
    );
    //狀態(tài)修改監(jiān)聽
    controller
      ..addStatusListener((AnimationStatus status) {
        debugPrint("狀態(tài):$status");
      })
      ..addListener(() {
        setState(() => {});
      });

    debugPrint("controller.value:${controller.value}");
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          width: controller.value,
          height: controller.value,
          color: Colors.blue,
        ),
        RaisedButton(
          child: Text("播放"),
          onPressed: () {
            if (forward) {
              controller.forward();
            } else {
              controller.reverse();
            }
            forward = !forward;
          },
        ),
        RaisedButton(
          child: Text("停止"),
          onPressed: () {
            controller.stop();
          },
        )
      ],
    );
  }
}

動畫狀態(tài)監(jiān)聽:在forword結(jié)束之后狀態(tài)為completed。在reverse結(jié)束之后狀態(tài)為dismissed

Tween

? 默認情況下,AnimationController對象值為:double類型,范圍是0.0到1.0 。如果我們需要不同的范圍或不同的數(shù)據(jù)類型,則可以使用Tween來配置動畫以生成不同的范圍或數(shù)據(jù)類型的值。要使用Tween對象,需要調(diào)用其animate()方法,然后傳入一個控制器對象,同時動畫過程中產(chǎn)生的數(shù)值由Tweenlerp方法決定。

import 'package:flutter/material.dart';

void main() => runApp(AnimationApp());

class AnimationApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "animation",
      home: Scaffold(
        appBar: AppBar(
          title: Text('animation'),
        ),
        body: AnimWidget(),
      ),
    );
  }
}

// 動畫是有狀態(tài)的
class AnimWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _AnimWidgetState();
  }
}

class _AnimWidgetState extends State<AnimWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;

  bool forward = true;

  Tween<Color> tween;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      // 動畫的時長
      duration: Duration(milliseconds: 2000),
      // 提供 vsync 最簡單的方式,就是直接繼承 SingleTickerProviderStateMixin
      vsync: this,
    );
    //使用Color
    tween = ColorTween(begin: Colors.blue, end: Colors.yellow);
    //添加動畫值修改監(jiān)聽
    tween.animate(controller)..addListener(() => setState(() {}));
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          width: 100,
          height: 100,
          //獲取動畫當前值
          color: tween.evaluate(controller),
        ),
        RaisedButton(
          child: Text("播放"),
          onPressed: () {
            if (forward) {
              controller.forward();
            } else {
              controller.reverse();
            }
            forward = !forward;
          },
        ),
        RaisedButton(
          child: Text("停止"),
          onPressed: () {
            controller.stop();
          },
        )
      ],
    );
  }
}

Curve

? 動畫過程默認是線性的(勻速),如果需要非線形的,比如:加速的或者先加速后減速等。Flutter中可以通過Curve(曲線)來描述動畫過程。

import 'package:flutter/material.dart';

void main() => runApp(AnimationApp());

class AnimationApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "animation",
      home: Scaffold(
        appBar: AppBar(
          title: Text('animation'),
        ),
        body: AnimWidget(),
      ),
    );
  }
}

// 動畫是有狀態(tài)的
class AnimWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _AnimWidgetState();
  }
}

class _AnimWidgetState extends State<AnimWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  bool forward = true;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      // 動畫的時長
      duration: Duration(milliseconds: 2000),
      // 提供 vsync 最簡單的方式,就是直接繼承 SingleTickerProviderStateMixin
      vsync: this,
    );

    //彈性
    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
    //使用Color
    animation = Tween(begin: 10.0, end: 100.0).animate(animation)
      ..addListener(() {
        setState(() => {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          //不需要轉(zhuǎn)換
          width: animation.value,
          height: animation.value,
          //獲取動畫當前值
          color: Colors.blue,
        ),
        RaisedButton(
          child: Text("播放"),
          onPressed: () {
            if (forward) {
              controller.forward();
            } else {
              controller.reverse();
            }
            forward = !forward;
          },
        ),
        RaisedButton(
          child: Text("停止"),
          onPressed: () {
            controller.stop();
          },
        )
      ],
    );
  }
}

AnimatedWidget

? 通過上面的學習我們能夠感受到Animation對象本身和UI渲染沒有任何關(guān)系。而通過addListener()setState() 來更新UI這一步其實是通用的,如果每個動畫中都加這么一句是比較繁瑣的。AnimatedWidget類封裝了調(diào)用setState()的細節(jié),簡單來說就是自動調(diào)用setState()

? Flutter中已經(jīng)封裝了很多動畫,比如對widget進行縮放,可以直接使用ScaleTransition

import 'package:flutter/material.dart';

void main() => runApp(AnimationApp());

class AnimationApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "animation",
      home: Scaffold(
        appBar: AppBar(
          title: Text('animation'),
        ),
        body: AnimWidget(),
      ),
    );
  }
}

// 動畫是有狀態(tài)的
class AnimWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _AnimWidgetState();
  }
}

class _AnimWidgetState extends State<AnimWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  bool forward = true;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      // 動畫的時長
      duration: Duration(milliseconds: 2000),
      // 提供 vsync 最簡單的方式,就是直接繼承 SingleTickerProviderStateMixin
      vsync: this,
    );

    //彈性
    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
    //使用Color
    animation = Tween(begin: 10.0, end: 100.0).animate(animation);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        ScaleTransition(
          child:  Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
          scale: controller,
        ),
        RaisedButton(
          child: Text("播放"),
          onPressed: () {
            if (forward) {
              controller.forward();
            } else {
              controller.reverse();
            }
            forward = !forward;
          },
        ),
        RaisedButton(
          child: Text("停止"),
          onPressed: () {
            controller.stop();
          },
        )
      ],
    );
  }
}

Hero動畫

? Hero動畫就是在路由切換時,有一個共享的Widget可以在新舊路由間切換,由于共享的Widget在新舊路由頁面上的位置、外觀可能有所差異,所以在路由切換時會逐漸過渡,這樣就會產(chǎn)生一個Hero動畫。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
          appBar: AppBar(
            title: Text("主頁"),
          ),
          body: Route1()),
    );
  }
}

// 路由A
class Route1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: InkWell(
        child: Hero(
          tag: "avatar", //唯一標記,前后兩個路由頁Hero的tag必須相同
          child: CircleAvatar(
            backgroundImage: AssetImage(
              "assets/banner.jpeg",
            ),
          ),
        ),
        onTap: () {
          Navigator.push(context, MaterialPageRoute(builder: (_) {
            return Route2();
          }));
        },
      ),
    );
  }
}

class Route2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Hero(
          tag: "avatar", //唯一標記,前后兩個路由頁Hero的tag必須相同
          child: Image.asset("assets/banner.jpeg")),
    );
  }
}

組合動畫

有些時候我們可能會需要執(zhí)行一個動畫序列執(zhí)行一些復雜的動畫。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Route(),
    );
  }
}

class Route extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return RouteState();
  }
}

class RouteState extends State<Route> with SingleTickerProviderStateMixin {
  Animation<Color> color;
  Animation<double> width;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      // 動畫的時長
      duration: Duration(milliseconds: 2000),
      // 提供 vsync 最簡單的方式,就是直接繼承 SingleTickerProviderStateMixin
      vsync: this,
    );

    //高度動畫
    width = Tween<double>(
      begin: 100.0,
      end: 300.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          //間隔,前60%的動畫時間 1200ms執(zhí)行高度變化
          0.0, 0.6,
        ),
      ),
    );

    color = ColorTween(
      begin: Colors.green,
      end: Colors.red,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.6, 1.0, //高度變化完成后 800ms 執(zhí)行顏色編碼
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主頁"),
      ),
      body: InkWell(
        ///1、不用顯式的去添加幀監(jiān)聽器,再調(diào)用setState()
        ///2、縮小動畫構(gòu)建的范圍,如果沒有builder,setState()將會在父widget上下文調(diào)用,導致父widget的build方法重新調(diào)用,現(xiàn)在只會導致動畫widget的build重新調(diào)用
        child: AnimatedBuilder(
            animation: controller,
            builder: (context, child) {
              return Container(
                color: color.value,
                width: width.value,
                height: 100.0,
              );
            }),
        onTap: () {
          controller.forward().whenCompleteOrCancel(() => controller.reverse());
        },
      ),
    );
  }
}

打包

? Flutter在打Release包時候回使用AOT,因此在對一個Flutter測試時候務必使用Release來進行測試。打包命令:flutter build apk 。當然我們需要打包時,還需要配置一些比如簽名的內(nèi)容。配置這些內(nèi)容和普通Android工程沒有區(qū)別,都是在build.gradle中進行,只是Flutter工程AS沒有提供GUI。

? 在Flutter工程的android/app下面的build.gradle可以修改包名、版本等信息,這就不用多說了。獲得簽名文件之后,將它復制到flutter的android目錄:

? 然后在app的build.gradle中配置:

signingConfigs {
        release {
            keyAlias 'enjoy'
            keyPassword '123456'
            // 因為是放到父級的根目錄,使用rootProject
            // 如果放在這個build.gradle的同級,直接使用file
            storeFile rootProject.file('enjoy.jks')
            storePassword '123456'
        }
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.release
        }
    }

餅圖

https://github.com/google/charts

Stack布局中的fit屬性與Image的fit類似,表示內(nèi)容的擴充情況。默認為StackFit.loose表示Stack與內(nèi)容一樣大。如果設(shè)置為StackFit.passthrough則表示Stack父Widget的約束會傳給Stack內(nèi)部非Positioned的子Widget。效果如代碼中的StackFit.dart

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。