Menu
murmur blog
  • About me
  • Flutter
murmur blog

Flutter widget 소개

Posted on 2019년 10월 8일2019년 10월 28일 by JH Y
Table of Content
본 문서는 flutter 에서 제공하는 공식 tutorial 문서를 참고하여 작성 했습니다.

widget 이란?

Flutter widget은 React 에 영향을 받아 만들어 졌습니다.
기본 아이디어는 widget을 이용해 UI를 만드는 것입니다.

widget은 현재 구성과 state(상태) 에서 화면이 어떻게 보일지를 정의 합니다.
widget의 state가 변경되면 다시 build 됩니다. framework는 이전 상태와 다음 상태를 비교해서 render tree가 최소한만 변경되도록 합니다.

Hello world

아래 간단한 Flutter app은 단순하게 runApp() function을 호출 하는게 다 입니다.

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

위 예제에선 widget tree가 두개의 widget으로 구성 됩니다.
Center widget과 하부 widget Text 입니다.
runApp() function은 Center widget 을 widget tree의 root 로 지정 합니다.

framwork 는 root widget으로 스크린을 커버 하고
"Hello, world" 는 스크린의 중간에 위치 하게 됩니다. 이런경우 text의 방향을 지정 해야 합니다.
MaterialApp widget을 사용할때는, 이부분이 자동으로 처리 됩니다. 나중에 좀 더 자세히 설명 될 것입니다.

app을 만들때 일반적으로 우리는 StatelessWidget 또는 StatefulWidget을 상속받아 widget을 만듭니다.
widget의 주 역할은 build() function을 만드는 것입니다.
framework는 process가 widget의 바탕이 되는 RenderObject를 수행하기까지 이런 widget을 build 합니다.
RenderObject는 widget을 계산하고 표현 합니다.
(영어가 짧아 번역이 엉망입니다. 의역하자면,
widget의 주 역할은 build() function을 만드는 것이고 framework가 알아서 이 widget을 그린다는 의미 입니다.)

Basic widgets

Flutter는 강력한 기본 widget을 제공합니다. 아래는 대표적인 기본 widget입니다.
Text
Text widget 은 style 적용된 text를 출력 합니다.
Row, Column
(Row) 와 (Column) widget은 가로, 세로 방향으로 레이아웃을 만드는 역할을 합니다.
Stack
수평또는 수직으로 배치하는 것 대신에, Stack widget 은 그리는 순서대로 쌓아가며 widget을 배치 할 수 있습니다.
Stack widget 의 Positioned 위젯을 사용하여 자식 위젯들의 top,right,bottom,left 를 기준으로 상대적인 위치를 지정할 수 있습니다.
이는 web 개발시에 absolute positioning 과 비슷 합니다.
Container
Container widget 은 사각형 엘리먼트를 만들어 줍니다.
container는 BoxDecoration으로 background,border, shadow 등을 줄 수 있습니다.
또한 Container에는 margins, padding을 줄 수 있고 크기도 제한 할 수 있습니다. 그리고 Container는 matrix를 이용해 x, y, z 축을 기준으로 변형을 할 수도 있습니다.

아래는 가단한 widget example 입니다.

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // widget subclass의 Fields는 항상 "final"로 선언 합니다. 
  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
    /*1*/
      height: 56.0, // pixel 사용 
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),

    /*2*/

      child: Row(// child widget을 수평으로 배치하는 Row
        // <Widget> 각 row에 들어갈 item widget
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // button이 disable 됩니다.
          ),
    /*3*/
          Expanded( // Expanded 는 child widget으로 남은영역을 채웁니다. 
            child: title,
          ),
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    // Material은 UI가 보여지는 바탕이라 생각하세요.
    return Material(
    /*4*/
      // Column 은 widget을 수평으로 배치 합니다.
      child: Column(
        children: <Widget>[
    /*5*/
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
    /*6*/
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'My app', // application이 아닌 OS에서 사용되는 이름 입니다. 
    home: MyScaffold(),
  ));
}

