Search code examples
flutterlayoutalignment

Flutter, Flow inside Align does not respond to the alignment rules


I am trying to use the Flow widget of Flutter, aligned to the top-right of the screen, and translating towards left. I tried using Stack + Positioned, I tried using Container + Align. Flow always sticks to the top-left. Here is the code I am struggling with;

/// Flutter code sample for Flow

// This example uses the [Flow] widget to create a menu that opens and closes
// as it is interacted with, shown above. The color of the button in the menu
// changes to indicate which one has been selected.

import 'package:flutter/material.dart';

void main() => runApp(FlowApp());

class FlowApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flow Example'),
        ),
        body: FlowMenu(),
      ),
    );
  }
}

class FlowMenu extends StatefulWidget {
  @override
  _FlowMenuState createState() => _FlowMenuState();
}

class _FlowMenuState extends State<FlowMenu>
    with SingleTickerProviderStateMixin {
  final double buttonDiameter = 40;
  AnimationController menuAnimation;
  IconData lastTapped = Icons.notifications;
  final List<IconData> menuItems = <IconData>[
    Icons.home,
    Icons.new_releases,
    Icons.notifications,
    Icons.settings,
    Icons.menu,
  ];

  void _updateMenu(IconData icon) {
    if (icon != Icons.menu) setState(() => lastTapped = icon);
  }

  @override
  void initState() {
    super.initState();
    menuAnimation = AnimationController(
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
  }

  Widget flowMenuItem(IconData icon) {
//    final double buttonDiameter =  MediaQuery.of(context).size.width / menuItems.length;

    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 0.0),
      child: RawMaterialButton(
        fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
        splashColor: Colors.amber[100],
        shape: CircleBorder(),
        constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
        onPressed: () {
          _updateMenu(icon);
          menuAnimation.status == AnimationStatus.completed
              ? menuAnimation.reverse()
              : menuAnimation.forward();
        },
        child: Icon(
          icon,
          color: Colors.white,
          size: buttonDiameter - 10,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(color: Colors.green),
      child: Align(
        alignment: Alignment.topRight,
        child: Container(
          constraints: BoxConstraints.tight(
              Size((buttonDiameter) * menuItems.length, buttonDiameter+5)),
          decoration: BoxDecoration(color: Colors.red),
          child: Align( //why this align does not work?
            alignment: Alignment.topRight,
            child: Flow(
              delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
              children: menuItems
                  .map<Widget>((IconData icon) => flowMenuItem(icon))
                  .toList(),
            ),
          ),
        ),
      ),
    );
  }
}

class FlowMenuDelegate extends FlowDelegate {
  FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);

  final Animation<double> menuAnimation;

  @override
  bool shouldRepaint(FlowMenuDelegate oldDelegate) {
    return menuAnimation != oldDelegate.menuAnimation;
  }

  @override
  void paintChildren(FlowPaintingContext context) {
    double dx = 0.0;
    for (int i = 0; i < context.childCount; ++i) {
      dx = context.getChildSize(i).width * i;
      context.paintChild(
        i,
        transform: Matrix4.translationValues(
          -dx * menuAnimation.value,
          0,
          0,
        ),
      );
    }
  }
}


And the resulting screen is;

enter image description here

Can you help me with aligning flow to the top right? Thank you so much, for reading or responding.

ps. It says I am not allowed to create tags. So here is a list for the future: Flow Widget, Align Widget, Flutter

Edit

What I am trying to achieve:

  1. and 2. gifs are achieved. the last one shows how I failed to align it to the right.

Not this one,

Not this one,

Not working


Solution

  • It seems the problem is the Padding widget wrapping the RawMaterialButton widget. So I have replaced it with Align, and also tweaked a little your animation Working code as follows

    /// Flutter code sample for Flow
    
    // This example uses the [Flow] widget to create a menu that opens and closes
    // as it is interacted with, shown above. The color of the button in the menu
    // changes to indicate which one has been selected.
    
    import 'package:flutter/material.dart';
    
    void main() => runApp(FlowApp());
    
    class FlowApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Flow Example'),
            ),
            body: FlowMenu(),
          ),
        );
      }
    }
    
    class FlowMenu extends StatefulWidget {
      @override
      _FlowMenuState createState() => _FlowMenuState();
    }
    
    class _FlowMenuState extends State<FlowMenu>
        with SingleTickerProviderStateMixin {
      final double buttonDiameter = 40;
      AnimationController menuAnimation;
      IconData lastTapped = Icons.notifications;
      final List<IconData> menuItems = <IconData>[
        Icons.home,
        Icons.new_releases,
        Icons.notifications,
        Icons.settings,
        Icons.menu,
      ];
    //  @override Size getSize(BoxConstraints constraints) => Size(100, 100);
    //  @override Size getSize(BoxConstraints constraints) => constraints.biggest / 2;
    
      void _updateMenu(IconData icon) {
        if (icon != Icons.menu) setState(() => lastTapped = icon);
      }
    
      @override
      void initState() {
        super.initState();
        menuAnimation = AnimationController(
          duration: const Duration(milliseconds: 250),
          vsync: this,
        );
      }
    
      Widget flowMenuItem(IconData icon) {
    //    final double buttonDiameter =  MediaQuery.of(context).size.width / menuItems.length;
    
        return Container(
          decoration: BoxDecoration(
            color: Colors.white
          ),
          child: Align(
    //      padding: const EdgeInsets.symmetric(vertical: 0.0),
            alignment: Alignment.topRight,
            child: RawMaterialButton(
              fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
              splashColor: Colors.amber[100],
              shape: CircleBorder(),
              constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
              onPressed: () {
                _updateMenu(icon);
                menuAnimation.status == AnimationStatus.completed
                    ? menuAnimation.reverse()
                    : menuAnimation.forward();
              },
              child: Icon(
                icon,
                color: Colors.white,
                size: buttonDiameter - 10,
              ),
            ),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: MediaQuery.of(context).size.width,
          decoration: BoxDecoration(color: Colors.green),
          child: Align(
            alignment: Alignment.topRight,
            child: Container(
              constraints: BoxConstraints.tight(
                  Size(buttonDiameter * (menuItems.length+1), buttonDiameter+5)),
              decoration: BoxDecoration(color: Colors.red),
              child: Align(
                alignment: Alignment.topRight,
                child: Flow(
                  delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
                  children: menuItems
                      .map<Widget>((IconData icon) => flowMenuItem(icon))
                      .toList(),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class FlowMenuDelegate extends FlowDelegate {
      FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);
    
      final Animation<double> menuAnimation;
    
      @override
      bool shouldRepaint(FlowMenuDelegate oldDelegate) {
        return menuAnimation != oldDelegate.menuAnimation;
      }
    //  @override Size getSize(BoxConstraints constraints) => constraints.biggest / 2;
    
    
      @override
      void paintChildren(FlowPaintingContext context) {
        double dx = 0.0;
        for (int i = 0; i < context.childCount; ++i) {
          dx = 40.0 * i;
          context.paintChild(
            i,
            transform: Matrix4.translationValues(
              -dx * menuAnimation.value,
              0,
              0,
            ),
          );
        }
      }
    }
    

    solution gif