Search code examples
flutterflutter-animation

Animate widget to position with AnimatedBuilder


Given these widths:

const DESKTOP_RESULT_DETAILS_WIDTH = 370.0;
const DESKTOP_SEARCH_RESULTS_WIDTH = 320.0;
_mapWidth =
    MediaQuery.of(context).size.width - DESKTOP_SEARCH_RESULTS_WIDTH;

I am trying to animate a widget to Position(left: DESKTOP_SEARCH_RESULTS_WIDTH) when a variable called _model is null, and Position(left: DESKTOP_SEARCH_RESULTS_WIDTH + DESKTOP_RESULT_DETAILS_WIDTH) when _model is not null.

The problem is that at the start of each animation, it starts the animation from the wrong position and then ends in the correct position. Can anyone see the issue with my code?:

The AnimationController definition:

  @override
  void initState() {
    _mapAc = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1000),
      value: 0,
    );
    super.initState();
  }

Calculate the left position:

  double _mapLeft({required SearchResultModel? model}) {
    if (model == null) {
      return (_mapWidth * _mapAc.value) + DESKTOP_SEARCH_RESULTS_WIDTH;
    } else {
      return _mapAc.value *
          (DESKTOP_SEARCH_RESULTS_WIDTH + DESKTOP_RESULT_DETAILS_WIDTH);
    }
  }

The animation definition and the code that triggers the animation:

final animation = Tween(
        begin: DESKTOP_SEARCH_RESULTS_WIDTH,
        end: DESKTOP_SEARCH_RESULTS_WIDTH + DESKTOP_RESULT_DETAILS_WIDTH)
    .animate(_mapAc);

ref.listen<SearchResultModel?>(vgnItmEstDetailsProvider, (previous, next) {
  if (next != null) {
    _mapAc.animateTo(1, duration: Duration(milliseconds: 1000));
  } else {
    _mapAc.animateTo(0, duration: Duration(milliseconds: 1000));
  }
});

The animated builder:

AnimatedBuilder(
  animation: animation,
  builder: (context, child) {
    return Positioned(
      left: _mapLeft(model: _model),
      child: SizedBox(
        width: MediaQuery.of(context).size.width -
            DESKTOP_SEARCH_RESULTS_WIDTH,
        height: MediaQuery.of(context).size.height - TOP_NAV_HEIGHT,
        child: MapWidget(
          vm,
        ),
      ),
    );
  },
)

Solution

  • hope it helps

    enter image description here

    // ignore_for_file: constant_identifier_names
    
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(
        const MaterialApp(
          home: MyApp(),
        ),
      );
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    const DESKTOP_RESULT_DETAILS_WIDTH = 370.0;
    const DESKTOP_SEARCH_RESULTS_WIDTH = 320.0;
    const TOP_NAV_HEIGHT = 64.0;
    
    class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
      bool hasModel = false;
    
      late AnimationController _mapAc;
      @override
      void initState() {
        _mapAc = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 300),
        );
    
        super.initState();
      }
    
      double _mapLeft({required bool hasModel, required double animationValue}) {
        final mapWidth =
            MediaQuery.of(context).size.width - DESKTOP_SEARCH_RESULTS_WIDTH;
    
        final positionWhereMapShouldBeIfHasModel =
            DESKTOP_SEARCH_RESULTS_WIDTH + DESKTOP_RESULT_DETAILS_WIDTH;
        final positionWhereMapShouldBeIfHasNoModel = DESKTOP_SEARCH_RESULTS_WIDTH;
        final animationDiff = positionWhereMapShouldBeIfHasModel -
            positionWhereMapShouldBeIfHasNoModel;
    
        return positionWhereMapShouldBeIfHasNoModel +
            (animationDiff * animationValue);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: ElevatedButton(
              onPressed: () {
                setState(() {
                  hasModel = !hasModel;
                  if (hasModel) {
                    _mapAc.animateTo(1);
                  } else {
                    _mapAc.animateTo(0);
                  }
                });
              },
              child: const Text('Animate'),
            ),
          ),
          body: Stack(
            children: [
              AnimatedBuilder(
                animation: _mapAc,
                builder: (context, child) {
                  return Positioned(
                    left:
                        _mapLeft(hasModel: hasModel, animationValue: _mapAc.value),
                    child: SizedBox(
                      width: MediaQuery.of(context).size.width -
                          DESKTOP_SEARCH_RESULTS_WIDTH,
                      height: MediaQuery.of(context).size.height - TOP_NAV_HEIGHT,
                      child: child,
                    ),
                  );
                },
                child: const MapWidget(),
              ),
            ],
          ),
        );
      }
    }
    
    class MapWidget extends StatelessWidget {
      const MapWidget({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            border: Border.all(color: Colors.red),
          ),
          child: const Text('Map'),
        );
      }
    }