Vintage appMaker의 Tech Blog

[Flutter] Flow 위젯을 이용한 레이아웃 적용 본문

Source code or Tip/Flutter & Dart

[Flutter] Flow 위젯을 이용한 레이아웃 적용

VintageappMaker 2022. 12. 5. 00:34

Flow 위젯은 레이아웃 위젯과 같이 자식위젯들(children)의 위치를 관리하는 위젯이다. 단지, 어떤 Rule을 정하고 그 Rule에 맞게 자식들을 재배치한다. Flow 위젯은 주로 Animation을 이용하여 FloatingButton같은 기능을 처리할 때 많이 사용된다. 

 

1. Flow는 자식들의 위치를 raw level에서 처리한다. 

2. FlowDelegatge 클래스를 상속해서 만들고 paintChildren, sholdRepaint를 오버라이드 한다. 

3. raw level이다보니 스크린 전역의 값으로 위치를 지정해야 한다. 

 

Flow를 사용하려면

Flow(
  delegate: FlowDelegate을 상속받은 클래스,
  children: [자식위젯들],
);

와 같이 사용하면 된다. 

 

다음은 전체소스이다. 

 

import 'package:flutter/material.dart';

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

class FlowApp extends StatelessWidget {
  const FlowApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flow 예제'),
        ),
        body: Container(),
        floatingActionButton: FlowButton(),
      ),
    );
  }
}

class FlowButton extends StatefulWidget {
  const FlowButton({super.key});

  @override
  State<FlowButton> createState() => _FlowButtonState();
}

class _FlowButtonState extends State<FlowButton>
    with SingleTickerProviderStateMixin {
  late AnimationController buttonExtAni;
  bool hideItem = false;

  @override
  void initState() {
    super.initState();
    buttonExtAni = AnimationController(
      duration: const Duration(milliseconds: 150),
      vsync: this,
    )..addListener(() {
        // 애니메이션 이벤트핸들러
        if (buttonExtAni.status == AnimationStatus.dismissed) {
          setState(() {
            hideItem = true;
          });
        } else if (buttonExtAni.status == AnimationStatus.forward) {
          setState(() {
            hideItem = false;
          });
        }
      });
  }

  List<Widget> buttonList() {
    Widget buildItem(Widget w, [bool bAlawaysVisible = false]) {
      if (bAlawaysVisible) {
        hideItem = false;
      }
      return GestureDetector(
        onTap: () {
          if (buttonExtAni.status == AnimationStatus.completed) {
            buttonExtAni.reverse();
          } else {
            buttonExtAni.forward();
          }
        },
        child: (hideItem == true)
            ? Container()
            : Container(
                padding: EdgeInsets.all(5),
                alignment: Alignment.center,
                width: 50,
                height: 50,
                child: w),
      );
    }

    return [
      for (int i = 0; i < 3; i++)
        buildItem(Text(
          "$i",
          style: TextStyle(color: Colors.black),
        )),
      buildItem(Icon(Icons.burst_mode), true)
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: FlowButtonDelegate(ActionAnimation: buttonExtAni),
      children: [...buttonList()],
    );
  }
}

class FlowButtonDelegate extends FlowDelegate {
  FlowButtonDelegate({required this.ActionAnimation})
      : super(repaint: ActionAnimation);

  final Animation<double> ActionAnimation;

  @override
  bool shouldRepaint(FlowButtonDelegate oldDelegate) {
    return ActionAnimation != oldDelegate.ActionAnimation;
  }

  @override
  void paintChildren(FlowPaintingContext context) {
    double dy = 0.0;
    for (int i = 0; i < context.childCount; ++i) {
      var startX = context.size.width - 50;
      var starty = context.size.height - 50;

      dy = context.getChildSize(i)!.height * i + 20;
      context.paintChild(
        i,
        transform: Matrix4.translationValues(
          startX - 30,
          starty - dy * ActionAnimation.value,
          0,
        ),
      );
    }
  }
}

 

 

 

Comments