0. Widjets

  • Flutter 위젯은 React에서 영감을 얻어 제작된 프레임워크다.
  • 위젯의 핵심 아이디어는 UI를 위젯에서 빌드하는 것이다.
  • 위젯은 현재 설정과 상태에 따라 표현되며, state(상태) 변화에 따라 app이 자동으로 재빌드 된다.
  • Widget build(BuildContext context) 함수의 구현(implement)을 통해 위젯을 만들 수 있다.

1. runApp

import 'package:flutter/material.dart';

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

class SightStudy extends StatelessWidget {
@override
Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Sight study',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: MyHomePage(title: 'Sight flutter study',),
  );
}
}

runApp함수는 주어진 Widget을 가져 와서 트리의 루트로 만든다. 이 예시에서 위젯 트리의 루트는 SightStudy 위젯이 된다. Flutter는 루트 위젯이 화면을 덮도록(화면에 나타나도록) 한다.

앱을 작성할 때 위젯의 state 관리여부에 따라 StatelessWidget 또는 StatefulWidget의 하위 클래스를 작성한다. StatelessWidget은 말 그대로 state를 다루지 않는, 화면이 로드될 때 한번만 그려주고 변하지 않는 위젯이다. StatefulWidget은 state를 다루는 위젯인데, 앱이 동작하는 동안 Data 변경이 필요한 경우 화면을 다시 그려주는 위젯이다. 자세한 부분은 다음 시간에 배울 것이다.

참고링크: StatelessWidget과 StatefulWidget

2. Basic Widgets / Classes

  • Scaffold image Scaffold 위젯은 말 그대로 발판이다. 이 발판 위에 여러 위젯을 배치하는 것이다. 플러터 앱을 만들었을 때 하얀 바탕이 바로 Scaffold이다. (원래는 검은 바탕이다...!) 여러 위젯을 인수로 사용하며, 각 위젯은 시용자가 지정한 위치의 Scaffold 레이아웃에 배치된다.


  • AppBar image AppBar 위젯을 사용하면 아이콘 버튼 위젯과 제목 위젯의 작업을 전달할 수 있다. 이러한 패턴은 프레임 워크 전체에서 반복되며 자체 위젯을 디자인 할 때 고려할 수 있다.


  • Container
    Container 위젯은 말 그대로 다른 위젯이 들어갈 사각형 공간을 만드는 위젯이다. 컨테이너는 BoxDecoration 위젯으로 배경, 그림자, 테두리 등과 같은 요소들로 꾸밀 수 있다. 또한 margin, padding 도 설정할 수 있다.


  • Row, Column
    Row 와 Column 과 같은 flex widget 을 사용하면, 가로 (Row) 및 세로 (Column) 방향으로 유연한 레이아웃을 만들 수 있다. 이 개체의 디자인은 웹의 플렉스 박스 레이아웃 모델을 기반으로한다.


  • DefaultTabController

    image DefaultTabController는 흔히 알고있는 '네비게이션바'와 같은 위젯이다. DefaultTabController를 이용하면 전환효과와 슬라이드해서 다른 화면으로 전환이 가능하다.
    참고 : 탭을 사용하기위해서는 선택된 탭과 컨텐츠 섹션이 동기화되어야 한다. 이런 작업을 해주는 것이 TabContoroller클레스이다. 이 TabContoroller클레스는 사용자가 만들거나 DefaultTabController위젯을 사용해서 자동으로 만들수도 있다.


  • ElevatedButton

    image

    ElevatedButton은 평평한 Layout에 깊이감을 주기 위해서 사용한다. 그래서 이미 깊이감이 있는 dialog 혹은 card에는 사용하는 것을 자제해야 한다. ElevatedButton.icon을 이용해 버튼에 아이콘을 넣을 수 있다.


  • TextButton

    image

    텍스트 버튼은 경계선 없이 텍스트로만 이루어진 버튼이다. 사용자가 버튼인지 인식하기 어려울 수 있기 때문에 버튼을 배치하는 위치가 중요하다고 볼 수 있다. ElevateButton과 마찬가지로 TextButton.icon을 이용해 버튼에 아이콘을 넣을 수 있다.


  • OutlinedButton

    image

    TextButton에 경계선이 추가된 버튼이라고 볼 수 있다. OutlinedButton.icon을 이용해 버튼에 아이콘을 넣을 수 있다.


  • IconButton

    image

    icon만으로 이루어진 버튼이다. 다른 버튼들과는 다르게 child가 아니라 icon parameter를 통해 icon을 지정할 수 있다. 또, iconSize를 통해서 버튼의 크기를 조정할 수 있다.


  • ButtonBar

    image

    ButtonBar는 end-aligned 정렬의 버튼 Row로, button들을 정렬해주는 역할을 한다. 만약 공간이 부족하다면, column으로 layout 된다.