pubspec.yaml 파일에 uses-material-design: true 로 설정되어 있는지 확인 하세요. Material icons 을 사용하려면 필요 합니다.

name: my_app
flutter:
  uses-material-design: true

많은 Material 디자인 widget은 MaterialApp 내에서 동작 합니다.
MaterialApp의 theme data를 상속 하여 적절한 모양으로 화면을 그리게 됩니다.

source code를 자세히 설명 하겠습니다.

MyAppBar class 를 살펴 보겠습니다.
1) MyAppBar widget은 height 56 픽셀, 왼쪽, 오른쪽 padding 8 픽셀의 Container를 생성 합니다.
2) MyAppBar는 Row 를 통해 child widget을 수평으로 배치 합니다.
3) title widget 은 Expanded 로 지정되었습니다. 이는 여백 영역을 title widget으로 채우겠다 는 의미 입니다.
Expanded는 여러개의 widget을 지정 할 수있습니다. 각 child widget간에 여백 영역을 얼마나 차지할지 flex argument를 지정할 수 있습니다.

MyScaffold class 를 살펴 보겠습니다.
4) MyScaffold widget 은 Column 을 통해 child widget을 수직으로 배치 합니다.
5) Column의 첫번째 위젯으로 MyAppBar 인스턴스를 사용 합니다. MyAppBar는 Column의 가장 상단에 위치하게 됩니다.
MyAppBar에 title로 Text widget을 넘기고 있습니다.
widget을 다른 widget의 argument로 넘기는 것은 매우 강력한 방법입니다.
우리는 이 방법을 통해 generic widget을 다양하고 넓은 범위로 응용해서 사용 할 수 있습니다.
6) MyScaffold 역시 Expanded 속성으로 Text를 중앙에 보여주는 Center widget을 지정 합니다.

예제 코드를 수행하면 아래와 같은 결과를 보실 수 있습니다.

레이아웃에 대한 더 많은 정보는 Layouts 을 참고 하세요.

Material Components

Flutter 는 Material Design을 위한 여러 widget을 제공 합니다. Material app은 MaterialApp widget으로 시작 합니다.
여기에는 Navigator 도 있습니다. Navigator는 widget을 stack 으로 관리 하는데 흔히 아는 routes 역할을 합니다.
Navigator는 화면을 이동하는것을 지원 합니다. MaterialApp을 꼭 사용해야 하는 것은 아니지만 MaterialApp을 사용함으로써 손쉽게 좋은 app을 만들 수 있습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold 는 layout을 잡아주는 역할을 합니다. 
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body 는 screen의 대부분 영역을 말합니다.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // 사용자들에게 도움을 주는 tooltip 입니다.
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

이제, 처음 작성했던 MyAppBar , MyScaffold 코드를 Material design package의 AppBar 와 Scaffold widget을 사용하는 형태로 변경 해보겠습니다.
이렇게 함으로써 App의 좀더 Material 느낌이 될 것입니다.
예를들어 app bar 는 그림자와 타이틀의 스타일을 적절히 잡아 줄 것입니다. 새로운 페이지로 넘어가는 역할을 할 floating action button 도 넣어 보겠습니다.

Scaffold widgets은 다른 widget을 argument로 사용합니다.
이 widget들은 Scaffold가 지정하는 layout 속에서 적절히 배치 됩니다.
비슷하게 AppBar widget 또한 leading,actions,title 속성에 widget을 지정 할 수 있습니다.
Material 에서 지원하는 widget 뿐만 아니라 새롭게 만든 widget또한 가능 합니다.

예제 코드를 수행하면 아래와 같은 결과를 보실 수 있습니다.

더 많은 정보는 Material Components widgets을 참고하세요.

참고: Material 은 Android 스타일의 design 입니다.  
iOS-centric design을 원하면 Cupertino components package 역시 사용할 수 있습니다. 
Material 과 비슷하게 Cupertino 버전의 CupertinoApp 과 CupertinoNavigationBar 등이 있습니다. 

gestures 다루기

