Coaspe

Flutter.dev 번역 - Introduction to widgets 본문

Flutter/번역

Flutter.dev 번역 - Introduction to widgets

Coaspe 2023. 2. 17. 14:03

Flutter의 widgets은 React로 부터 영감을 받은 Modern framework로 부터 빌드 됩니다.

그것의 주된 아이디어는 UI를 widget 밖에서 빌드한다는 것 입니다. Widgets은 현재 주어진 설정과 state에 따라 view가 어떻게 보여져야하는 지를 나타냅니다. widget의 state가 변할 때 widget은 이것의 description을 rebuilds합니다. 그리고 그 변화는 기본 렌더 트리에서 한 상태에서 다른 상태로 전환하기 위해 필요한 최소한의 변경 사항을 결정하기 위한 framework diffs 입니다.

Hello world

기본적인 Flutter app은 runApp() 함수를 widget과 함께 호출합니다.

import 'package:flutter/material.dart';

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

runApp() 함수는 주어진 Widget을 widget tree의 root로 설정합니다. 위의 예로 설명하자면, widget tree는 Center widget과 그것의 child widget인 Text widget 총 2개의 widgets으로 이루어져 있습니다. Flutter는 root widget이 스크린을 cover하게끔 강제하고, 위의 예에서는 Hello world가 화면의 중앙에 위치함을 의미합니다. 이 예제에서는 text의 방향이 정해질 필요가 있습니다. 하지만 MaterialApp widget을 사용한다면 알아서 처리가 될 것 입니다.

 

app을 작성할 때 widget이 state를 갖는지의 여부에 따라 StatelessWidget, StatefulWidget의 subclasses를 흔히 사용하게 될 것입니다. widget의 주된 역할은 build() 함수를 구현하는 것 이고 그것은 다른 하위 수준(lower-level)의 widgets으로 widget을 구현하는 것 입니다.  framework는 프로세스가 끝날 때 까지 widget의 기하학적 정보를 갖고 있는 RenderObject를 구성하는 위젯들을 빌드 합니다.

 

Basic widgets

Text

Text widget은 styled text를 생성합니다.

RowColumn

수평 수직 방향으로 유동적인 layout을 생성합니다. 이러한 객체들의 디자인은 web의 flexbox layout model의 기본이 됩니다.

Stack

수직 수평 처럼 선형적인 방향이 아닌, Stack widget은 widgets을 paint 순서로 서로 위에 배치할 수 있도록 합니다. 그 다음에  Positioned widget을 Stack의 children으로 사용하면 Stack 안에서 상대적으로 위, 오른쪽, 아래, 옆으로 위치하게 할 수 있습니다.

Container

Container widget은 사각형 요소를 생성합니다.(div같은 것) container는 BoxDecoration으로 꾸밀 수 있습니다. background, border, shadow, padding, margin, size 제한 등 다 가능합니다. 그리고 matrix로 3D 방향으로 변형이 가능합니다.

import 'package:flutter/material.dart';

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

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        // <Widget> is the type of items in the list.
        children: [
          const IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child
          // to fill the available space.
          Expanded(
            child: title,
          ),
          const IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece
    // of paper on which the UI appears.
    return Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: [
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context) //
                  .primaryTextTheme
                  .headline6,
            ),
          ),
          const Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      title: 'My app', // used by the OS task switcher
      home: SafeArea(
        child: MyScaffold(),
      ),
    ),
  );
}

pubspec.yaml 파일의 uses-material-design: true를 해놓는 걸 잊지 마십세요. 이건 미리 정의되어 있는 Material icons를 사용할 수 있게 해줍니다.

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

많은 Material Design widgets은 정상적으로 동작하기 위해서(theme data를 상속받기 위해서)  MaterialApp안에 있어야 합니다. 그러므로 application을 MaterialApp으로 run 합니다.

 

MyAppBar widget은 왼쪽, 오른쪽에 padding 8 pixels, device-independent한 height 56 pixels를 갖는 Container를 생성합니다. container 안에서 MyAppBar는 children을 구성하기 위해서 Row layout을 사용합니다. 중간의 child인 title widget은 Expanded로 표시되어 있고, 그건 다른 children으로 차지되지 않는 여백을 이 widget으로 채우겠다는 것을 의미합니다. 다수의 Expanded children을 사용할 수 있고, 그것들이 각각 차지하는 비율을 정해 사용할 수 있습니다.

 

MyScaffold widget은 children을 vertical column으로 구성하였습니다. column의 가장 위에 MyAppBar instance를 배치하였고, app bar에게 title로 사용하게끔 Text widget을 전달합니다. 다른 widget에게 widget을 arguments로 넘겨주는 것은 많은 방법으로 재사용 될 수 있는 generic widget을 만들 수 있게 하는 강력한 스킬입니다. 마침내 MyScaffold는  Expanded를 사용하여 중앙에 메세지가 있는 body의 남는 부분을 채웁니다.

