>

상세 컨텐츠

본문 제목

[부트캠프] 2주차 강의

0. 창업

by 마켓플레이어, 마케터 봉 2025. 2. 19. 16:44

본문

## StatelessWidget & StatefulWidget

```dart hl:21-28,15 title:"StatelessWidget"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: FirstMyWidget(),
      ),
    );
  }
}

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

@override
Widget build(BuildContext context) {
    return Text('나의 첫번째 위젯입니다.');
  }
}
```
- 변경 불가능한 위젯이며, 단순하고 예측가능하여 코드의 유지관리와 테스트/디버그에 유리함.
- StatelessWidget을 생성하려면 만들고자 하는 클래스를 만들고(위의 경우 FirstMyWidget 임), StatelessWidget을 상속받아 build 함수를 재정의 하면 된다.(라인24~26)
![StatelessWidget 라이프사이클](https://teamsparta.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F83c75a39-3aba-4ba4-a792-7aefe4b07895%2F2d6b937f-667c-40a6-ba8d-f06dcdaadf33%2Fstateless_life_cycle.png?table=block&id=29d8c1bd-8c4c-4bc6-8f3d-e198b4e6eced&spaceId=83c75a39-3aba-4ba4-a792-7aefe4b07895&width=770&userId=&cache=v2)
- 플러터에서 StatelessWidget의 경우 상태를 갖지 않는 위젯이다. 따라서 라이프사이클이 간단하다.
- 생성(constructor)과 동시에 Build함수가 호출이되고, 끝나게 된다.


```dart hl:12,15,21-55 point1:21,26 title:"StatefulWidget"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: MyWidget(),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StatefulWidget 예제'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Counting : $_counter'),
            ElevatedButton(
              onPressed: _incrementCounter,
              child: Text('더하기'),
            ),
          ],
        ),
      ),
    );
  }
}
```
- StatefulWidget은 앱 작동 중에 모양과 동작을 변경할 수 있는 위젯으로, 동적인 바로 반영이 되는 UI를 구성할 수 있고, 애니메이션처리나 네트워크요청, 복잡한 데이터 처리와 같이 상태 업데이트 처리를 위한 로직을 만들 때 유연성을 제공함.
- StatefulWidget의 경우 2가지 클래스가 만들어진다.
1. StatefulWidget을 상속 받고 만들어진 MyWidget 클래스.(라인 21)
2. MyWidget의 상태(라인26, state)를 가진 (언더바)MyWidgetState 클래스. ► 여기서 상태가 정의되어지고, 상태관리가 되게 된다.
![StatefulWidget 라이프사이클](https://teamsparta.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F83c75a39-3aba-4ba4-a792-7aefe4b07895%2F713ddcab-5b79-4b7a-af9a-b04d31b4b40c%2Fstateful_life_cycle_.png?table=block&id=4765b1b9-bf38-41f2-9456-fe9650297f3b&spaceId=83c75a39-3aba-4ba4-a792-7aefe4b07895&width=770&userId=&cache=v2)
- StatefulWidget은 생성(constructor) 후 여러 단계를 거쳐 화면을 그리게 된다(build 함수)
- createState : 생성(constructor) 됨과 동시에 다음번 이벤트가 발생하는 것이 상태 생성(createState)이다. 상태 클래스는 바로 이 createState를 통해 생성된다. 즉 State 객체를 생성한다.(라인23)
- initState : 상태 객체가 생성된 다음에 바로 호출된 것으로, 최초 한 번만 호출된다. 상태를 초기화하는 데 사용된다. 변수/애니메이션 초기화 등에 상태된다. 반드시 super.initState() 를 호출해야 한다.
- didChangeDependencies : initState처럼 생성된 다음 한 번 호출되는 객체다. 또는 State 객체의 종속성이 변결될 때 호출된다.
- build(BuildContext context) : 화면에 위젯을 그리는 역할을 함. State가 변경될 때 다시 한 번 호출되고, 부모로부터 새로운 값을 받을 때도 호출되게 된다.
- setState : 상태를 변경하고 화면을 다시 그려야할 때 호출된다. 라인30~32처럼 1개씩 숫자를 카운트 된 것을 다시 화면에 그릴 때 호출될 수 있다.
- didUpdateWidget : 부모로부터 새로운 값을 받을 때는 didUpdateWidget을 사용한다. 이전 상태와 현재 업데이트 받은 상태를 비교해서, build를 할지 말지 결정할 수 있다.




## 뷰위젯 - PageView

```dart title:"PageView - 스와이프, 가로스크롤" hl:15 point1:17-25
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          body: PageView(
        children: [
          Container(
            color: Colors.red,
            child: const Center(
              child: Text(
                "1",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
          Container(
            color: Colors.blue,
            child: const Center(
              child: Text(
                "2",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
          Container(
            color: Colors.yellow,
            child: const Center(
              child: Text(
                "3",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
        ],
      )),
    );
  }
}
```

```dart title:"PageView - 세로 스크롤(가로스크롤에서 1줄만 추가하면됨. 스크롤디렉션)" hl:16
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          body: PageView(
    scrollDirection: Axis.vertical,
        children: [
          Container(
            color: Colors.red,
            child: const Center(
              child: Text(
                "1",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
          Container(
            color: Colors.blue,
            child: const Center(
              child: Text(
                "2",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
          Container(
            color: Colors.yellow,
            child: const Center(
              child: Text(
                "3",
                style: TextStyle(fontSize: 50, color: Colors.white),
              ),
            ),
          ),
        ],
      )),
    );
  }
}
```

```dart title:"PageView - Controller(페이지이동, 얼럿창, 페이지스내핑, 온페이지체인지 등)" hl:31,51-56,68 point1:69,60-67
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(body: SampleWidget()));
  }
}

class SampleWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget> {
  final _controller = PageController();

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      if (_controller.position.maxScrollExtent == _controller.offset) {
        showDialog(
          context: context,
          builder: (context) => const CupertinoAlertDialog(
            content: Text('마지막에 도달했습니다.'),
          ),
        );
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.all(15.0),
            child: ElevatedButton(
              onPressed: () {
                _controller.jumpToPage(1);
              },
              child: Text('2페이지로 가기'),
            ),
          ),
          Expanded(
            child: PageView(
            onPageChanged: (int index) {
                showDialog(
                  context: context,
                  builder: (context) => CupertinoAlertDialog(
                    content: Text('$index 페이지 활성화'),
                  ),
                );
            },
             pageSnapping: false,
              scrollDirection: Axis.vertical,
              controller: _controller,
              children: [
                Container(
                  color: Colors.red,
                  child: const Center(
                    child: Text(
                      "1",
                      style: TextStyle(fontSize: 50, color: Colors.white),
                    ),
                  ),
                ),
                Container(
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      "2",
                      style: TextStyle(fontSize: 50, color: Colors.white),
                    ),
                  ),
                ),
                Container(
                  color: Colors.yellow,
                  child: const Center(
                    child: Text(
                      "3",
                      style: TextStyle(fontSize: 50, color: Colors.white),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    ));
  }
}

```
- 라인31 : 컨트롤러의 포지션(현재 위치)의 맥스 스크롤(최대 스크롤)과 현재 위치의 스크롤(offset)이 일치한다면, showDialog해서 메세지를 띄워라.
- 라인51-56 : 엘리베이터버튼이 눌렸을 때(onPressed), 2페이지로 이동됨.
- 라인60-67 : 해당 페이지에 도달했을 때 showDialog로 특정 텍스트 띄우기.
- 라인68 : 해당 줄을 [삭제]or[true] 값으로 변경하면 페이지가 딱딱 맞춰지고, [false]값으로 넣으면, 화면이 멈춘 지점에서 멈춰진다. 즉, true 값이면 틱톡처럼 다음 아이템으로 넘어가고, false값이면 블로그처럼 내리다 멈춰진다.
- 라인 69 : 컨트롤러를 사용해 화면 움직임을 조정할 때 필수 코드임. 없으면 작동 안됨.(이벤트 처리가 아예 처리가 안됨.)




## 뷰위젯 - ListView

```dart hl:16 point1:17 title:"ListView - scrollDirection, reverse"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: ListView(
      reverse: true,
          scrollDirection: Axis.horizontal,
          children: List.generate(
            10,
            (index) => Container(
              width: 100,
              height: 100,
              margin: const EdgeInsets.all(5),
              color: Colors.red.withAlpha((index + 1) * 25),
            ),
          ),
        ),
      ),
    );
  }
}
```
- 라인16 : 기본 값은 false임. 아이템들의 방향을 반대로 바꾸려면 false로 노출. 
- 라인17 : axis(축)이 horizontal(가로)이다. 즉, 가로 방향으로 스크롤 하도록 만들겠다는 의미. 해당 라인을 없애면 기본값(default)은 vertical(세로)이다.

```dart title:"ListView - controller, physics" point4:40,53-54 hl:48
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SampleWidget(),
      ),
    );
  }
}

class SampleWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget> {
  final _controller = ScrollController(); // 1번

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            SizedBox(
              height: 50,
              child: ElevatedButton(
                onPressed: () {
                  _controller.jumpTo(330); // 3번
                },
                child: const Text('3번영역으로 이동'),
              ),
            ),
            Expanded(
              child: ListView(
                controller: _controller, // 2번
                physics: const ClampingScrollPhysics(), //다른 설정의 클래스를 넣어주시면 됩니다.
                children: List.generate(
                  10,
                  (index) => Container(
                    width: 100,
                    height: 100,
                    margin: const EdgeInsets.all(5),
                    color: Colors.red.withAlpha((index + 1) * 25),
                    child: Center(child: Text(index.toString())),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```
- 라인40/53-54 : 화면 위치 이동시 ► pageview의 경우 몇번 페이지인지 인덱스를 확인해줬지만, listview의 경우 스크롤의 위치(position)을 확인해줘야한다. 이 경우 height가 100이고, margin이 위아래로 5씩이니까, 한개의 아이템당 110씩 더해야 위치가 맞춰질 것이다.
- 라인48 : physics는 화면의 끝에 도달했을 때, 스크롤의 끝에 도달했을 때 나타나는 어떤 현상으로, 살짝 바운스 하듯 화면이 움직인다거나 하는 것이 이에 해당된다.(BouncingScrollPhysics) 혹은 스크롤가능한 영역을 넘어 스크롤을 시도할 때 끝부분에서 잘림(clamp)효과가 발생하는 것도 이에 해당한다.(ClamScrollPhysics). NeverScrollableScrollPhysics은 스크롤링 자체가 안되도록 막는 기능이다.(예를 들면, 어떤 이벤트가 발생된 다음에야 스크롤 가능한 경우 사용)
- 번외로 라인47에(리스트뷰 안에) 여러 코드를 추가해 추가 기능을 줄 수 잇따.
-  [padding: EdgeInsets.all(20),] 과 같은 코드를 추가해, 전체 아이템 리스트에 패딩을 줄 수 있다.
- 혹은 [cacheExtent : 1000,]은 1000 스크롤까지 캐싱시켜놓는 건데, 아마 1000px까지 기억해놓도록 한다는 의미이다.




## 뷰위젯 - GriedView

```dart title:"SliverGridDelegateWithFixedCrossAxisCount 예제" hl:16 point1:17-19
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return  MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 2,
            crossAxisSpacing: 2,
          ),
          children: List.generate(
            100,
            (index) => Center(
              child: Container(
                color: Colors.grey,
                child: Center(child: Text(index.toString())),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
```
- 그리드위젯의 필수 : gridDelegate
- 라인16 - SliverGridDelegateWithFixedCrossAxisCount
- 라인17 - crossAxisCount : 몇분할 할 것인지 여부
- 라인18 - mainAxisSpacing : 위/아래 그리드의 간격
- 라인19 - crossAxisSpacing : 옆 그리드의 간격
- 라인16 - SliverGridDelegateWithMaxCrossAxisExtent
- 라인17 - maxCrossAxisExtent : 유동적으로 분할을 나눌 때 사용. 하나의 그리드가 최대 000px을 넘어갈 때, 반응형으로 변경됨.
- 라인18 - mainAxisSpacing : 상동
- 라인19 - crossAxisSpacing : 상동
- 라인15~16 사이
- scrollDirection: Axis.horizontal ► 스크롤 방향 설정
- reverse: true ► 아이템들의 순서를 거꾸로 할건지 설정


```dart title:"controller, padding 예제" hl:11-17,33-43,60 point1:59
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: const SampleWidget()),
    );
  }
}

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

  @override
  State<SampleWidget> createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget> {
  final _controller = ScrollController();

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      // 1번
      if (_controller.position.maxScrollExtent == _controller.offset) {
        showDialog(
          context: context,
          builder: (context) => const CupertinoAlertDialog(
            content: Text('마지막에 도달했습니다.'),
          ),
        );
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            ElevatedButton(
                onPressed: () {
                  _controller.jumpTo(800);
                },
                child: const Text('28번째로 이동')),
            Expanded(
              child: GridView(
                padding: EdgeInsets.all(30),
                controller: _controller,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisSpacing: 5,
                  crossAxisSpacing: 5,
                ),
                children: List.generate(
                  100,
                  (index) => Center(
                    child: Container(
                      color: Colors.grey,
                      child: Center(child: Text(index.toString())),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```




## 뷰위젯 - TapbarView

```dart title:"탭바" hl:35,51-53 point1:47-49 point2:56-74
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: const SampleWidget()),
    );
  }
}

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

  @override
  State<SampleWidget> createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget>
    with TickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(
      length: 3,
      vsync: this,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TabBar(
            controller: _tabController,
            labelColor: Colors.blue,
            unselectedLabelColor: Colors.grey,
            labelPadding: const EdgeInsets.symmetric(vertical: 20),
            tabs: const [
              Text('메뉴1'),
              Text('메뉴2'),
              Text('메뉴3'),
            ],
          ),
          Expanded(
            child: TabBarView(
              controller: _tabController,
              children: [
                Container(
                  color: Colors.blue,
                  child: Center(child: Text('메뉴1 페이지 ')),
                ),
                Container(
                  color: Colors.blue,
                  child: Center(child: Text('메뉴2 페이지 ')),
                ),
                Container(
                  color: Colors.blue,
                  child: Center(child: Text('메뉴3 페이지 ')),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

```
- 라인35 : 몇개의 탭이 구성될 것인지 지정
- 라인51-53 : 각 탭의 명칭 지정. 라인35의 length와 같아야함.
- 라인47-49 : 선택된 탭(라벨)컬러는 블루, 선택되지 않은 라벨(탭)의 컬러는 그레이, 각 라벨(탭)의 패딩은 위 아래로 20px씩 넣음.




## 레이아웃 위젯 - Container

```dart title:"컨테이너" hl:16-19 point1:20-38 point2:39-40
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Container(
          padding: const EdgeInsets.only(
            left: 20,
            right: 20,
          ),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [
                Color.fromARGB(255, 255, 59, 98).withOpacity(0.7),
                Color.fromARGB(255, 255, 59, 98)
              ],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            borderRadius: BorderRadius.circular(10),
            boxShadow: [
              BoxShadow(
                color: Color.fromARGB(255, 255, 59, 98).withOpacity(0.5),
                spreadRadius: 5,
                blurRadius: 7,
                offset: Offset(0, 3), // changes position of shadow
              ),
            ],
          ),
          width: 200,
          height: 150,
          child: Center(
              child: Text(
            'Container',
            style: TextStyle(color: Colors.white),
          )),
        ),
      ),
    );
  }
}


```
- 라인16-19 : 컨테이너 내부 간격 조절
- 라인39-40 : 컨테이너 너비(width), 높이(height) 조절
- 라인20-38 : 그라데이션, 그림자효과, 모서리둥근설정, 컬러 등 고급스러운 UI 구성
- colors : 컨테이너에 들어갈 컬러지정. begin 지점과 end 지점 지정 가능
- borderRadius : 모서리 둥근 설정
- boxShadow : 박스 그림자를 통해 입체적 느낌을 줌.
- spreadRadius, blurRadius : 번짐효과를 줌.
- 모든 위젯에는 child가 존재하여 자식 Widget을 지정할 수 있음.




## 레이아웃 위젯 - SizedBox

```dart title:"위아래 간격"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              color: Colors.red,
              width: 100,
              height: 40,
            ),
            const SizedBox(height: 10),
            Container(
              color: Colors.blue,
              width: 100,
              height: 40,
            ),
          ],
        ),
      ),
    );
  }
}

```
- 간격 조절할 때 SizedbBox를 사용함.


```dart title:"좌/우 간격"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              color: Colors.red,
              width: 100,
              height: 40,
            ),
            const SizedBox(width: 10),
            Container(
              color: Colors.blue,
              width: 100,
              height: 40,
            ),
          ],
        ),
      ),
    );
  }
}

```

## 레이아웃 위젯 - Row/Column

```dart title:"로우(가로)" hl:17 point1:18
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: List.generate(
              5,
              (index) => Container(
                width: 40,
                height: 40,
                color: Colors.red,
                margin: const EdgeInsets.all(5),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

```
- 라인17(MainAxisAlignment) : start, center, end, spaceBetween(시작과 끝에 고정하고, 동일한 간격으로 나머지 아이템을 나눔), spaceEvenly(모든 아이템을 동일한 간격으로 나눔), spaceAround(각 아이템의 margin값을 기준으로 겹치지 않게 아이템을 배치함. 즉, 2~n-1 아이템의 간격은 1&n개보다 두껍고, 1번째의 왼쪽, n번째의 오른쪽보단 얇다.)
- 라인18(CrossAxisAlignment) : baseline, center, end, start, values, stretch
- 세로의 영역을 어떻게 가져갈 것인지에 대한 명령어
- stretch : 부모의 사이즈만큼 쭉 늘려서 가져갈 것이다.
- start : 포함된 영역의 상단 시작점에 배치함
- end : 포함된 영역의 하단 끝점에 배치함


```dart title:"컬럼(세로)" hl:17 point1:18
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: List.generate(
              5,
              (index) => Container(
                width: 40,
                height: 40,
                color: Colors.red,
                margin: const EdgeInsets.all(5),
              ),
            ),
          ),
        ),
      ),
    );
  }
}


```




## 레이아웃 위젯 - Expanded

```dart title:"Row에서 Expanded" hl:19-25
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Expanded(
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
              ...List.generate(
                4,
                (index) => Container(
                  width: 40,
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

```
- 라인19-25 : 가로(로우) 정렬 된 아이템들을 배치하고, 남은 공간만큼 확장(expanded)하겠다는 의미.

```dart title:"Expanded 옵션의 flex 기능 사용"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Expanded(
                flex: 1, // 1
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
              Expanded(
                flex: 3, // 2
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
              Expanded(
                flex: 2, // 1
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

```
- 모든 아이템이 expanded를 사용할 경우, flex를 통해 몇 등분의 영역을 가져갈지 정하는 것. 위 예제는 flex가 1/3/2 이기에 1/6, 3/6, 2/6 만큼 각각 영역을 나눠 가져가는 것임.

```dart title:"Column에서 Expanded 옵션"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Expanded(
                flex: 1, // 1
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
              Expanded(
                flex: 3, // 2
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
              Expanded(
                flex: 2, // 1
                child: Container(
                  height: 40,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

```




## 레이아웃 위젯 - Stack

```dart title:"스택 위젯" hl:16 point1:18-24 point2:25-36
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Stack(
          fit: StackFit.expand,
          children: [
            const CircleAvatar(
              radius: 50,
              child: Icon(
                Icons.person,
                size: 40,
              ),
            ),
            Positioned(
              bottom: 0,
              right: 0,
              child: Container(
                padding: const EdgeInsets.all(7),
                decoration: const BoxDecoration(
                    shape: BoxShape.circle, color: Colors.white),
                child: const Icon(
                  Icons.camera_enhance,
                  size: 24,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

```
- 라인18-24 : 가장 하단에 쌓일 아이템
- 라인25-36 : 그 위에 쌓일 아이템
- 라인26-27 : 밑에 놓인 아이템을 기점으로 좌측(left), 우측(right), 위(top), 아래(bottom)을 지정하며, 해당 영역에서 몇px 떨어질 것인지 지정함. 단, 상하좌우 모두 0px로 둘 경우, 전체를 꽉 채워서 아이템이 들어감.
- 라인17 : 부모 위젯만큼 위젯을 확장 시킴. 기본값이 loose임. 확장할 때 외에는 쓸 일 없음




## 기능성 위젯 - Text, TextField, Switch, Slider, GestureDetector, Image 등

```dart title:"Text - Style" hl:18-27
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: Text(
          '텍스트의 스타일을 봐보자',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
            color: Colors.blue,
            fontStyle: FontStyle.italic,
            letterSpacing: 2.0,
            wordSpacing: 4.0,
            decoration: TextDecoration.underline,
            fontFamily: 'Roboto',
          ),
        )),
      ),
    );
  }
}

```
- 라인20 : fontWeight - 폰트 두께
- 라인22 : fontStyle - 폰트 기울기 여부
- 라인23 : letterSpacing - 자간 간격
- 라인24 : wordSpacing - 단어 간격
- 라인25 : decoration - 밑줄, 취소선 등


```dart title:"TextField - 기본"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: TextField(
    decoration: InputDecoration(
            labelText: '이름을 입력해주세요',
          ),
        )),
      ),
    );
  }
}

```

```dart title:"TextField - 옵션 설정"
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('TextField Example'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: MyCustomForm(),
        ),
      ),
    );
  }
}

class MyCustomForm extends StatefulWidget {
  @override
  _MyCustomFormState createState() => _MyCustomFormState();
}

class _MyCustomFormState extends State<MyCustomForm> {
  final TextEditingController myController = TextEditingController();
  
  @override
  void initState(){
    super.initState();
    myController.addListener(() {
      print(myController.text);
    });
  }

  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          maxLength: 20, //텍스프 필드 내 글자 수 제한
          readOnly: false, //읽기전용인지 여부
          textAlign: TextAlign.left, // 텍스트 정렬(좌/우)
          decoration: InputDecoration(
            labelText: 'Enter your name',
            hintText: 'John Doe',
            border: OutlineInputBorder(),
            counterText: '', // This hides the counter below the TextField
          ),
          controller: myController,
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            // 텍스트 필드의 값 출력
            print(myController.text);
          },
          child: Text('Submit'),
        ),
      ],
    );
  }

  @override
  void dispose() {
    myController.dispose();
    super.dispose();
  }
}
```


```dart title:"Switch 위젯" hl:22-30
import 'package:flutter/material.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var state = false;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Switch(
            value: state,
            onChanged: (bool newValue) {
              // 토글 이벤트 처리
              setState(() {
                state = newValue;
              });
            },
          ),
        ),
      ),
    );
  }
}

```


```dart title:"slider위젯" hl:22-32
import 'package:flutter/material.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var state = 50.0;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: Slider(
          value: state,
          min: 0,
          max: 100,
          onChanged: (double newValue) {
            // 슬라이더 변경값에 따른 이벤트 처리
            setState(() {
              state = newValue;
            });
          },
        )),
      ),
    );
  }
}

```

```dart title:"GestureDetector - 제스쳐를 감지하는 위젯" hl:17-20 point1:21-23
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: GestureDetector(
          onTap: () { //1회 클릭/탭 시
            // 탭 이벤트 발생
            print('클릭');
          },
          onDoubleTap: () { //2회 클릭/탭 시
            print('더블클릭');
          },
              
          child: Container(
            color: Colors.blue,
            width: 100,
            height: 100,
          ),
        )),
      ),
    );
  }
}

```


```dart title:"Image"
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.blue,
        body: Center(
          child: Image.network(
            'https://sudar-life.github.io/images/logo_white_mode.png',
            width: 200,
            height: 200,
          ),
        ),
      ),
    );
  }
}

```

관련글 더보기