대부분의 application은 사용자와 상호작용을 합니다.
간단한 button을 만들어서 제스처를 어떻게 사용 하는지 알아 보겠습니다.

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 36.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(5.0),
          color: Colors.lightGreen[500],
        ),
        child: Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}

GestureDetector widget은 화면에 보여지지는 않고 사용자의 제스처를 인식 합니다. 사용자가 Container를 탭하면 GestureDetector는 onTap() callback 함수를 호출 합니다.
본 실습에서는 메시지를 출력 합니다.
GestureDetector를 통해 탭 뿐만 아니라 드래그,스케일등의 제스처도 인식 가능 합니다.
많은 widget이 GestureDetector 를 제공합니다.
예를들어 IconButton,RaisedButton,FloatingActionButton widget은 사용자가 버튼을 탭 했을때 onPressed() callback 을 지원 합니다.

본 예제를 통해 만든 MyButton widget을 바로 전 예제에 추가해서 실습 해보겠습니다.
TutorialHome의 body 에 단순히 Hello, world! text만출력 했었는데 Text widget 대신 MyButton widget을 넣어 보겠습니다.
TutorialHome 코드를 아래와 같이 변경 해보세요.

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold 는 layout을 잡아주는 역할을 합니다. 
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body 는 screen의 대부분 영역을 말합니다.
      body: Center(
        child: MyButton(),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // 사용자들에게 도움을 주는 tooltip 입니다.
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

app을 reload 하면 아래와 같이 나옵니다.

Engage 버튼을 누르면 Debug console 에 MyButton was tapped! 라는 메시지가 출력 될 것입니다.

제스처에 대한 더 많은 정보는, Gestures in Flutter를 참고 하세요.

input에 따라 widget 변경 하기

지금까지, 우리는 stateless widget을 다뤄 보았습니다. 이전에 작성한 코드를 기억 해보세요.
Stateless widget은 parent widget으로 부터 argument를 받아 final 로 선언된 멤버 변수에 저장 합니다.
widget이 build() method를 호출 할때 이 final 멤버 변수를 argument 값으로 지정 했었습니다.

좀더 복잡한 UI를 빌드하기 위해서 Flutter 는 StatefulWidgets 을 사용 합니다.
StatefulWidgets 은 State objects를 만들어서 state관리를 합니다. RaisedButton widget을 사용하는 아래 예제를 통해 StatefulWidgets을 다뤄 보겠습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: Counter(),
  ));
}

class Counter extends StatefulWidget {
  // 이 StatefulWidget class는 State를 위한 것입니다. 
  // 일반적으로 StatefulWidget class는 parent로 부터 받은 값을 final member 변수에 저장하고 이를 State의 build method에서 사용합니다. 
  // 본 예제에서는 parent로 부터 넘어오는 값이 없기에 final 변수도 없습니다. 
  // 위젯의 서브클래스 모든 필드는 final로 선언되어야 합니다.

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

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {    
      // setState() 메소드는 Flutter 프레임워크에게 State가 변경됨을 알립니다. 이를 통해 build method가 다시 호출되고 화면을 다시 그리게 됩니다. 
      // 만약 setState() 호출 없이 _counter 값을 변경하면 이 변경이 화면에 반영되지 않습니다. 
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // build method는 setState가 호출될때 마다 수행 됩니다. 
    // 본 예제에서는 _increment method에 의해 setState()가 호출 됩니다.
    // Flutter framework는 build method를 다시 수행하는데 최적화 되어 있습니다. 

    return Row(
      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}

StatefulWidget 과 State 를 따로 분리한 이유가 궁금하죠?
Flutter 에서는, StatefulWidget 과 State 이 다른 life cycle 을 갖고 있습니다.
Widgets은 Application의 현재 state의 화면을 만드는데 사용 되는 임시적인 객체 입니다.
State 객체는, build() method가 호출되어도 정보를 그대로 유지하고 있습니다.

위 예제는 사용자 input을 받아서 그대로 build() method 에서 사용합니다.
좀 더 복잡한 application에서는,widget 계층은 각각 다른 목적으로 사용 될 것입니다.

예를들어, 날짜나 위치등의 정보를 취합하는 UI를 가진 A라는 widget이 있다 합시다.
다른 B라는 widget은 A widget이 받은 정보를 이용해 현재 화면에 반영합니다.

Flutter 에서,
변경에 대한 알림은 위젯 상속계층 구조의 "위" 방향으로 callback 됩니다. 반면, 화면 flow는 stateless widget위젯을 향해 "아래" 방향으로 진행 됩니다.
이 flow 를 만드는 것이 State 입니다. (내용이 어렵네요. State에 대해서는 다음에 자세히 다루게 될 테니 넘어가세요.)
아래 예제를 보시면 좀더 이해하는데 도움이 될 것입니다.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: Counter(),
  ));
}