For more information, see Layouts.

Using Material Components

Flutter는 Material Design을 따르는 app을 build 할 수 있게 해주는 다양한 widget을 제공합니다. MaterialApp은 routes로도 불리며, string으로 구분되는 widgets의 stack을 관리하는, Navigator를 포함하며, app의 root에 유용한 widgets들을 빌드하는, MaterialApp widget으로 부터 시작합니다. Navigator는 당신의 앱의 screen에서 screen으로 자연스럽게 transition하게 해줍니다. MaterialApp widget을 사용하는 것은 완전히 선택적인 것이지만 좋은 관습입니다.

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for
    // the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: const IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: const Text('Example title'),
        actions: const [
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: const Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: const FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

위의 코드에서 MyAppBarMyScaffold가 각각 AppBarScaffold widget으로 바뀌었습니다. 그리고 material.dart 덕분에 app은 더욱 Material스럽게 작동합니다. 예를 들면 새로운 app bar는 그림자를 갖고 있고 title text는 알맞은 스타일을 자동으로 상속 받습니다. floating action button 또한 추가 되었습니다.

 

widgets은 widgets의 arguments로 전달된다는 것을 꼭 기억하세요. Scaffoldwidget은 다른 widgets들을 arguments로 전달 받고 각각의 widgets들은 적당한 장소에 배치됩니다. 비슷하게 AppBarwidget안에 leading, actions, title 같은 widgets들이 들어갈 수 있습니다. 이런 패턴은 framework 전반적으로 계속 발생하고, widgets을 디자인 할 때 꼭 고려해야하는 부분입니다.

For more information, see Material Components widgets.

Handling gestures

대부분의 applications은 user가 system과 상호작용 할 수 있는 form을 갖고있습니다. 상호작용하는 app을 만드는 첫 단계는 input gestures를 감지하는 것입니다. 그런 것들이 어떻게 가능한지 간단한 버튼을 예로 확인해봅시다.

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 50.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: const Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyButton(),
        ),
      ),
    ),
  );
}

 GestureDetector widget은 외형이 없지만 user의 gestures를 감지합니다. user가 Container를 탭하면, GestureDetectoronTap() 을 호출하고, 위의 경우에는 console에 메세지를 출력합니다. 당신은 GestureDetector로 tap, drag, scale 등 다양한 input gestures를 감지할 수 있습니다.

 

많은 widgets들은 다른 widgets들을 위한 선택적인 callbakcs을 위해 GestureDetector 를 사용합니다.

예를들면 IconButtonElevatedButton, FloatingActionButton widgets 들은 user가 widget을 tap 했을 때 발생하는 onPressed() callbacks를 갖고 있습니다.

 

더 많은 정보를 보고 싶다면, Gestures in Flutter 를 방문해주세요.

Changing widgets in response to input

지금까지 우리가 만든 페이지는 stateless widgets(상태가 없는 widgets)만을 사용했습니다. Stateless widgets은 부모 widgets으로부터 arguments를 받고 final member 변수에 저장합니다.

 

더 다양한 유저 input에 반응하는 것 처럼 더 복잡한 경험을 빌드하기 위해서 applications은 state를 사용합니다. Flutter는 이런 아이디어를 구현하기 위해 StatefulWidgets을 사용합니다.  StatefulWidgets은 state를 갖는 State objects를 생성하는 특별한 widgets입니다. ElevatedButton을 활용한 다음의 예제를 통해 확인해봅시다.

import 'package:flutter/material.dart';

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

  const Counter({Key? key}) : super(key: key);

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

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

  void _increment() {
    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 you
      // change _counter without calling setState(), then
      // the build method won't be called again, and so
      // nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment 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 changes instances of widgets.
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
        const SizedBox(width: 16),
        Text('Count: $_counter'),
      ],
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Counter(),
        ),
      ),
    ),
  );
}

이쯤되면 StatefulWidgetState가 왜 분리된 객체인지 궁금할 것이다. Flutter에서 이 두개의 객체는 다른 life cycles(생명 주기)를 가집니다. Widgets은 현재 상태에서 application을 표현하는 일시적인 객체입니다. State 객체는 반대로 build() 함수 사이에 일관적으로 지속되는 객체입니다.

 

위의 예에서 user의 input을 받고 바로 그 결과를 build() 함수에서 사용합니다. 더 복잡한 applications에서, widget 계급의 다른 부분들은 각기 다른 역할을 다 합니다. 예를 들면 한 widget은 구체적인 정보를 모으는 것을 목표로 복잡한 UI를 보여줄 것이고, 다른 widget은 전체적인 외형을 바꾸기 위해 그 정보를 이용할 것 입니다.

 

