Search code examples
animationflutterdartaccelerometerflutter-animation

Flutter code creating laggy animation. What is wrong here?


I have created a flutter widget that moves the circle with accelerometer. It is very laggy as I have to use setState to change the position of the circle when phone is moved. Is there an alternative to creating this?

I have used AnimatedBuilder here but not sure how that can change the position of circle when device is moved smoothly.

class _AnimationWidgetState extends State<AnimationWidget>
    with TickerProviderStateMixin {
  AnimationController _animeController;
  Animation _anime;
  double x = 0.0, y = 0.0;
  @override
  void initState() {
    super.initState();
    _animeController =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
    _anime = Tween(begin: 0.5, end: 0.5).animate(
        CurvedAnimation(parent: _animeController, curve: Curves.ease));
    accelerometerEvents.listen((AccelerometerEvent event) {
      var a = ((event.x * 100).round() / 100).clamp(-1.0, 1.0) * -1;
      var b = ((event.y * 100).round() / 100).clamp(-1.0, 1.0);
      if ((x - a).abs() > 0.02 || (y - b).abs() > 0.02) {
        setState(() {
          x = a; y = b;
        });
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    _animeController.forward();
    final double width = MediaQuery.of(context).size.width;
    final double height = MediaQuery.of(context).size.height;
    return AnimatedBuilder(
      animation: _animeController,
      builder: (context, child) {
        return Scaffold(
          body: Transform(
            transform: Matrix4.translationValues(
                _anime.value * width * x, _anime.value * height * y, 0.0),
            child: Center(
              child: CircleAvatar(
                radius: 15.0,
                backgroundColor: Colors.green,
              ),
            ),
          ),
        );
      },
    );
  }
}

Animation is not at all smooth. This is because I have to use setState but the movement of circle is working as desired.


Solution

  • The whole purpose of using AnimationController is to listen to its events - that's what AnimatedBuilder does and rebuilds its subtree accordingly.

    I will post here my overall recommendations on what to change in the code.


    Remove setState - that's what makes your entire layout rebuild all over again, i.e. lags.

    Also trigger _animeController listeners, i.e. AnimatedBuilder in your case - to rebuild itself.

    accelerometerEvents.listen((AccelerometerEvent event) {
      var a = ((event.x * 100).round() / 100).clamp(-1.0, 1.0) * -1;
      var b = ((event.y * 100).round() / 100).clamp(-1.0, 1.0);
      if ((x - a).abs() > 0.02 || (y - b).abs() > 0.02) {
        x = a; y = b;
        _animeController.value = _animeController.value; // Trigger controller's listeners
      }
    });
    

    Start animation in initState, instead of build. This is the second thing that produces lags in your case. .forward triggers rebuild of your widgets, which leads to an infinite loop.

    @override
    void initState() {
      super.initState();
      _animeController.forward();
    }
    

    Use child property of your AnimatedBuilder to save up resources on rebuilding the avatar block every time. Also I'm not sure what Scaffold is here for - let's remove it if it's not needed.

    AnimatedBuilder(
      animation: _animeController,
      builder: (context, child) => Transform(
        transform: Matrix4.translationValues(_anime.value * width * x, _anime.value * height * y, 0.0),
        child: child,
      ),
      child: Center(
        child: CircleAvatar(
          radius: 15.0,
          backgroundColor: Colors.green,
        ),
      ),
    );
    

    Follow Official Animations Tutorial to master Flutter animations.

    Let me know if this helps.