Flutter : 기본 예제 설명 및 해설

koeyhoyh·2022년 7월 4일
6

App_Flutter

목록 보기
10/19

플러터 프로젝트를 처음 생성하면 나오는 예제

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

일부러 주석까지 다 가지고 왔다. (사실 지금까지 잘 읽어보지도, 알려고 하지도 않았다.)

기본 예제는 처음에 실행해보고, 끝이었는데 조금 더 깊이 파고들고 알고 싶어져서 보며 공부하려 이 글을 작성한다.


import 'package:flutter/material.dart';

material design ( MaterialApp() ) 을 사용하기 위한 플러터의 기본 패키지이다.
유사품으로 애플 디자인의 cupertino.dart가 있다.


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

모든 프로그램과 같이, 처음에 main문을 실행한다.

runApp은 widget Tree의 Root를 형성하는 widget을 argument로 받는다.
그리고, 해당 widget을 확장해 화면 전체를 채운다.

-> 여기서는 MyApp()이라는 widget으로 화면을 채운다.

flutter는 => (화살표 표기법)을 지원한다.

int sum(int x, int y) {
  return x + y;
}

해당 함수는 화살표 표기법으로 간단히 만들 수 있다.

int sum(int x, int y) => (x + y);

참고 1 : https://hanco.tistory.com/27
참고 2 : https://stackoverflow.com/questions/65343818/whats-the-meaning-of-arrow-in-dart-flutter

참고 : https://freeprog.tistory.com/462


class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyApp extends StatelessWidget {

class 인 MyApp은 StatelessWidget을 상속한다.

StatelessWidget을 상속한다는 것은, 해당 위젯의 모든 것이 immutable 즉 변경할 수 없고, 모든 값은 final로 변경할 수 없다.

참고 : https://snowdeer.github.io/flutter/2020/06/13/flutter-extends-and-with/
final과 const : https://zoiworld.tistory.com/703


const MyApp({key? key}) : super(key: key);

{key? key} key타입의 key 매개변수가 있거나, null일 수 있다.

MyApp({key? key}) : super(key: key)
MyApp 생성자의 인자인 key는 : 상속해준 클래스의 key를 value로 가진다. (상속받은 클래스의 key) (map) (python의 dict형 같이)
여기서, key는 null일 수 있다.

기본적으로, flutter 의 위젯은 생성자에서 key 매개변수를 받을 수 있다.

위젯이 위젯트리에서 위치를 변경해도 key의 정보는 그대로 남아있기 때문에, 리스트의 컬렉션이 수정될 때 스크롤 위치를 기억하거나 상태를 기억해야 할 때 key가 유용하게 사용된다.

결론적으로는, 위젯트리에서 상태를 보존하고 싶은 곳에 key를 사용하자.

키에 대한 자세한 설명은 여기에...
참고 (key) : https://nsinc.tistory.com/214
https://www.youtube.com/watch?v=kn0EOS-ZiIc
https://stackoverflow.com/questions/52056035/myhomepagekey-key-this-title-superkey-key-in-flutter-what-would-b
https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d


상속받은 클래스의 메소드를 재정의한다.

즉, MyApp 클래스는 StatelessWidget의 Widget build() 을 재정의한다.


Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

Widget build() 함수

대부분의 위젯은 build() 함수를 가지고 있다. return 형이 Widget 이므로 widget을 반환한다. 반환된 위젯은 화면에 나온다.

여기서는
MaterialApp
(Flutter Demo 라는 이름을 가지고,
테마는 ThemeData 안의 primarySwatch (미리 지정되어 있는 견본 색상)을 파란색으로 주었다.
가장 먼저 나오는 화면은 MyHomePage(title: 'Flutter Demo Home Page') 이다.
)


class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

MyHomePage 클래스는 StatefulWidget을 상속받는다.

StatefulWidget

변경 가능한 상태를 가진 위젯
Widget이 빌드될 때 동기적으로 읽거나, 생명주기동안 변경될 수 있는 정보가 있다면 사용하는 것이 좋다.
참고 :
StatefulWidget의 서브클래스에서 상태를 변경하는게 아니라 State쪽에서 상태를 변경하므로 
변경가능한 것들은 전부 State쪽에 선언하고, 
StatefulWidget 서브클래스에서는 변경 가능하지 않은 상수들을 선언한다고 합니다.

출처: https://zeddios.tistory.com/1115 [ZeddiOS:티스토리]
참고 : https://www.youtube.com/watch?v=AqCMFXEmf3w&list=PL-B0-NEYQT8dMVGTVYKv0sv_NOg--wtx1&index=3

MyHomePage 생성자는 key와 title을 가지는데, required 가 붙었으므로 title은 반드시 인자로 가져야 한다.
key는, 저번과 같이 상위 클래스의 key를 상속받는데, null일 수도 있다. ('?')

title은 final로, 한번 정해졌으므로 이제부터는 변경이 불가능하다.


State<MyHomePage> createState() => _MyHomePageState();


출처 : https://bacha.tistory.com/56

Stateful Widget은 createState() 메소드에서 생성한 객체를 반환해야 한다.
그리고 StatefulWidget 안이 아닌, State 클래스에 build 메소드가 있다.

StatelessWidget은 build 메서드에서 생성한 객체를 바로 반환하지만 StatefulWidget은 이 createState() 메서드에서 생성한 객체를 반환한다는 것.

-> _MyHomePageState() 에서 build가 진행 될 것이다.


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class _MyHomePageState extends State<MyHomePage> {

State<MyHomePage>를 상속한 _MyHomePageState 클래스를 만든다.

int _counter = 0;
void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

dart에서 이름의 맨 앞에 _이 붙는다는 것은, private 속성을 가진 것이라고 보면 된다.

void incrementCounter() 라는 함수를 정의한다.
해당 함수가 실행되면, setState()를 통해 Widget이 다시 rebuild(정확히는, build 메소드가 재실행)되고, _counter 의 값이 1 올라간다.

setState() -> State 객체의 상태가 변경되었다는 것을 알린다.



  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),

build 메소드는 뼈대로 Scaffold를 이용한다. 안에 있는 인자로 AppBar와 body를 사용해 화면을 구성할 것이다.

먼저, Appbar에는 제목인 Flutter Demo HomePage 가 들어간다.


body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),

body에는 Center라는 레이아웃 위젯이 들어가는데, 하나의 child를 가운데에 배치한다는 뜻이다.

child로는 Column 이라는 레이아웃 위젯이 들어갔다.
Column은 여러 개의 child를 받을 수 있어 Children을 가진다.

mainAxisAlignment 옵션을 통해 가운데에 정렬했고,
가운데에 정렬 될 자식들로는 Text위젯 2개가 들어갔다.

하나는 ('You have pushed the button this many times:'),
다른 하나는 _counter의 값을 가진다. -> 해당 위젯은 h4의 크기도 가진다.


floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );

Scaffold의 안에는 floatingActionButton 이라는 옵션도 있다.

해당 옵션에는 FloatingActionButton을 사용했는데,
누르면 _incrementCounter 함수가 실행되고,
tooltip(커서를 가져다 대면 해당 설명이 나온다.)으로 'Increment'를 가지고,
버튼을 구성하는 것으로는 (+) 형태의 아이콘을 넣었다.


이상으로 정리를 마쳤습니다.
혹시라도 틀린 점이 있거나, 고치고 싶은 점, 추가하고 싶은 점이 있으시다면 언제든지 댓글, 이메일등으로 연락주시면 확인하겠습니다 : )

profile
내가 만들어낸 것들로 세계에 많은 가치를 창출해내고 싶어요.

0개의 댓글