Flutter에서 변화의 알림은 callbacks을 이용해 widget hierarchy(계급)을 거슬러 "올라"가는 동시에, 현재 state는 UI를 보여주는(do presentation) stateless widgets으로 흘러 "내려"갑니다. 이런 흐름을 제어하는 부모(parent)가 State 입니다. 다음의 복잡한 예가 이것의 대표적인 예 입니다.

import 'package:flutter/material.dart';

class CounterDisplay extends StatelessWidget {
  const CounterDisplay({required this.count, Key? key}) : super(key: key);

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  const CounterIncrementor({required this.onPressed, Key? key})
      : super(key: key);

  final VoidCallback onPressed;

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

class Counter extends StatefulWidget {
  const Counter({Key? key}) : super(key: key);

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

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

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

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        CounterIncrementor(onPressed: _increment),
        const SizedBox(width: 16),
        CounterDisplay(count: _counter),
      ],
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Counter(),
        ),
      ),
    ),
  );
}

두개의 stateless widgets을 주목하세요! counter(ConuterDisplay)를 displaying하는 것과 counter(ConuterIncrementor)를 changing하는 것을 분리하였습니다. 비록 결과는 앞의 예와 동일하지만, 책임의 분리(separation of resopnsibility)는 부모에서 단순성을 유지하면서 개별 위젯에 더 큰 복잡성을 캡슐화할 수 있게 합니다.

 

Bringing it all together

다음은 앞선 개념을 종합한 보다 완전한 예입니다. 가상 쇼핑 애플리케이션은 판매하기 위해 제공되는 다양한 제품을 표시하고, 구매를 위해 쇼핑 카트를 유지합니다. presentation class인 ShoppingListItem을 정의하는 것부터 시작합니다.

import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = Function(Product product, bool inCart);

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

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

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

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

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

    return const 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)),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: ShoppingListItem(
            product: const Product(name: 'Chips'),
            inCart: true,
            onCartChanged: (product, inCart) {},
          ),
        ),
      ),
    ),
  );
}

ShoppingListItem widget은 전형적인 stateless widgets 패턴을 따릅니다. 이것은 build() 함수가 실행되는 동안 사용할 final 멤버 변수를 생성자로 부터 전달 받고 저장합니다. 얘를 들면 inCart boolean 변수는 두가지 UI를 토글 합니다. 하나는 현재 theme의 주된 색을 사용하는 것이고, 다른 하나는 회색을 사용하는 것 입니다.

 

user가 리스트의 아이템을 tap하면, widget은 inCart의 value를 즉시 변경하지 않습니다. 대신에, widget은 부모 widget으로 부터 전달받은 onCartChanged 함수를 호출합니다. 이런 패턴은 당신이 보다 높은 계층의 widget에 state를 저장 할 수 있게 해주고 좀 더 긴 시간동안 state를 유지시켜줍니다. 극단적으로 runApp()에 전달되는 widget에 저장된 state는 application의 생명주기 동안 유지됩니다.

부모가 onCartChanged callback을 전달 받으면, 부모는 이것의 내부 state를 갱신하고 그것은 새로운 inCart value를 갖는 ShoppingListItem instance를 생성하고 부모를 rebuild하게 합니다. 비록 부모가 rebuild 할 때 ShoppingListItem의 instance를 생성한다고 해도, 그런 operation들은 framework에서 RenderObject에 있는 이전에 built된 widget과, 새롭게 build 될 widget을 비교하여 차이점만을 새롭게 build하므로 적은 소모값을 요구합니다.

 

다음은 mutable state를 저장하는 부모 widget의 예입니다.

import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = Function(Product product, bool inCart);

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

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

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

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

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

    return const 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),
      ),
    );
  }
}

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

  final List<Product> products;

  // The framework calls createState the first time
  // a widget appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses
  // the State object instead of creating a new State object.

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

