Flutter 實(shí)現(xiàn) 3D 動畫效果詳解

前言

上一篇我們介紹了 AnimationAnimationController 的使用貌嫡,這是最基本的動畫構(gòu)建類比驻。但是,如果我們想構(gòu)建一個可復(fù)用的動畫組件岛抄,通過外部參數(shù)來控制其動畫效果的時候别惦,上一篇的方法就不太合適了。在 Flutter 中提供了 AnimatedWidget 組件用于構(gòu)建可復(fù)用的動畫組件夫椭。本篇我們用 AnimatedWidget 來實(shí)現(xiàn)組件的3D 旋轉(zhuǎn)效果掸掸,如下圖所示。

3D旋轉(zhuǎn).gif

AnimatedWidget 簡介

AnimatedWidget是一個抽象的 StatefulWidget, 構(gòu)造方法如下所示扰付。

const AnimatedWidget({
    Key? key,
    required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

主要在于接收一個 listenable 參數(shù)堤撵,通常會是 Animation 對象。在 AnimatedWidget 內(nèi)部的_AnimatedState 類中羽莺,會添加該對象變化監(jiān)聽回調(diào)实昨,進(jìn)而刷新界面。

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }
  
  // ...
}

可以看到盐固,只需要將 Animation 對象傳給 AnimatedWidget 對象后荒给,就不需要我們之前那樣自己寫 addListener 之類的處理了。而整個動畫可以交給外部其他對象來控制刁卜,從而實(shí)現(xiàn)動畫組件的復(fù)用志电。

3D 旋轉(zhuǎn)動畫的實(shí)現(xiàn)

3D 旋轉(zhuǎn)的實(shí)現(xiàn)比較簡單,在 Container 組件有兩個參數(shù)控制轉(zhuǎn)換(transform)蛔趴,分別是:

  • transformMatrix4對象挑辆,可以實(shí)現(xiàn)圍繞 X、Y孝情、Z軸的旋轉(zhuǎn)鱼蝉、平移,以及變形等效果咧叭。關(guān)于 Matrix4涉及到很多矩陣運(yùn)算和線性代數(shù)的知識蚀乔,可以參考 Matrix4的源碼自行溫習(xí)一下大學(xué)的數(shù)學(xué)知識??烁竭。
  • transformAlignment:轉(zhuǎn)換的對齊方式菲茬,可以理解為起點(diǎn)位置,可以使用 Alignment 對象來設(shè)置派撕。

有了這個基礎(chǔ)婉弹,我們就可以定義3D 旋轉(zhuǎn)動效了,我們定義一個通用的組件终吼,ThreeDAnimatedWidget

class ThreeDAnimatedWidget extends AnimatedWidget {
  final Widget child;
  const ThreeDAnimatedWidget(
      {Key? key, required Animation<double> animation, required this.child})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;

    return Center(
      child: Container(
        transform: Matrix4.identity()
          ..rotateY(2 * pi * animation.value)
          ..setEntry(1, 0, 0.01),
        transformAlignment: Alignment.center,
        child: child,
      ),
    );
  }
}

這里我們設(shè)置的是圍繞中心點(diǎn)繞 Y 軸旋轉(zhuǎn)镀赌,并使用 setEntry 設(shè)置了一定的傾斜角 (這會更有立體感)。實(shí)際我們也可以設(shè)置圍繞 X 軸或 Z 軸旋轉(zhuǎn)际跪。接下來就是這個動畫組件的應(yīng)用了商佛,我們構(gòu)建一個帶有陰影的文字(看起來像立體字)作為這個動畫的子組件,其他的控制和上一篇的是類似的姆打,完整代碼如下:

class AnimatedWidgetDemo extends StatefulWidget {
  const AnimatedWidgetDemo({Key? key}) : super(key: key);

  @override
  _AnimatedWidgetDemoState createState() => _AnimatedWidgetDemoState();
}

class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 3), vsync: this);
    animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedWidget 動畫'),
      ),
      body: ThreeDAnimatedWidget(
        animation: animation,
        child: Text(
          '島上碼農(nóng)',
          style: TextStyle(
            fontSize: 42.0,
            color: Colors.blue,
            fontWeight: FontWeight.bold,
            shadows: [
              Shadow(
                  blurRadius: 2,
                  offset: Offset(2.0, 1.0),
                  color: Colors.blue[900]!),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow, color: Colors.white),
        onPressed: () {
          if (controller.status == AnimationStatus.completed) {
            controller.reverse();
          } else {
            controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

可以看到良姆,這個 ThreeDAnimatedWidget 可以做到復(fù)用了,在需要這樣動效的場景里幔戏,按照上面的方式給它傳入 Animation 對象和子組件就可以了玛追。例如我們將文字修改為一張圖片。

//...
body: ThreeDAnimatedWidget(
  animation: animation,
  child: Image.asset(
    'images/avatar.jpg',
    width: 100,
    height: 100,
  ),
),
//...
復(fù)用動畫效果.gif

總結(jié)

本篇介紹了 AnimatedWidget 的使用,通過 AnimatedWidget 可以構(gòu)建可復(fù)用的動畫組件痊剖。同時韩玩,還通過 Containertransform 屬性加上 AnimatedWidget 實(shí)現(xiàn)了三維空間的旋轉(zhuǎn)效果。實(shí)際開發(fā)過程中陆馁,我們可以基于 AnimatedWidget 構(gòu)建很多個性化的找颓、可復(fù)用的動畫組件,提升應(yīng)用的趣味性叮贩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叮雳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妇汗,更是在濱河造成了極大的恐慌帘不,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杨箭,死亡現(xiàn)場離奇詭異寞焙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)互婿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門捣郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慈参,你說我怎么就攤上這事呛牲。” “怎么了驮配?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵娘扩,是天一觀的道長。 經(jīng)常有香客問我壮锻,道長琐旁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任猜绣,我火速辦了婚禮灰殴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掰邢。我一直安慰自己牺陶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布辣之。 她就那樣靜靜地躺著掰伸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪召烂。 梳的紋絲不亂的頭發(fā)上碱工,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼怕篷。 笑死历筝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的廊谓。 我是一名探鬼主播梳猪,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蒸痹!你這毒婦竟也來了春弥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叠荠,失蹤者是張志新(化名)和其女友劉穎匿沛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榛鼎,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逃呼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了者娱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡笼。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黄鳍,靈堂內(nèi)的尸體忽然破棺而出推姻,到底是詐尸還是另有隱情,我是刑警寧澤框沟,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布藏古,位于F島的核電站,受9級特大地震影響街望,放射性物質(zhì)發(fā)生泄漏校翔。R本人自食惡果不足惜弟跑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一灾前、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孟辑,春花似錦哎甲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至貌虾,卻和暖如春吞加,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工衔憨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叶圃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓践图,卻偏偏與公主長得像掺冠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子码党,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容