[フラッターパフォーマンス] 2021年ですが、アニメーションでまだsetStateを使用していますか?

著者:張傑テリークレイジー

1.事前知識

UIフレームごとに、アニメーション、ビルド、レイアウト、合成ビット、ペイント、合成が主に順番に実行されます。インターフェイスが変更されるたびに、フレームによってトリガーされ、結果が更新されます。次の2つのグリッドは、フレームのUI時間(左)とラスター時間(右)を表しています。左側が高い場合は、インターフェースに問題があることを意味します。次の2つのUIフレームを見ると、ビルドが大部分を占めていることがわかります。これは、UIの効率が低い可能性があることを意味します。


ビルドトラバーサル全体の深さを見下ろすことができます。ツリーが深すぎる場合は、問題がある可能性があります。このとき、不要な部分が更新されていないか確認してください。


ただし、グローバルトピックやテキストなどの場合、更新は必然的にトップノードからトラバースされることに注意してください。これは避けられません。ある程度の遅延は発生しますが、視覚的に影響を受けない操作であり、操作の数はそれほど多くありません。ただし、アニメーションに関しては違います。数フレーム後、動かなくなって滑らかになりません。一方、アニメーションは一定期間レンダリングされ続けるので、パフォーマンスの問題に特に注意してください。さらに、デバッグモードでのパフォーマンス、デバッグモードでのパフォーマンス、デバッグモードでのパフォーマンスを確認しないでください。プロファイルモードを使用します。


2.ネガティブな教材!

アニメーションは次のとおりです。中央の円形のグラデーションがアニメーションを拡大し、上下の正方形は移動しません。

プログラムエントリー

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage());
  }
}

_HomePageStateをSingleTickerProviderStateMixinに混合し、アニメーターコントローラーを作成し、アニメーターを監視し、トリガーされるたびに_HomePageStateのsetStateメソッドを呼び出して、_HomePageStateに保持されている要素を更新します。中央がクリックされると、アニメーションがトリガーされます。

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        lowerBound: 0.3,
        upperBound: 1.0,
        vsync: this,
        duration: const Duration(milliseconds: 500));
    controller.addListener(() {
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    print('---------_HomePageState#build------');
    return Scaffold(
        appBar: AppBar(
          title: Text("动画测试"),
        ),
        body: Column(
          children: [
            Expanded(
              child: Padding( padding: EdgeInsets.only(top: 20),
                child: buildBoxes(),
              ),
            ),
            Expanded(
              child: Center(
                child: buildCenter(),
              ),
            ),
            Expanded(
              child: Padding( padding: EdgeInsets.only(bottom: 20),
                child: buildBoxes(),
              ),
            ),
          ],
        ));
  }

  Widget buildCenter() => GestureDetector(
                onTap: () {
                  controller.forward(from: 0.3);
                },
                child: Transform.scale(
                  scale: controller.value,
                  child: Opacity(opacity: controller.value, child: Shower()),
                ),
              );

  Widget buildBoxes() => Wrap(
        spacing: 20,
        runSpacing: 20,
        children: List.generate( 24,
            (index) => Container(
                  alignment: Alignment.center,
                  width: 40,
                  height: 40,
                  color: Colors.orange,
                  child: Text('$index',style: TextStyle(color: Colors.white),),
                )),
      );
}

テストを容易にするために、中間コンポーネントはシャワーに分けられます。StatefulWidgetを使用して、アニメーションの実行中に_ShowerStateコールバック関数をテストします。

class Shower extends StatefulWidget {
  @override
  _ShowerState createState() => _ShowerState();
}

class _ShowerState extends State<Shower> {
  @override
  void initState() {
    super.initState();
    print('-----Shower#initState----------');
  }

  @override
  Widget build(BuildContext context) {
    print('-----Shower#build----------');
    return Container(
      width: 150,
      height: 150,
      alignment: Alignment.center,
      decoration: BoxDecoration(color: Colors.orange, shape: BoxShape.circle),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Container(
                height: 30,
                width: 30,
                decoration:
                    BoxDecoration(color: Colors.white, shape: BoxShape.circle),
              ),
              Container(
                height: 30,
                width: 30,
                decoration:
                    BoxDecoration(color: Colors.white, shape: BoxShape.circle),
              )
            ],
          ),
          Text(
            'Toly',
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ],
      ),
    );
  }
}