class _ShoppingListState extends State<ShoppingList> {
  final _shoppingCart = <Product>{};

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need
      // to change _shoppingCart inside a setState call to
      // trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shopping List'),
      ),
      body: ListView(
        padding: const 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(const MaterialApp(
    title: 'Shopping App',
    home: ShoppingList(
      products: [
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}

StatefulWidget을 extends하는 class인 ShoppingList는 mutable state를 저장한다는 의미를 내포합니다. ShoppingList widget이 tree에 처음 삽입되면, frameworkcreateState() 함수를 호출하여 tree의 해당 위치와 연결할 _ShoppingListState의 새 instance를 만듭니다. (State의 subclasses들은 관습적으로 private한 상세 구현이라는 것을 알려주기 위해 이름의 첫 글자를 언더바(_)로 시작한 다는 것을 알아두세요.) 이 widget의 부모가 rebuild할 때, 부모는 ShoppingList의 instance를 다시 생성하지만, framework가 createState를 다시 호출하는 대신에 tree에 이미 있던 _ShoppingListState instance를 재사용합니다. 

 

현재 ShoppingList의 프로퍼티들에 접근하기 위해서, _ShoppingListState는 이것의 widget 프로퍼티를 사용 할 수 있습니다. 만약에 부모가 rebuild하고 새로운 ShoppingList를 생성한다면, _ShoppingListState는 새로운 widget value와 함 깨 rebuild 될 것 입니다. widget 프로퍼티가 변경될 때 알림을 받으려면 이전 widget을 현재 widget과 비교할 수 있도록 oldWidget을 전달하는 didUpdateWidget() 함수를 재정의하세요.

 

OnCartChanged callback을 처리할 때, _ShoppingListState_shoppingCart로 부터 물건을 없애거나 더하면서 이것의 내부 state를 변화시킵니다. framework에게 내부 state를 변경하였다는 것을 알려주기 위해서 그런 과정들을 setState() 로 감쌉니다. setState을 호출 하면  이 widget을 dirty로 표시하고 다음 번에 앱에서 화면을 업데이트해야 할 때 다시 빌드하도록 예약합니다. 만약 당신이 widget 내부의 state를 변경 할 때 setState를 호출하는 것을 잊어버린다면, framework는 당신의 widget이 dirty인지 알 수 없고 widget의 build() 함수를 호출하지 않을 것 입니다. 그리고 그것은 UI가 변화된 state에 맞춰 갱신되지 않는 다는 것을 의미합니다. 이런한 방법으로 state를 관리하므로서 당신은 child widgets을 갱신하고(update) 생성(create)하는 코드를 분리해서 작성 할 필요가 없습니다. 대신에 그저 build 함수를 구현하면 알아서 두가지 일을 처리해줄 것 입니다.

 

Responding to widget lifecycle events

StatefulWidget에서 createState()를 호출 한 이후, framework는 새로운 state 객체를 tree에 삽입하고 state 객체에 initState()를 호출합니다. State의 subclasses들은 initState를 override(재정의) 하여 한 번만 수행해야 하는 작업을 수행할 수 있습니다. 예를들면 애니메이션을 설정하거나 플랫폼 서비스를 구독하기 위해 initState를 재정의합니다. initState의 구현은 super.initState를 호출하며 시작되어야만 합니다.

 

state 객체가 더 이상 필요하지 않다면, framwork는 dispose()를 state 객체에 대해 호출합니다. 어떤 정리 작업을 수행하려면 dispose 기능을 재정의하세요. 예를들면, 플랫폼 서비스의 구독을 취소하거나 타이머를 취소하는 것들이 있습니다. dispose의 구현은 보통 super.dispose를 선언하면서 끝이 납니다.

 

For more information, see State.

 

Keys

widgets을 재구성할 때 프레임워크가 다른 widgets과 일치하는 widget을 제어하려면 keys를 사용하세요. 기본적으로 framework는 그들의  runtimeType와 나타나는 순서에따라 widgets의 현재와 이전의 build를 매치합니다. keys를 사용하는 경우 framework는 두 widget은 동일한 runtimeType뿐만 아니라 동일한 key의 소유를 요구합니다.

 

Keys는 같은 타입의 widget의 instances들을 많이 생성 하는 widget에서 유용합니다. 자신의 표시영역(visible region)을 충분히 채울 만큼 ShoppingListItem instance를 생성하는 ShoppingList widget을 예로 들 수 있습니다.

  • keys가 없다면, 현재 build의 첫 번째 항목은 항상 이전 빌드의 첫 번째 항목과 동기화(sync)됩니다. 의미적으로 목록의 첫 번째 항목이 화면 밖으로 스크롤되어 뷰포트에 더 이상 표시되지 않는 경우에도 마찬가지입니다.
  • 목록의 각 엔트리를 "sementic" key로 할당함으로써, framwork가 일치하는 sementic key와 함께 엔트리를 동기화하기 때문에 무한 리스트는 더 효율적일 수 있습니다. (key로 목록을 구별 할 수 있다는 말) 또한, 항목을 의미적으로 동기화한다는 것은 stateful child widgets에 유지된(retained) 상태가 뷰포트에서 동일한 숫자 위치(numerical position)에 있는 항목이 아니라 동일한 의미 항목(semantic entry)에 부여(attached)된다는 것을 의미합니다.

For more information, see the Key API.

 

Global keys

Global keys를 사용하여 child widgets을 고유하게 식별합니다. Global keys는 형제 간(예를 들면 리스트 안에 있는 항목들이 형제이다.)에 고유해야 하는 local key와 달리 전체 widget 계층에서 전체적으로 고유해야 합니다. Global keys는 전체적으로 고유하기 때문에 Global keys를 사용하여 widget과 연결된 state를 검색할 수 있습니다.

Comments