/*1*/
class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}
/*2*/
class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: onPressed,
      child: Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Row(
        children: <Widget>[

            CounterIncrementor(onPressed: _increment),
            CounterDisplay(count: _counter),
        ],
        mainAxisAlignment: MainAxisAlignment.center
    );
  }
}

이 예제의 최종 화면 모습은 아래와 같습니다.

이 예제에서 우리는 stateless widget 두개를 만듭니다.
1) 현재 상태를 출력 하는 CounterDisplay stateless 위젯,
2) counter 값을 변경시키는 CounterIncrementor stateless 위젯.

CounterIncrementor는 사용자로부터 정보를 받습니다. 이 경우 Increment 버튼 클릭 입니다.
사용자로부터 onPressed event 가 발생하면
_CounterState 의 _increment() callback함수가 수행 됩니다. 이를 통해 _counter 값을 증가시키고 build 가 수행 됩니다.
_counter 정보는 위젯 계층의 위로 흐릅니다.
이 정보를 화면에 적용하기 위해 CounterDisplay쪽으로 위젯 계층의 아래로 흐릅니다.

MyApp->Counter->_CounterState->Row ->CounterIncrementor -> Increment 버튼 widget
-> CounterDisplay -> count 정보표시 Text widget

최종 결과는 바로전 예제와 동일하지만, 역할별로 Counter값을 증가 시키는 widget, Counter값을 출력 하는 widget으로 분리해서 코드가 명확하고 단순한 형태가 되었습니다.

StatefulWidget에 대한 자세한 정보는 아래 link를 참고하세요:

  • StatefulWidget
  • setState()

지금까지 배운 내용을 모두 적용한 예제

아래 예제는 지금까지 배운 내용을 모두 적용한 것입니다. 쇼핑 어플리케이션은 다양한 제품을 보여주고,
장바구니 기능이 있습니다. ShoppingListItem 을 정의하는 것으로 시작 합니다.

class Product {
  const Product({this.name});
  final String name;
}

typedef void CartChangedCallback(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({Product product, this.inCart, this.onCartChanged})
      : product = product,
        super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // 본 class 에서 사용하는 테마는 BuildContext에 의해 결정됩니다. 다른 파트는 다른 테마를 가질 수도 있습니다.
    // BuildContext는 build의 위치를 지정 합니다. 

    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }

  TextStyle _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

ShoppingListItem widget은 stateless widget의 일반적인 패턴을 따릅니다. 이 위젯은 생성자에서 받은 값을 final 멤버 변수로 저장합니다. 이 값은 build() 함수에서 사용됩니다.
예를 들어, inCart boolean은 현재 테마의 color 와 gray color 사이에서 토글 됩니다.

사용자가 리스트 아이템을 클릭 했을 때 이 위젯은 자신의 inCart 값을 직접 변경하지않고 parent로부터 전달받은 onCartChanged function를 호출합니다.
이 패턴은 State를 상위 계층의 widget 에 저장하게 하고, 더 긴 시간 동안 state 정보가 유지되도록 합니다.

익스트림한 케이스로 보면 runApp() widget 에 state를 저장하면 application 의 lifetime 동안 state 정보가 유지 됩니다.

