๊ด€๋ฆฌ ๋ฉ”๋‰ด

ruriruriya

[Flutter] Flutter ๋ ˆ์ด์•„์›ƒ ์ดํ•ดํ•˜๊ธฐ(Layout) ๋ณธ๋ฌธ

๐Ÿ“ฑFlutter/Flutter Framework

[Flutter] Flutter ๋ ˆ์ด์•„์›ƒ ์ดํ•ดํ•˜๊ธฐ(Layout)

๋ฃจ๋ฆฌ์•ผใ…‘ 2025. 2. 18. 16:11
๋ฐ˜์‘ํ˜•

 

1. Flutter์—์„œ ๋ ˆ์ด์•„์›ƒ์ด๋ž€?

Flutter๋Š” UI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์œ„์ ฏ(toolkit)์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์€ ์œ„์ ฏ์„ ์ด์šฉํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“œ๋Š” ๋ฐ ์ง‘์ค‘๋œ๋‹ค. ๋ชจ๋“  ์š”์†Œ(ํ…์ŠคํŠธ, ์ด๋ฏธ์ง€, ์•„์ด์ฝ˜, ๋ฒ„ํŠผ ๋“ฑ)๋Š” ์œ„์ ฏ์ด๋ฉฐ, ๋ณด์ด์ง€ ์•Š๋Š” ์š”์†Œ์ธ ํ–‰(Row), ์—ด(Column), ๊ทธ๋ฆฌ๋“œ(Grid) ๋“ฑ๋„ ์œ„์ ฏ์ด๋‹ค.

์œ„์ ฏ์„ ์กฐํ•ฉํ•˜์—ฌ ๋” ๋ณต์žกํ•œ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  Flutter ๋ ˆ์ด์•„์›ƒ์ด ๊ตฌ์„ฑ๋œ๋‹ค.

 

2. ๋ ˆ์ด์•„์›ƒ์˜ ํ•ต์‹ฌ ๊ฐœ๋…

2.1. Constraints

Flutter์—์„œ ๋ ˆ์ด์•„์›ƒ์€ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ๋ถ€๋ชจ ์œ„์ ฏ์€ ์ž์‹ ์œ„์ ฏ์—๊ฒŒ Constaraints๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

  • Constaraints์€ ์ตœ์†Œ ๋ฐ ์ตœ๋Œ€ ๋„ˆ๋น„, ์ตœ์†Œ ๋ฐ ์ตœ๋Œ€ ๋†’์ด๋ฅผ ํฌํ•จํ•˜๋Š” 4๊ฐœ์˜ ๊ฐ’์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.
  • ์ž์‹ ์œ„์ ฏ์€ ํ•ด๋‹น Constaraints ๋‚ด์—์„œ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ  ๋ถ€๋ชจ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ๋ถ€๋ชจ๋Š” ์ž์‹ ์œ„์ ฏ์˜ ์œ„์น˜๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • ์ด ๊ณผ์ •์€ "Constaraints์€ ๋‚ด๋ ค๊ฐ€๊ณ  ํฌ๊ธฐ๋Š” ์˜ฌ๋ผ๊ฐ€๋ฉฐ, ๋ถ€๋ชจ๊ฐ€ ์œ„์น˜๋ฅผ ์„ค์ •ํ•œ๋‹ค(Constraints go down. Sizes go up. Parent sets position)."๋กœ ์š”์•ฝ๋œ๋‹ค.

2.2. Box Types

Flutter์˜ ๋ชจ๋“  ์œ„์ ฏ์€ RenderBox๋ฅผ ํ†ตํ•ด ๋ Œ๋”๋ง๋œ๋‹ค.
๋ฐ•์Šค๋Š” 3๊ฐ€์ง€ ์œ ํ˜•์ด ์žˆ๋‹ค.

  1. ๊ฐ€๋Šฅํ•œ ํ•œ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋Š” ๋ฐ•์Šค (์˜ˆ: Center, ListView)
  2. ์ž์‹ ํฌ๊ธฐ์— ๋งž์ถฐ์ง€๋Š” ๋ฐ•์Šค (์˜ˆ: Transform, Opacity)
  3. ํŠน์ • ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง€๋Š” ๋ฐ•์Šค (์˜ˆ: Image, Text)

Container ์œ„์ ฏ์€ ์„ค์ •์— ๋”ฐ๋ผ ๋™์ž‘์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค.

 

3. ๊ธฐ๋ณธ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•

3.1. ๋‹จ์ผ ์œ„์ ฏ ๋ฐฐ์น˜ํ•˜๊ธฐ

๋‹จ์ผ ์œ„์ ฏ์„ ๋ฐฐ์น˜ํ•˜๋ ค๋ฉด Center, Padding ๋“ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค.