3. Using Material Components

Flutter는 Material design을 따르는 앱을 빌드하는 데 도움이되는 다양한 위젯을 제공한다. Material 앱은 MaterialApp위젯으로 시작 한다. 문자열로 식별되는 위젯 스택을 관리하는 Navigator를 포함한 앱의 root에 유용한 여러가지 위젯을 빌드한다. Navigator를 사용하면 응용 프로그램 화면간 원활한 전환을 할 수 있다.

참고: Material Components widgetsCupertino widgets을 사용하면 앱을 더 쉽고 예쁘게 만들 수 있다.

4. 간단한 예제

다음은 위에서 소개한 위젯들을 이용한 간단한 예제이다.

소스코드: https://github.com/Somy-John/sight_study_widget/blob/main/lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

material과 cupertino 페키지를 가져왔다. material은 안드로이드의 UI를 담고있으며, cupertino는 iOS의 UI를 담고 있다.

구글에서 아이폰 스타일이라고 할 수 없으니 애플본사가 있는 지역명인 Cupertino라고 했다는 말도 있다.

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

class SightStudy extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sight study',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: WidgetTest(
        title: 'Sight flutter study',
      ),
    );
  }
}

위에서 배웠듯이 runApp을 통해 SightStudy라는 위젯을 루트위젯으로 설정하고, 화면에 나타나도록 해주었다. SightStudy의 home은 WidgetTest로 설정해두었다.

