Search code examples
flutteruser-interfaceuser-experience

How can I use AnimatedPositioned widget as a child of a row widget?


I'm currently having an issue animating an AnimatedPositioned widget inside a row widget. The widget does not size itself as it should and also throws an error. I imagine it has to do with the row not knowing the size of its child as animation occurs.

Currently the sidebar sizes correctly but i need that invisible box underneath to size as well for page content to be lapped properly.

When I collapse and show the sidebar I'm getting: Incorrect use of ParentDataWidget. error.

I was looking at using AnimatedSize except wanted to check here if theres something simple I can change or do before going into this AnimatedSize change.

I would like the invisible box underneath my sidebar to animate just like my sidebar does. - code is copy/paste ready working example!

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Center(
            child: MyWidget(),
          ),
        ),
      ),
    );
  }
}

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  bool showSidebar = true;

  @override
  Widget build(BuildContext context) {
    double width = MediaQuery.of(context).size.width;
    double height = MediaQuery.of(context).size.height;

    return SafeArea(
      child: Stack(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              Expanded(
                child: SingleChildScrollView(
                  child: Column(
                    children: [
                      Container(
                        width: double.maxFinite,
                        height: 700,
                        color: Colors.red,
                      ),
                      Text(
                        'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
                        softWrap: true,
                      ),
                      Container(
                        width: double.maxFinite,
                        height: 700,
                        color: Colors.yellow,
                      ),
                    ],
                  ),
                ),
              ),
              // Invisible box to allow space for sidebar when shown or hidden
              // SizedBox(
              //   width: (showSidebar) ? 43 : 0,
              //   height: height,                             // Works but is too fast (instant)
              // ),
              AnimatedPositioned(
                right: (showSidebar) ? 0 : -43,
                curve: Curves.ease,
                duration: const Duration(milliseconds: 100),    // Does not work & throws error
                child: SizedBox(          
                  width: 43,
                  height: height,
                ),
              ),
            ],
          ),
          // Sidebar box
          AnimatedPositioned(
            right: (showSidebar) ? 0 : -43,
            curve: Curves.ease,
            duration: const Duration(milliseconds: 100),
            child: Container(
              width: 43.0,
              height: height,
              color: Colors.blue,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                children: const [
                  SizedBox(height: 100.0),
                  Expanded(
                    child: Center(
                      child: RotatedBox(
                        quarterTurns: -1,
                        child: Text(
                          'EXAMPLE MENU BAR - PAGE 1',
                          style: TextStyle(
                              fontSize: 20.0, fontWeight: FontWeight.bold, color: Colors.white),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(left: 5.0, top: 5.0, right: 4.0),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                fakeButton(),
                Column(
                  children: [
                    fakeButton(),
                    fakeButton(),
                    fakeButton(),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // Fake buttons
  Widget fakeButton() {
    return GestureDetector(
      onTap: () => {
        setState(() {
          (showSidebar) ? showSidebar = false : showSidebar = true;
        })
      },
      child: SizedBox(
        height: 35,
        width: 35,
        child: Stack(
          children: const [
            Icon(
              Icons.circle,
              color: Colors.green,
              size: 35.0,
            ),
            Align(
              alignment: Alignment.center,
              child: Text(
                'HIDE',
                style: TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                  fontSize: 9.0,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


Solution

  • AnimatedPositioned only works a child of Stack. If the goal is for the row to move out of the way as the sidebar animates in you could wrap it in its own AnimatedPositioned that shrinks as the sidebar moves:

        return SafeArea(
          child: Stack(
            children: [
              AnimatedPositioned(
                top: 0,
                bottom: 0,
                left: 0,
                right: (showSidebar) ? 43 : 0,
                curve: Curves.ease,
                duration: const Duration(milliseconds: 100),
                child: Row(
                // ...
    

    AnimatedPadding with padding applied only to the right would probably also work.

    Full example on DartPad.