次に、_HomePageState#buildとShower#buildが引き続きトリガーされることがわかります。根本的な原因は、setStateがより高いレベルで実行されるため、ツリーがトラバースされることです。この場合、アニメーションを実行することはお勧めできません。私たちがする必要があるのは、更新要素ノードのレベルを下げることです。FlutterはAnimatedBuilderを提供します。


3.ポジティブな教科書AnimatedBuilder

行う必要のある変更:1。モニターアニメーターを削除します2.AnimatedBuilderを使用します

@override
void initState() {
  super.initState();
  controller = AnimationController(
      vsync: this,
      lowerBound: 0.3,
      upperBound: 1.0,
      duration: const Duration(milliseconds: 500)); // 1、移除监听动画器
}

Widget buildCenter() => GestureDetector(
  onTap: () {
    controller.forward(from: 0);
  },
  child: AnimatedBuilder( //  2、使用 AnimatedBuilder
      animation: controller,
      builder: (ctx, child) {
        return Transform.scale(
          scale: controller.value,
          child: Opacity(opacity: controller.value, child: child),
        );
      },
      child: Shower()),
);

それだけです、効果を見てみましょう、アニメーションは正常に実行されます

コンソールには何もありませんが、多すぎませんか?これは私のsetStateを顔に叩きつけていませんか?

以下のUIフレームからわかるように、同じシナリオで、アニメーションにAnimatedBuilderを使用すると、ビルドプロセスを効果的に短縮できます。


4.AnimatedBuilderソースコード分析

まず、AnimatedBuilderはAnimatedWidgetを継承し、そのメンバーにはBuilder Builderと子コンポーネントが含まれ、オブジェクトを作成するときにリスナブルオブジェクトアニメーションも必要です。

class AnimatedBuilder extends AnimatedWidget {
  const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  }) : assert(animation != null),
       assert(builder != null),
       super(key: key, listenable: animation);

  final TransitionBuilder builder;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return builder(context, child);
  }
}

typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);

AnimatedBuilderは非常にシンプルで、使用のコアはAnimatedWidgetにある必要があります。AnimatedWidgetは、状態を変更する必要があるStatefulWidgetであることがわかります。

abstract class AnimatedWidget extends StatefulWidget {
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  final Listenable listenable;

  @protected
  Widget build(BuildContext context);

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

_AnimatedStateでの処理も非常に簡単で、受信するリスナブルを監視し、_handleChangeを実行し、_handleChangeを実行します...はい:結局のところ、おじはまだおじです。更新は引き続きsetStateに依存します。ただし、上記のsetStateと比較すると、ここでのsetStateの影響ははるかに小さくなります。

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.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}

ビルドが実行されると、widget.build(context)が実行されます。つまり、現在のコンテキストがwidget.buildメソッドにコールバックされ、widget.buildメソッドが実行されます。builder(context、child)、これは作成したビルダーです。 (下の図)、子コールバックがまだ着信子であることがわかります。これにより、新しいShowerコンポーネントがビルドされず、Stateに対応するShowerコンポーネントのbuildメソッドがトリガーされません。すべてのアニメーションのニーズはbuilderメソッドで実行されます。 、更新されたものもAnimatedBuilderによって部分的にラップされます。このように、年は静かで穏やかです。

@override
Widget build(BuildContext context) {
  return builder(context, child);
}


この観点からすると、AnimatedBuilderは不思議ではないようです。これらを理解し、Flutterフレームワークにカプセル化されたさまざまなアニメーションコンポーネントを見ると、突然啓発されます。これはあなたが知っていることです。

要約すると、setStateが悪いというわけではありませんが、タイミングは正しいです。AnimatedBuilderは基本的にsetStateを使用して更新をトリガーするため、問題を一方的かつ積極的に見ないでください。アプリケーションインターフェイスUIの場合、特にアニメーションやスライドなどのレンダリングを継続的に更新するシーンの場合、ビルドプロセスの消費を最小限に抑える方法に注意する必要があります。


5.最後に

最後に、ここでは、乾物、Android学習PDF +アーキテクチャビデオ+大物が収集したソースノート高度なアーキテクチャテクノロジーの高度なブレインマップ、Android開発インタビューの特別資料、高度な高度なアーキテクチャの資料を共有します。上級レベルを向上させ、オンラインで情報を検索する時間を節約します。また、周囲の友達と共有して一緒に学ぶこともできます。

おすすめ

転載: blog.csdn.net/ajsliu1233/article/details/111396263