플로우
포스트
취소

플로우

Flow

  • FlowDelegate 의 로직에 따라 하위 요소의 크기와 위치를 효율적으로 지정하는 위젯
  • 주로 사용하는 속성
    • FlowDelegate delegate
      • children의 변환 행렬을 제어하는 ​​대리자
      • 필수
    • List children
      • 내부에 배치될 위젯 목록
      • 기본 값 : const []
  • 참고
class FlowMenu extends StatefulWidget {
    const FlowMenu({super.key});

    @override
    State<FlowMenu> createState() => _FlowMenuState();
}

class _FlowMenuState extends State<FlowMenu> with SingleTickerProviderStateMixin {
    late 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), //250ms동안 애니메이션 동작
            vsync: this,
        );
    }

    //메뉴 위젯
    Widget flowMenuItem(IconData icon) {
        final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length; //버튼 크기
        return Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0),
            child: RawMaterialButton(
                fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
                splashColor: Colors.amber[100],
                shape: const 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: 45.0,
                ),
            ),
        );
    }

    @override
    Widget build(BuildContext context) {
        return Flow(
            delegate: FlowMenuDelegate(menuAnimation: menuAnimation), //대리자 지정
            children: menuItems.map<Widget>((IconData icon) => flowMenuItem(icon)).toList(),
        );
    }
}

class FlowMenuDelegate extends FlowDelegate {
    FlowMenuDelegate({required 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,), );
        }
    }
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.