'Flutter AppBar height not changing properly with animation

I'm trying to create an appbar that will show a second row when a action button is clicked. I've managed do to this without animations but I would like to add some animations to the appbar as it's quite jarring without it.

I'm just trying to change the size of the appbar so the appbar will expand to show a row of buttons in the bottom property. At the moment I have the following code;

import 'package:flutter/material.dart';

class AnimatedAppBar extends StatefulWidget implements PreferredSizeWidget{
  final String titleText;
  const AnimatedAppBar({required this.titleText});

  @override
  State<StatefulWidget> createState() => _AnimatedAppBarState();

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

class _AnimatedAppBarState  extends State<AnimatedAppBar> with SingleTickerProviderStateMixin{
  late Animation<double> animation;
  late AnimationController controller;
  bool toggled = false;

  void toggle(){
    toggled ? controller.reverse() : controller.forward();
    toggled = !toggled;
  }

  @override
  void initState(){
    super.initState();
    controller = AnimationController(duration: const Duration(milliseconds: 100), vsync: this);
    animation = Tween<double>(begin: kToolbarHeight, end: kToolbarHeight*2).animate(controller)..addListener(() {setState(() {

    });});
    controller.reset();
  }
  
  @override
  Widget build(BuildContext context) {
    return AppBar(
      title: Text(widget.titleText),
      toolbarHeight: animation.value,
      actions: [
        IconButton(
          icon: const Icon(Icons.person),
          onPressed: (){
            toggle();
          }
        )

      ],
    );
  }

}

I tried to apply the height to the bottom property but this just causes the content of the appbar to move up and down inside the appbar whilst the appbar's height remains constant.



Solution 1:[1]

We can use Column in replace of appbar and simplify the process using AnimatedContainer.

class TestCase extends StatefulWidget {
  const TestCase({Key? key}) : super(key: key);

  @override
  State<TestCase> createState() => _TestCaseState();
}

class _TestCaseState extends State<TestCase>
    with SingleTickerProviderStateMixin {
  bool isExpanded = false;

  void showOption() {
    setState(() {
      isExpanded = !isExpanded;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Container(
            color: Colors.cyanAccent.withOpacity(.3),
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text("title"),
                    IconButton(
                      onPressed: showOption,
                      icon: const Icon(Icons.abc),
                    )
                  ],
                ),

                /// this will be covered by next widget on 0 height
                AnimatedContainer(
                  height: isExpanded ? kToolbarHeight : 0,
                  duration: const Duration(milliseconds: 200),
                  alignment: Alignment.bottomCenter,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: List.generate(
                        5,
                        (index) => IconButton(
                            onPressed: () {}, icon: Icon(Icons.abc))),
                  ),
                ),
              ],
            ),
          ),

          ///scollable widget
          Expanded(
            child: Container(
              color: Colors.red,
            ),
          )
        ],
      ),
    );
  }
}

If you like to use separate widget with PreferredSizeWidget, it get messy to work with and reverse animation doesn't work too well,

class MyAppBar extends StatelessWidget with PreferredSizeWidget {
  MyAppBar({
    Key? key,
    required this.callback,
    required this.titleText,
    required this.height, /// pass different height based on expanded mode
  }) : super(key: key);

  final VoidCallback callback;
  final double height;
  final String titleText;

  @override
  Size get preferredSize => Size.fromHeight(height);

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      key: const ValueKey("animatedAppBar"),
      height: height,
      color: Colors.red,
      duration: const Duration(milliseconds: 400),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            children: [
              Text(titleText),
              IconButton(
                onPressed: callback,
                icon: const Icon(Icons.person),
              )
            ],
          )
        ],
      ),
    );
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yeasin Sheikh