24

Goal: Press a button and have it switch to another page with an animated transition.

For that, I'm using AnimatedSwitcher.

Below is my code:

class WelcomeScreenSwitcher extends State<RandomWords>{
  Widget calledWidget;

  void switchPage(int newNumber, context) {
    if (newNumber == 1) {
      setState(() {calledWidget = welcomeScreen(context);},);
    } else if (newNumber == 2) {
      setState(() {calledWidget = learnMoreScreen(context);},);}
    }

  @override
  Widget build(BuildContext context) {
    if (calledWidget == null) {
      switchPage(1, context);
    }
    return AnimatedSwitcher(
      duration: Duration(milliseconds: 5000),
      child: calledWidget,
    );

  Widget welcomeScreen(context){
    return Scaffold({body: Column(children: [
      RaisedButton(onPressed: () {switchPage(2, context);}, child: Text('B'),),],
        );
      });
    }

  Widget learnMoreScreen(context){
    return Scaffold({body: Column(children: [
      RaisedButton(onPressed: () {switchPage(2, context);}, child: Text('B'),),],
        );
      });
    }
  }

The code is functional. It does actually switch between the two pages, but there's no animation to go with it.

At some point during development, I was getting the animation, but then it just stopped happening and I can't find out why. I don't remember changing anything specific about how I call the AnimatedSwitcher.

If it's of any use, hot reloading also doesn't work anymore. I need to Restart the app to have ANY change register. It used to function properly before beginning work on the AnimatedSwitcher.

poultrynews
  • 591
  • 1
  • 7
  • 15

3 Answers3

100

If the new widget you're switching in, is the same type as previous widget, set the key property to another value to get that animation

for example, this AnimatedSwitcher doesn't work:

  AnimatedSwitcher(
    duration: const Duration(milliseconds: 1000),
    child: (condition)
        ? Container(
            width: 100,
            height: 100,
            color: Colors.red,
          )
        : Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
  );

Because we want to switch between two Containers, and AnimatedSwitcher can't understand the difference of them, so it doesn't animate their switch. If we set different key property in those Containers, then AnimatedSwitcher can understand they are different and does animate their switch.

Just like this:

  AnimatedSwitcher(
    duration: const Duration(milliseconds: 1000),
    child: (condition)
        ? Container(
            key: ValueKey<int>(0),
            width: 100,
            height: 100,
            color: Colors.red,
          )
        : Container(
            key: ValueKey<int>(1),
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
  );
amir-msh
  • 1,192
  • 1
  • 6
  • 8
  • 9
    wasted hours just for this, thanks man! but how do you know this? – Irfandi D. Vendy Jul 25 '20 at 18:43
  • they mention it here: https://www.youtube.com/watch?v=LKKgYpC-EPQ&ab_channel=Flutter – krumpli Sep 14 '20 at 16:49
  • You saved me. Thanks a lot! – Ovidiu Uşvat Oct 14 '21 at 09:09
  • Thanks a lot. I would never have figured this out. – Siddharth Mehra Apr 01 '22 at 05:14
  • How to avoid "duplicated key" issue while switching very fast before the animation is over? – yellowgray Feb 01 '23 at 07:35
  • Hi @yellowgray, using `UniqueKey()` for children is the easiest solution, but in this case, it has some drawbacks, one of which is that when you rebuild your `AnimatedSwitcher` widget without even changing its child, it animates its child. To avoid that, you can define an int variable (let's name it counter), and every time you want to swap your child, increment it by two, then use `ValueKey(counter)` for the first child and `ValueKey(counter+1)` for the second child. Therefore, unwanted animations will also disappear ;) – amir-msh Feb 01 '23 at 14:54
  • @yellowgray You can also use `AnimatedCrossFade` without worrying about "duplicated key" issue! – amir-msh Feb 01 '23 at 15:03
  • This answer needs to be marked as the correct one, greetings! – Javier Garcia Feb 10 '23 at 04:08
4

you can try this

class _AniSwitchState extends State<AniSwitch> {
  Widget calledWidget;

  @override
  Widget build(BuildContext context) {
    void switchPage(int newNumber) {
      if (newNumber == 1) {
        setState(() {calledWidget = WelcomeScreen();},);
      } else if (newNumber == 2) {
        setState(() {calledWidget = LearnMoreScreen();},);}
    }

      if (calledWidget == null) {
        switchPage(1);
      }

    return Column(
      children: <Widget>[
        AnimatedSwitcher(
          duration: Duration(milliseconds: 2000),
          child: calledWidget
        ),
        RaisedButton(
          child: const Text('Increment'),
          onPressed: () {
            setState(() {
              switchPage(2);
            });
          },
        ),
      ],
    );
  }
}

class WelcomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text("Welcome"),
      ],
    );
  }
}

class LearnMoreScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text("hello world"),
      ],
    );
  }
}
key
  • 1,334
  • 1
  • 19
  • 39
0

i am also having this issue and couldn´t solve since few days, i gave different keys to my Widgets even i used AnimatedSwitcher´s layoutBuilder to wrap front widget around a Conrainer() that has given UniqueKey() every time it has changed so maybe it would work but it still no use.

My Code;

           RxInt currentTab = 0.obs;

           RxList<Map<String, Widget>> stepList = [
             {
               'title': Text('Adres Bilgisi'),
               'view': Step1(key: ValueKey(1),)
             },
             {
               'title': Text('Adres Bilgisi'),
               'view': Step2(key: ValueKey(2),),
             },
             {
               'title': Text('Gönderi Detayı'),
               'view': Step3(key: ValueKey(3),),
             }
           ].obs;

           SliverToBoxAdapter(
              child: Obx(() => AnimatedSwitcher(
                key: Key('switcher.tab'),
                duration: new Duration(microseconds: 500),
                switchInCurve: Curves.easeIn,
                layoutBuilder: (currentChild, previousChildren) {
                  return Container(
                    key: UniqueKey(),
                    child: AnimatedSwitcher.defaultLayoutBuilder(currentChild, previousChildren),
                  );
                },
                transitionBuilder: (Widget child, Animation<double> animation) {
                  /*final offsetAnimation = Tween(
                  begin: const Offset(1.0, 0.0),
                  end: const Offset(0.0, 0.0),
                ).animate(animation);
                // 3.
                return ClipRect(
                  child: SlideTransition(
                    position: offsetAnimation,
                    child: child,
                  ),
                );*/
                  return ScaleTransition(scale: animation, child: child);
                },
                child: controller.tabs[controller.currentTab.value.toInt()],
              )),
            )

This is the TabBar code that i change current selected widget;

            TabBarWidget(
              height: 60,
              initialSelectedId: 0,
              tag: 'advertform.tab',
              tabs: [
                ChipWidget(
                  tag: 'advertform.tab',
                  text: "1".tr,
                  id: 0,
                  onSelected: (id) {
                    controller.prevStep.value = controller.onStep.value;
                    controller.onStep.value = id;
                    controller.changeTab(id);
                  },
                ),
                ChipWidget(
                  tag: 'advertform.tab',
                  text: "2".tr,
                  id: 1,
                  onSelected: (id) {
                    controller.prevStep.value = controller.onStep.value;
                    controller.onStep.value = id;
                    controller.changeTab(id);
                  },
                ),
                ChipWidget(
                  tag: 'advertform.tab',
                  text: "3".tr,
                  id: 2,
                  onSelected: (id) {
                    controller.prevStep.value = controller.onStep.value;
                    controller.onStep.value = id;
                    controller.changeTab(id);
                  },
                ),
              ],
            )

            void changeTab(int index) {
              currentTab.value = index;
            }