1. Pre-knowledge

For each UI frame, Animate, Build, Layout, Compositing Bits, Paint, Compositing are executed in sequence. Every time the interface changes, it is the result of a frame that triggers an update. The following two bars represent the UI time (left) and Raster time (right) of a frame. When the left side is too high, your interface is not written properly. If you look at the two UI frames below, you can see that Build is a large part of the UI, indicating that there may be some inefficiency in the UI.


You can look down at the depth of the entire Build traversal. If the tree is too deep, there may be a problem. This is a good time to look and see if you have updated any unnecessary parts.


Note, however, that updates to the global theme, text, etc., will inevitably be traversed from the top node. This will cause some delay, but these are visually insensitive operations and not very frequent. Animations, on the other hand, are rendered over a period of time, so pay special attention to performance issues. Also do not look at performance in Debug mode, do not look at performance in Debug mode, do not look at performance in Debug mode! Use profile mode.


2. Negative lesson!!

The animation is shown below, with the middle circle gradually enlarging the animation, and the upper and lower squares not moving.

Program entrance

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()); }}Copy the code

Will create animation _HomePageState SingleTickerProviderStateMixin, with the controller and monitor animation, every time trigger calls _HomePageState setState method, Update the Element held in _HomePageState. Animation is triggered when the middle is clicked.

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("Animation test"),
        ),
        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),),
                )),
      );
}
Copy the code

To facilitate testing, the intermediate components are separated here into showers. Use the StatefulWidget to make it easy to test the _ShowerState callback during animation execution.

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), ), ], ), ); }}Copy the code

Then you’ll see that _HomePageState#build and Shower#build are constantly triggered. The root cause is that setState is performed at a higher level, causing the tree below to be traversed, in which case performing animation is not desirable. What we need to do is lower the update element node hierarchy. Flutter provides us with AnimatedBuilder.


3. Positive textbooksAnimatedBuilder

Need to make changes: 1. Remove listener animator 2. Use AnimatedBuilder

@override
void initState() {
  super.initState();
  controller = AnimationController(
      vsync: this,
      lowerBound: 0.3,
      upperBound: 1.0,
      duration: const Duration(milliseconds: 500)); // Remove the listener animator
}

Widget buildCenter() => GestureDetector(
  onTap: () {
    controller.forward(from: 0);
  },
  child: AnimatedBuilder( // Use AnimatedBuilder
      animation: controller,
      builder: (ctx, child) {
        return Transform.scale(
          scale: controller.value,
          child: Opacity(opacity: controller.value, child: child),
        );
      },
      child: Shower()),
);
Copy the code

That’s it. Let’s see, the animation works

There’s nothing on the console. Is that too much? Isn’t that pop-pop-slap me setState in the face?

As you can see from the UI frames below, using AnimatedBuilder to animate the same scenario can effectively shorten the Build process.


4.AnimatedBuilderThe source code parsing

First, the AnimatedBuilder inherits from the AnimatedWidget and has a constructor Builder and child component, child, and Listenable object Animation when the object is created.

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) {
    returnbuilder(context, child); }}typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);
Copy the code

The AnimatedBuilder is simple, and the core should all be in the AnimatedWidget. You can see that the AnimatedWidget is a StatefulWidget that needs to change its state.

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();
}
Copy the code

Handling in _AnimatedState is also very simple, listening for the incoming listEnable, executing _handleChange, and _handleChange executing….. That’s right: your uncle is still your uncle. Update again depends on setState. But the setState here is much less important than the setState above.

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);
}
Copy the code

When a build is executed, widget.build(context) is executed, which calls back the current context to the widget.build method, and the widget.build method executes: The constructor (context, child) is the same as the constructor (context, child), so the new Shower component is not built. It does not trigger the State build method of the Shower component. All animation needs to be done in the Builder method, and the refresh is wrapped locally in the AnimatedBuilder. In this way, the years quiet good, placid.

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


In this way, there seems to be nothing mysterious about AnimatedBuilder. Knowing this, and then looking at the various animation components encapsulated in the Flutter framework, it becomes clear that the whole idea is clear. So to summarize, it’s not that setState is bad, it’s that it’s the right time to use it. AnimatedBuilder is also essentially using setState to trigger updates, so don’t be partial and radical. For the UI, we need to focus on how to minimize the Build process, especially for animations, slides, and other scenes that are constantly being rendered.

@Zhang Fengjietele 2020.12.16 not allowed to transfer my public number: the king of programming contact me – email :[email protected] – wechat :zdl1994328 ~ END ~