class WidgetTest extends StatefulWidget {
  WidgetTest({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _WidgetTestState createState() => _WidgetTestState();
}

STF인 WidgetTest의 state를 생성해주었다.

class _WidgetTestState extends State<WidgetTest> {
  int num = 0;

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

  void _decrementCounter() {
    setState(() {
      num--;
    });
  }

  Icon starIcon = Icon(Icons.star);

counter에 쓰일 함수와 변수, IconButton에 쓰일 아이콘 변수를 생성해 주었다. setState는 state를 업데이트할 때 쓰는 메소드이다. setState를 호출하지 않고 상태를 직접 변경하면 경로에 재빌드가 예약되지 않는다. 즉, 렌더링이 업데이트되지 않는다.

  @override
  Widget build(BuildContext context) {
    //DefaultTabController위젯을 이용할 것이므로 DefaultTabController를 리턴해준다.
    return DefaultTabController(
        length: 3,
        child: Scaffold(
          //여기서 widget.title은 SightStudy의 home위젯인 widgetTest의 title이다.
            appBar: AppBar(
              title: Text(widget.title),
            ),
          //
            body: TabBarView(
              // TabBarView는 chiledren에 배열을 넘겨준다. 배열 안에 위젯들을 구성할 수 있다.
              children: [
                // 카운터의 코드이다.
                Container(
                    child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      '$num',
                      style: Theme.of(context).textTheme.headline4,
                    ),
                    Padding(padding: EdgeInsets.only(top: 200)),
                    Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          FloatingActionButton(
                            onPressed: _incrementCounter,
                            tooltip: 'Increment',
                            child: Icon(Icons.add),
                          ),
                          FloatingActionButton(
                            onPressed: _decrementCounter,
                            tooltip: 'Decrement',
                            child: Icon(Icons.remove),
                          )
                        ]),
                  ],
                )),

                // timePicker 코드
                // iOS스타일 시간 선택 위젯이다.
                Container(
                    height: 100,
                    child: CupertinoDatePicker(
                      initialDateTime: DateTime.now(),
                      onDateTimeChanged: (DateTime newdate) {
                        print(newdate);
                      },
                      use24hFormat: true,
                      // minimumDate: DateTime.now(), // 주석을 지우면 현재시각 이전의 시각을 선택할 수 없게 할 수 있다.
                      maximumYear: 2022,
                      minuteInterval: 1,
                      mode: CupertinoDatePickerMode.time,
                    )),

                // 버튼들 코드
                // onPressed에 버튼이 눌러졌을 때의 동작들을 지정해줄 수 있다.
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 50),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      ElevatedButton(
                        onPressed: () {},
                        child: Text("ElevatedButton"),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {},
                        icon: starIcon,
                        label: Text("ElevatedButton.icon"),
                      ),
                      TextButton(
                        onPressed: () {},
                        child: Text("TextButton"),
                      ),
                      TextButton.icon(
                        onPressed: () {},
                        icon: starIcon,
                        label: Text("TextButton.icon"),
                      ),
                      OutlinedButton(
                        onPressed: () {},
                        child: Text("OutlinedButton"),
                      ),
                      OutlinedButton.icon(
                        onPressed: () {},
                        icon: starIcon,
                        label: Text("OutlinedButton.icon"),
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          IconButton(
                            onPressed: () {},
                            icon: starIcon,
                            iconSize: 20,
                          ),
                          IconButton(
                            onPressed: () {},
                            icon: starIcon,
                            iconSize: 40,
                          ),
                          Ink(
                            decoration: const ShapeDecoration(
                              color: Colors.lightBlue,
                              shape: CircleBorder(),
                            ),
                            child: IconButton(
                              onPressed: () {},
                              icon: starIcon,
                              iconSize: 20,
                              color: Colors.white,
                            ),
                          ),
                        ],
                      ),
                      // ButtonBar를 보여주기 위해 Card를 사용하였다.
                      Padding(
                        padding: const EdgeInsets.symmetric(vertical: 30.0),
                        child: Card(
                          child: Column(
                            children: [
                              Padding(
                                padding:
                                    const EdgeInsets.symmetric(vertical: 30),
                                child: Column(
                                  children: [
                                    SizedBox(height: 10),
                                    Text(
                                      "This is a Card widget to show how to use ButtonBar",
                                    ),
                                  ],
                                ),
                              ),
                              ButtonBar(
                                children: [
                                  TextButton(
                                    onPressed: () {},
                                    child: Text("TextButton"),
                                  ),
                                  ElevatedButton(
                                    onPressed: () {},
                                    child: Text("ElevatedButton"),
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ),
                      )
                    ],
                  ),
                ),
              ],
            ),
            bottomNavigationBar: BottomBar()));
  }
}

// DefaultTabController위젯의 bottomNavigationBar 파라미터에 다음 코드를 지정해주면, DefaultTabController를 디자인할 수 있다!
class BottomBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue, //색상
      child: Container(
        height: 70,
        padding: EdgeInsets.only(bottom: 10, top: 5),
        child: TabBar(
          indicatorSize: TabBarIndicatorSize.label,
          indicatorColor: Colors.white,
          indicatorWeight: 4,
          labelColor: Colors.white,
          unselectedLabelColor: Colors.black38,
          labelStyle: TextStyle(fontSize: 17),
          tabs: [
            Tab(
              icon: Icon(
                Icons.add,
                size: 20,
              ),
              text: 'Counter',
            ),
            Tab(
              icon: Icon(
                Icons.timer,
                size: 20,
              ),
              text: 'Time Picker',
            ),
            Tab(
              icon: Icon(
                Icons.gamepad,
                size: 20,
              ),
              text: 'Buttons',
            )
          ],
        ),
      ),
    );
  }
}