parent widget이 onCartChanged callback을 받았을 때 parent 는 자신의 state를 update 합니다. 바로 이때 build 가 다시 수행되고, 변경된 inCart값을 가지는 새로운 ShoppingListItem 인스턴스를 생성 합니다.
이때 전체를 다시 Rendering 하는 것이 아니라 Flutter framework에서 자동으로 이전 widget과 비교하여 변경된 부분만을 다시 적용합니다.

다음은 parent widget에 변경가능한 state를 저장하는 예제 입니다. :

class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);

  final List<Product> products;

  // framework 는 widget이 계층구조에 들어오면 createState() 를 호출 합니다.  
  // 만약 parent widget이 기존과 같은 key를 가진 동일한 widget을 사용 한다면, framework는 State 객체를 재사용합니다.  

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

class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = Set<Product>();

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // 사용자가 장바구니 안의 무엇인가를 변경하면 _shoppingCart 값이 바뀌어야 합니다.  
      // 이는 build 가 다시 필요하므로 setState() 내부에서 이루어 집니다.  
      // setState()에 의해 build 가 불려지면 화면이 update 될 것입니다.  

      if (!inCart)
        _shoppingCart.add(product);
      else
        _shoppingCart.remove(product);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shopping List'),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'Shopping App',
    home: ShoppingList(
      products: <Product>[
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}

ShoppingList class는 StatefulWidget을 상속합니다. 이것은 변경되는 state를 저장한다는 것을 의미합니다.
ShoppingList widget이 처음 widget tree에 삽입될 때 ShoppingListState의 인스턴스를 생성하기 위해 createState() 함수를 호출합니다.
(State의 서브클래스는 private을 의미하는 underscore(
)로 시작되도록 이름을 지정 해야 합니다.)

ShoppingList widget의 parent가 rebuild 될 때 parent는 ShoppingList 인스턴스도 새로 만들어야 합니다.
하지만 ShoppingList widget이 이미 widget tree에 있다면 _ShoppingListState 인스턴스를 재사용 합니다.

_ShoppingListState는 자신의 widget 속성을 사용해 현재의 ShoppingList의 속성에 접근 할 수 있습니다.
만약 parent가 rebuild 되고 새로운 ShoppingList를 생성된다면,
_ShoppingListState 도 새로운 값으로 rebuild 됩니다.
만약 widget widget 속성이 바뀌었을 때 알림을 받고 싶다면, didUpdateWidget() 함수를 override 할 수 있습니다.
didUpdateWidget()는 oldWidget 을 넘겨 줍니다. 이를 통해 현재 state의 widget과 oldWidget 을 비교할 수 있습니다.

_ShoppingListState가 _shoppingCart의 제품을 삭제하거나 추가하는등의 수정에 의해 자신의 상태를 변경하면 onCartChanged callback이 발생합니다.
setState() 함수 호출을 통해 이 변경사항을 framework에게 알립니다.
setState() 호출은 위젯이 바뀌어야 할 부분을 표시하고, 앱의 화면이 갱신될때 rebuild 되도록 스케줄링 합니다.

만약 setState()를 호출하는 것을 잊어버린다면, framework는 위젯의 변경될 부분을 알지 못할 것이고 위젯의 build() 함수도 호출되지 않을 것입니다. 그것은 UI가 변경된 상태를 반영하지 않을 것을 의미합니다.

만약 setState()를 호출하지 않으면, framework는 변경사항을 알 수 없고, build() 함수도 불려지지 않습니다.그러면 변경사항이 화면에도 반영되지 않음을 뜻합니다.
이런식으로 state를 관리 함으로써, child widget 각각 코드를 따로 작성할 필요가 없습니다. 간단히 build() 함수를 구현해 관리 할 수 있습니다.

widget lifecycle event에 대한 응답

StatefulWidget에서 createState() 함수를 호출하면, framework는 새로운 state 객체를 tree에 삽입합니다.
그리고 state 객체의 initState()를 호출합니다.
State의 서브클래스는 한번만 수행되어야 하는 작업을 위해 initState를 override할 수 있습니다.

예를 들어, 애니메이션을 구성하거나 플랫폼 서비스를 subscribe하기 위해 initState를 override 할 수 있습니다.
initState는 super.initState를 호출 하는것으로 시작되어야 합니다.

state 객체가 더 이상 필요하지 않을 때, framework는 state 객체의 dispose()를 호출합니다.
dispose함수를 override 하여 작업을 정리하는 코드를 넣으면 좋습니다.
예를 들어, 타이머를 취소하거나 플랫폼 서비스로부터 unsubscribe하기 위해 dispose()를 override 하는 경우가 많습니다.
일반적으로 super.dispose를 함수의 마지막에 넣습니다.

더 많은 정보는 아래 link 를 참고 하세요:
State

Keys

widget이 rebuild 될 때 key를 통해 framework 에 특정 widget을 지정하여 매칭 할 수 있습니다.
기본적으로, framework는 현재와 과거 widget을 runtimeType 순서와 화면에 표시된 순서에 따라 매칭 시킵니다.
key를 사용하려면, 두 widget이 동일한 runtimeType과 동일한 key를 가져야 합니다.

key는 같은 type의 많은 widget 인스턴스를 build 할때 매우 유용 합니다.

예를 들어, ShoppingList widget이 화면을 채우기위해 많은 ShoppingListItem 인스턴스들을 build 한다고 가정해봅시다.
key가 없다면, 현재 build의 첫 번째 항목은 이전 build의 첫 번째 항목과 항상 동기화 됩니다.
예를 들어 무제한 스크롤이 되는 Listview의 첫번째 item A가 scroll 되어 화면에서 사라지고 새로이 화면에 첫번째로 item B가 놓이면,
실제로 A와 B는 다른 item 이지만 framework는 같은 item으로 인식 하게 됩니다.

반면에 각 항목에 semantic key를 할당하면, 무제한 리스트 같은 widget이 효율적이 됩니다.
key를 통해 widget 을 controle 함으로써 무제한 리스트에서 비록 화면에 보이지 않더라도 적절한 순서를 유지 하게 됩니다.
viewport 의 위치를 기준으로 매칭 하는게 아니라 semantic key라는 고유한 값으로 매칭을 하기 때문에 가능 합니다.

Key에대한 더 많은 정보는 아래 링크를 참고 하세요.
Key API.

Global Keys

child widget들을 구별하기 위해 global key를 사용할 수 있습니다. global key는 모든 위젯 계층을 통틀어 유니크 해야 합니다.
(반면, local key는 같은 level의 widget 사이에서만 유니크 하면 됩니다.)
global key는 주어진 위젯의 state를 찾는 데 사용될 수 있습니다.

GlobalKey에 대한 자세한 정보는 아래 링크를 참고 하세요.
GlobalKey API.

이 글 공유하기:

  • 인쇄하기 (새 창에서 열림)
  • 페이스북에 공유하려면 클릭하세요. (새 창에서 열림)
  • 트위터로 공유하기 (새 창에서 열림)
  • LinkedIn으로 공유하기 (새 창에서 열림)
  • 구글 +1에서 공유하려면 클릭하세요 (새 창에서 열림)
  • 친구에게 전자우편으로 보내기 (새 창에서 열림)

관련

flutter widget 플러터 플로터 플루터
Flutter

답글 남기기 응답 취소

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.

카테고리

  • a-Tech
  • Aix
  • ETC
  • Flutter
  • Food
  • K_culture
  • Media
  • Power system
  • Storage

그 밖의 기능

  • 로그인
  • 글 RSS
  • 댓글 RSS
  • WordPress.org

최근 글

  • Box Constraint
  • Flutter responsive UI
  • Flutter 레이아웃
  • Flutter widget 소개
  • Write your first Flutter app, part 2
©2023 murmur blog | Powered by WordPress & Superb Themes
loading 취소
글이 전송되지 않았습니다. 이메일 주소를 확인하세요!
이메일 확인에 실패했습니다. 다시 시도하세요
죄송합니다. 귀하의 블로그에서 이메일로 글을 공유할 수 없습니다.