I'm trying to make a widget that scale down and reappears at the different side, I was expecting it to scale down and scale up regardless of its alignment. But when I try, it looks like as it was sliding from the right to the left.

Tried removing the ListTile from the _buildRightAlignedListTile and uses the text directly and the ValueKey assigned to it but it still looks the same.

Is there any way to prevent this?

H̶e̶r̶e̶ ̶i̶s̶ ̶t̶h̶e̶ ̶s̶c̶r̶i̶p̶t̶ ̶y̶o̶u̶ ̶c̶a̶n̶ ̶t̶r̶y̶ ̶t̶o̶ ̶r̶u̶n̶ ̶i̶n̶ ̶[̶D̶a̶r̶t̶P̶a̶d̶]̶(̶h̶t̶t̶p̶s̶:̶/̶/̶d̶a̶r̶t̶p̶a̶d̶.̶d̶e̶v̶)̶,̶ ̶p̶l̶e̶a̶s̶e̶ ̶h̶a̶v̶e̶ ̶a̶ ̶l̶o̶o̶k̶.̶

EDIT

Finally found a way to share it, please use this DartPad link to reproduce the issue.

You can also copy and paste the script below just in case the link is dead. It was made directly from the dartpad.

EDIT 2

I apologize if my explanation is confusing, I'm having trouble trying to find the right words to explain it. I'm not a native speaker.

I want the right aligned widget to stay on the right and scale down in place until it disappear completely. And then the left aligned widget to scale up on the left, instead of scaling up from the middle to the left.

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _active = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: SwitchingWidget(active: _active),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _toggle,
          child: const Icon(Icons.check)
        ),
      ),
    );
  }

  void _toggle() {
    setState(() {
      _active = !_active;
    });
  }
}

class SwitchingWidget extends StatefulWidget {
  const SwitchingWidget({super.key, this.active = false});

  final bool active;

  @override
  State<SwitchingWidget> createState() => _SwitchingWidgetState();
}

class _SwitchingWidgetState extends State<SwitchingWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 200),
        transitionBuilder: (child, animation) {
          return ScaleTransition(scale: animation, child: child);
        },
        child: widget.active
          ? _buildLeftAlignedListTile()
          : _buildRightAlignedListTile(),
      ),
    );
  }

  Widget _buildLeftAlignedListTile() {
    return const ListTile(
      key: ValueKey(1),
      leading: SizedBox(
        width: 20,
        height: 20,
        child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
      ),
      title: Text(
        'Left aligned',
        style: TextStyle(color: Colors.white),
      ),
    );
  }

  Widget _buildRightAlignedListTile() {
    return const ListTile(
      key: ValueKey(2),
      title: Text(
        'Right aligned',
        textAlign: TextAlign.right,
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}


Solution 1: Yura

Found a solution to this while tinkering around with another stuff. The solution is to use separate AnimatedSwitcher for each widget I want to animate instead of using one and placing it directly with a conditional statement.

The script above has slightly modified and it works perfectly just like what I wanted. You can also copy and paste to run it on the DartPad since it was made from there to try it out.

Please let me know if there is a better approach!

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _active = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: SwitchingWidget(active: _active),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _toggle,
          child: const Icon(Icons.check)
        ),
      ),
    );
  }

  void _toggle() {
    setState(() {
      _active = !_active;
    });
  }
}

class SwitchingWidget extends StatefulWidget {
  const SwitchingWidget({super.key, this.active = false});

  final bool active;

  @override
  State<SwitchingWidget> createState() => _SwitchingWidgetState();
}

class _SwitchingWidgetState extends State<SwitchingWidget> {
  final scaleDuration = const Duration(milliseconds: 200);

  Widget transitionBuilder(child, animation) {
    return ScaleTransition(child: child, scale: animation);
  }

  @override
  Widget build(BuildContext context) {  
    return Container(
      color: Colors.red,
      height: 60,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        mainAxisSize: MainAxisSize.max,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          AnimatedSwitcher(
            duration: scaleDuration,
            transitionBuilder: transitionBuilder,
            child: widget.active
              ? const SizedBox()
              : _buildLeftAlignedWidget(),
          ),
          AnimatedSwitcher(
            duration: scaleDuration,
            transitionBuilder: transitionBuilder,
            child: widget.active
              ? _buildRightAlignedWidget()
              : const SizedBox(),
          ),
        ],
      ),
    );
  }

  Widget _buildLeftAlignedWidget() {
    return Row(
      key: const ValueKey(1),
      mainAxisAlignment: MainAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Container(
          margin: const EdgeInsets.only(right: 16),
          width: 20,
          height: 20,
          child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
        ),
        const Text(
          'Left aligned',
          style: TextStyle(color: Colors.white),
        ),
      ],
    );
  }

  Widget _buildRightAlignedWidget() {
    return const Text(
      key: ValueKey(2),
      'Right aligned',
      textAlign: TextAlign.right,
      style: TextStyle(color: Colors.white),
    );
  }
}