Widget build(BuildContext context) {
  return Center(
    child: BorderedImage(),
  );
}

 

3.2 Container ํ™œ์šฉํ•˜๊ธฐ

Container๋Š” ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ(๋ ˆ์ด์•„์›ƒ, ํŽ˜์ธํŒ…, ํฌ์ง€์…”๋‹, ํฌ๊ธฐ ์กฐ์ •)์„ ์ œ๊ณตํ•˜๋Š” ์œ„์ ฏ์œผ๋กœ, padding๊ณผ margin์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.

Widget build(BuildContext context) {
  return Container(
    padding: EdgeInsets.all(16.0),
    child: BorderedImage(),
  );
}

 

3.3. ์—ฌ๋Ÿฌ ์œ„์ ฏ์„ ์ˆ˜์ง/์ˆ˜ํ‰์œผ๋กœ ๋ฐฐ์น˜ํ•˜๊ธฐ

์—ฌ๋Ÿฌ ์œ„์ ฏ์„ ์ˆ˜์ง ๋˜๋Š” ์ˆ˜ํ‰์œผ๋กœ ๋ฐฐ์น˜ํ•˜๋ ค๋ฉด Column๊ณผ Row ์œ„์ ฏ์„ ์‚ฌ์šฉํ•œ๋‹ค.

Widget build(BuildContext context) {
  return Row(
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

Column๊ณผ Row ์กฐํ•ฉํ•˜์—ฌ ๋ฐฐ์น˜ํ•˜๊ธฐ

Widget build(BuildContext context) {
  return Row(
    children: [
      Column(
        children: [
          BorderedImage(),
          Text('Dash 1'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 2'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 3'),
        ],
      ),
    ],
  );
}

 

4. ์ •๋ ฌ๊ณผ ํฌ๊ธฐ ์กฐ์ •

4.1. ์œ„์ ฏ ์ •๋ ฌํ•˜๊ธฐ

์œ„์ ฏ ์ •๋ ฌ์€ mainAxisAlignment์™€ crossAxisAlignment๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

 

4.2. Expanded๋กœ ํฌ๊ธฐ ์กฐ์ •ํ•˜๊ธฐ

์œ„์ ฏ์ด ํ™”๋ฉด์„ ์ดˆ๊ณผํ•  ๊ฒฝ์šฐ Expanded๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํฌ๊ธฐ๋ฅผ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค.

Widget build(BuildContext context) {
  return Row(
    children: [
      Expanded(child: BorderedImage()),
      Expanded(flex: 2, child: BorderedImage()), // 2๋ฐฐ ํฌ๊ธฐ
      Expanded(child: BorderedImage()),
    ],
  );
}

 

5. ์Šคํฌ๋กค ๊ฐ€๋Šฅํ•œ ๋ฆฌ์ŠคํŠธ ๋งŒ๋“ค๊ธฐ

5.1. ๊ธฐ๋ณธ ListView

ListView๋Š” ์Šคํฌ๋กค์ด ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผํ˜• ๋ ˆ์ด์•„์›ƒ์ด๋‹ค.

Widget build(BuildContext context) {
  return ListView(
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

 

5.2. ListView.builder ์‚ฌ์šฉํ•˜๊ธฐ

์•„์ดํ…œ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ๊ฑฐ๋‚˜ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•ด์•ผ ํ•  ๋•Œ๋Š” ListView.builder๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete ? 'โœ…' : 'โŒ'),
          ],
        ),
      );
    },
  );
}

 

6. ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ๋งŒ๋“ค๊ธฐ

6.1 LayoutBuilder ์‚ฌ์šฉํ•˜๊ธฐ

LayoutBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ ˆ์ด์•„์›ƒ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      if (constraints.maxWidth <= 600) {
        return _MobileLayout();
      } else {
        return _DesktopLayout();
      }
    },
  );
}

 


Flutter์—์„œ ๋ ˆ์ด์•„์›ƒ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์€ ์ œ์•ฝ(Constraints), ๋ฐ•์Šค(Box Types), ์ •๋ ฌ(Alignment), ํฌ๊ธฐ ์กฐ์ •(Sizing), ์Šคํฌ๋กค(Scrolling), ๋ฐ˜์‘ํ˜• ๋””์ž์ธ(Adaptive Layouts) ๋“ฑ์„ ํ™œ์šฉํ•˜๋Š” ๊ณผ์ •์ด๋‹ค. ๋‹ค์–‘ํ•œ ์œ„์ ฏ์„ ์กฐํ•ฉํ•˜์—ฌ ์›ํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Expanded, ListView.builder, LayoutBuilder ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ฉด ๋ณด๋‹ค ์œ ์—ฐํ•œ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜•