<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何快速提升 Flutter App 中的動畫性能

          共 10005字,需瀏覽 21分鐘

           ·

          2020-12-03 11:33

          點擊上方“逆鋒起筆”,公眾號回復(fù) pdf
          領(lǐng)取大佬們推薦的學(xué)習(xí)資料

          觀前提醒:本文假設(shè)你已經(jīng)有一定的 Flutter 開發(fā)經(jīng)驗,對Flutter 的 Widget,RenderObject 等概念有所了解,并且知道如何開啟 DevTools。


          現(xiàn)有一個簡單的汽泡動畫需要實現(xiàn),如下圖:



          一、直接通過 AnimationController 實現(xiàn)


          當(dāng)看到這個效果圖的時候,很快啊,啪一下思路就來了。涉及到動畫,有狀態(tài),用 StatefulWidget ,State 里創(chuàng)建一個 AnimationController,用兩個 Container 對應(yīng)兩個圈,外圈的 Container 的寬高監(jiān)聽動畫跟著更新就行。
          代碼如下:


          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          const double size = 56;

          class BubbleAnimationByAnimationController extends StatefulWidget {
          @override
          _BubbleAnimationByAnimationControllerState createState() => _BubbleAnimationByAnimationControllerState();
          }

          class _BubbleAnimationByAnimationControllerState extends State<BubbleAnimationByAnimationController>
          with SingleTickerProviderStateMixin {
          AnimationController _controller;

          @override
          void initState() {
          super.initState();
          _controller = AnimationController(
          duration: const Duration(seconds: 1),
          vsync: this,
          )..addListener(() => setState(() {}));
          _controller.repeat(reverse: true);
          }

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

          @override
          Widget build(BuildContext context) {
          // 兩個 `Container` 對應(yīng)兩個圈
          return Container(
          alignment: Alignment.center,
          constraints: BoxConstraints.tight(
          Size.square((1 + _controller.value * 0.2) * size),
          ),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue[200],
          ),
          child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue,
          ),
          width: size,
          height: size,
          child: Text(
          'Hello world!',
          style: TextStyle(color: Colors.white, fontSize: 12),
          ),
          ),
          );
          }
          }




          跑起來,很完美的實現(xiàn)了要求,如下圖所示:



          但且慢,仔細 review 一下代碼,有沒有發(fā)現(xiàn),內(nèi)圈的 Container其實和動畫并沒有什么關(guān)系,換句話說,它并不需要跟隨動畫一起被 build


          用 DevTools 的 Timeline 開啟Track Widgets Builds 跟蹤一下,如下圖所示:



          可以發(fā)現(xiàn),在 Build 階段,BubbleAnimationByAnimationController 因為 setState 引發(fā) rebuild,進而重新 build 了兩個 Container,包括內(nèi)圈里的 Text


          解決辦法也很簡單,把內(nèi)圈的 Widget 提前構(gòu)建好,外圈直接用就行了:


          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          class BubbleAnimationByAnimationController extends StatefulWidget {
          final Widget child;
          const BubbleAnimationByAnimationController({this.child});
          @override
          _BubbleAnimationByAnimationControllerState createState() => _BubbleAnimationByAnimationControllerState();
          }

          class _BubbleAnimationByAnimationControllerState extends State<BubbleAnimationByAnimationController>
          with SingleTickerProviderStateMixin {
          AnimationController _controller;

          @override
          void initState() {
          super.initState();
          _controller = AnimationController(
          duration: const Duration(seconds: 1),
          vsync: this,
          )..addListener(() => setState(() {}));
          _controller.repeat(reverse: true);
          }

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

          @override
          Widget build(BuildContext context) {
          // 外圈 `Container` 包裹內(nèi)圈
          return Container(
          alignment: Alignment.center,
          constraints: BoxConstraints.tight(
          Size.square((1 + _controller.value * 0.2) * size),
          ),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue[200],
          ),
          // 這里的 widget.child 不會 rebuild
          child: widget.child,
          );
          }
          }

          // 使用時,外部構(gòu)建內(nèi)圈的Widget
          final Widget buble = BubbleAnimationByAnimationController(
          child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue,
          ),
          width: size,
          height: size,
          child: Text(
          'Hello world!',
          style: TextStyle(color: Colors.white, fontSize: 12),
          ),
          ),
          );



          二、通過 AnimatedBuilder 實現(xiàn)


          其實 Flutter 官方提供的AnimatedBuilder就是這么做的,它將不變部分的 child 交由外部構(gòu)建。


          用 AnimatedBuilder 改造代碼如下:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          class BubbleAnimationByAnimatedBuilder extends StatefulWidget {
          @override
          _BubbleAnimationByAnimatedBuilderState createState() =>
          _BubbleAnimationByAnimatedBuilderState();
          }

          class _BubbleAnimationByAnimatedBuilderState
          extends State<BubbleAnimationByAnimatedBuilder>
          with SingleTickerProviderStateMixin {
          AnimationController _controller;

          @override
          void initState() {
          super.initState();
          _controller = AnimationController(
          duration: const Duration(seconds: 1),
          vsync: this,
          );

          // 注意:這里不需要監(jiān)聽了并setState了,AnimatedBuilder 已經(jīng)內(nèi)部這樣做了

          _controller.repeat(reverse: true);
          }

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

          @override
          Widget build(BuildContext context) {
          // 用 AnimatedBuilder 內(nèi)部監(jiān)聽動畫
          return AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
          return Container(
          alignment: Alignment.center,
          constraints: BoxConstraints.tight(
          Size.square((1 + _controller.value * 0.2) * size),
          ),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue[200],
          ),
          child: child, // 這個child 其實就是外部構(gòu)建好的 內(nèi)圈 `Container`
          );
          },
          child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue,
          ),
          width: size,
          height: size,
          child: Text(
          'Hello world!',
          style: TextStyle(color: Colors.white, fontSize: 12),
          ),
          ),
          );
          }
          }



          再次跑起來,非常完美。DevTools 的 Timeline 如下圖所示:



          可以看到,Build 階段完全沒有 rebuild 內(nèi)圈的內(nèi)容,只有外圈 Container隨著 rebuild。


          且慢,還沒完呢,還有沒有辦法完全不 rebuild 呢?畢竟這個動畫很簡單,內(nèi)圈完全不變的,只有外圈隨時間累加而放大/縮小。這個外圈動畫自己畫行不行?


          三、用 CustomPaint 實現(xiàn)


          Flutter 提供了一個Widget 叫 CustomPaint,它只需要我們實現(xiàn)一個 CustomPainter 自己往 Canvas 繪制內(nèi)容。


          先定義一個 CustomPainter,根據(jù)動畫的值畫外圈,代碼如下:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          class _BubblePainter extends CustomPainter {
          final Animation<double> animation;
          const _BubblePainter(this.animation) : super(repaint: animation);
          @override
          void paint(Canvas canvas, Size size) {
          final center = size.center(Offset.zero);
          // 跟隨動畫放大/縮小圈的半徑
          final radius = center.dx * (1 + animation.value * 0.2);
          final paint = Paint()
          ..color = Colors.blue[200]
          ..isAntiAlias = true;
          canvas.drawCircle(center, radius, paint);
          }

          @override
          bool shouldRepaint(_BubblePainter oldDelegate) {
          return oldDelegate.animation != this.animation;
          }
          }


          特別注意,父類構(gòu)造方法的調(diào)用不能省 super(repaint: animation),后面告訴你為什么。


          其它代碼跟之前沒什么兩樣,如下:


          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51

          class BubbleAnimationByCustomPaint extends StatefulWidget {
          @override
          _BubbleAnimationByCustomPaintState createState() =>
          _BubbleAnimationByCustomPaintState();
          }

          class _BubbleAnimationByCustomPaintState
          extends State<BubbleAnimationByCustomPaint>
          with SingleTickerProviderStateMixin {
          AnimationController _controller;

          @override
          void initState() {
          super.initState();
          _controller = AnimationController(
          duration: const Duration(seconds: 1),
          vsync: this,
          );

          _controller.repeat(reverse: true);
          }

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

          @override
          Widget build(BuildContext context) {
          return CustomPaint(
          painter: _BubblePainter(_controller),
          // CustomPaint 的大小會自動使用 child 的大小
          child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.blue,
          ),
          width: size,
          height: size,
          child: Text(
          'Hello world!',
          style: TextStyle(color: Colors.white, fontSize: 12),
          ),
          ),
          );
          }
          }


          跑起來,跟之前版本一樣的完美。



          你可能好奇了,CustomPaint 怎么會自己動起來呢?其實,秘密就在 CustomPainter 的構(gòu)造方法里的 repaint 參數(shù)。


          CustomPaint創(chuàng)建的 RenderObject 對象 RenderCustomPaint 會監(jiān)聽這個 repaint,而該對象是外部傳入的 _controller,動畫更新觸發(fā)markNeedsPaint(),進而畫面動起來了。可以戳這里看一眼 RenderCustomPaint 源碼。


          這次 DevTools 的 Timeline 如下圖所示,完全沒有了 Build 的蹤影:



          再且慢,還沒結(jié)束。到這里只是解決了 Build 階段頻繁rebuild 的問題,看上圖所示,Paint 階段似乎還能再擠幾滴性能出來?


          最后的最后


          怎么跟蹤查看 repaint 呢,總不至于打log吧?


          開啟 DevTools 的 Repaint RainBow 選項即可。或者在代碼中設(shè)置debugRepaintRainbowEnabled = true


          在手機畫面上立馬會看到色塊,如果畫面上有動畫的話更明顯,其會隨著 paint 的次數(shù)增加而變化,像彩虹燈一樣。如下圖:



          可以看到,整個 APP 界面包括頭部的 AppBar 的顏色是跟著內(nèi)部的汽泡一起變的,說明在隨著內(nèi)部動畫而發(fā)生 repaint


          Flutter 提供了一個 RepaintBoundary 用于限制重繪區(qū)域,專門用來解決此問題。

          使用方式很簡單,直接套在CustomPaint外面,代碼如下:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          @override
          Widget build(BuildContext context) {
          return RepaintBoundary(
          child: CustomPaint(
          painter: _BubblePainter(_controller),
          child: Container(...),
          ),
          );
          }



          效果立桿見影,彩虹圖如下圖所示,只重繪了動畫的區(qū)域:



          相對應(yīng)的,Paint 階段耗時也很明顯的降低:



          結(jié)語


          恭喜你,又離資深 Flutter 開發(fā)更近了一步。通過本文,你應(yīng)該學(xué)會了如何讓 Flutter 動畫動得更有效率。關(guān)注公眾號 逆鋒起筆,回復(fù) pdf,下載你需要的各種學(xué)習(xí)資料。


          還在等什么呢,趕快回去按本文思路優(yōu)化你項目中的動畫吧。


          如有更好的思路,或者其它的點,歡迎留下你的評論。


          原文地址:https://yrom.net/blog/2020/11/16/optimize-animation-in-flutter/



          分享 Flutter 學(xué)習(xí)總結(jié)
          Flutter 是移動應(yīng)用程序開發(fā)的未來?
          下載!閑魚最新升級版 Flutter 技術(shù)電子書!

             
          點個『在看』支持下 
          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线观看三级片视频 | 水蜜桃视频在线观看 | 蜜臀久久99精品久久宅男 | 伊人久久香蕉网 | 免费看黄色日逼视频 |