Introduction to widgets | Flutter

 

Introduction to widgets

Learn about Flutter's widgets.

docs.flutter.dev

 

Flutter widgets are built using a modern framework that takes inspiration from React. 

 

The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

핵심 아이디어는 위젯으로 UI를 구축한다는 것입니다.

상태 변화에 따라 최소한의 변경을 하도록 함

 

Hello world

import 'package:flutter/material.dart';

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

 

runApp() 함수는 주어진 위젯을 가져와 위젯 트리의 루트로 만듭니다.

이 예에서 위젯 트리는 Center 위젯과 그 자식인 Text 위젯이라는 두 개의 위젯으로 구성됩니다.

프레임워크는 루트 위젯이 화면을 덮도록 강제합니다.

즉, "Hello, world" 텍스트가 화면 중앙에 표시됩니다.

이 경우 텍스트 방향을 지정해야 합니다.

MaterialApp 위젯이 사용되면 나중에 설명하는 것처럼 이 작업이 자동으로 처리됩니다.

 

앱을 작성할 때 일반적으로 위젯이 상태를 관리하는지 여부에 따라 StatelessWidget 또는 StatefulWidget의 하위 클래스인 새 위젯을 작성합니다.

  위젯의 주요 작업은 다른 하위 수준 위젯과 관련하여 위젯을 설명하는 build() 함수를 구현하는 것입니다.

프레임워크는 위젯의 지오메트리를 계산하고 설명하는 기본 RenderObject를 나타내는 위젯에서

프로세스가 끝날 때까지 이러한 위젯을 차례로 빌드합니다.

Basic widgets

Flutter는 강력한 기본 위젯 모음과 함께 제공되며, 그 중 일반적으로 사용되는 위젯은 다음과 같습니다.

 

Text  텍스트 위젯을 사용하면 애플리케이션 내에서 스타일이 지정된 텍스트 실행을 만들 수 있습니다.

Row, Column  이러한 플렉스 위젯을 사용하면 가로(행) 및 세로(열) 방향 모두에서 유연한 레이아웃을 만들 수 있습니다. 이러한 객체의 디자인은 웹의 플렉스박스 레이아웃 모델을 기반으로 합니다.

(Row는 가로 Linear Layout임)(Column은 세로 Linear Layout임)

Stack 선형 방향(수평 또는 수직) 대신 스택 위젯을 사용하면 위젯을 페인트 순서로 서로 위에 배치할 수 있습니다.

그런 다음 Stack의 자식에 Positioned 위젯을 사용하여 스택의 위쪽, 오른쪽, 아래쪽 또는 왼쪽 가장자리를 기준으로 위치를 지정할 수 있습니다. 스택은 웹의 절대 위치 레이아웃 모델을 기반으로 합니다.

Container 컨테이너 위젯을 사용하면 직사각형 시각적 요소를 만들 수 있습니다.

컨테이너는 배경, 테두리 또는 그림자와 같은 BoxDecoration으로 장식할 수 있습니다. 컨테이너에는 크기에 여백, 패딩 및 제약 조건이 적용될 수도 있습니다. 또한 컨테이너는 행렬을 사용하여 3차원 공간에서 변환할 수 있습니다.

다음은 이러한 위젯과 다른 위젯을 결합한 몇 가지 간단한 위젯입니다.

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  const MyAppBar({required this.title, super.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({super.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 파일의 flutter 섹션에 uses-material-design: true 항목이 있는지 확인하세요.

미리 정의된 머티리얼 아이콘 세트를 사용할 수 있습니다.

재료 라이브러리를 사용하는 경우 일반적으로 이 줄을 포함하는 것이 좋습니다.

 

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

많은 머티리얼 디자인 위젯은 테마 데이터를 상속하기 위해 제대로 표시하려면 MaterialApp 내부에 있어야 합니다.

따라서 MaterialApp으로 애플리케이션을 실행하십시오.

MyAppBar 위젯은 왼쪽과 오른쪽 모두에서 내부 패딩이 8픽셀이고 높이가 56개 장치 독립적 픽셀인 컨테이너를 만듭니다. 컨테이너 내부에서 MyAppBar는 행 레이아웃을 사용하여 자식을 구성합니다. 제목 위젯인 가운데 자식은 Expanded로 표시됩니다. 즉, 다른 자식이 사용하지 않은 남아 있는 사용 가능한 공간을 채우도록 확장됩니다. 여러 개의 Expanded 자식을 가질 수 있으며 Expanded에 대한 flex 인수를 사용하여 사용 가능한 공간을 소비하는 비율을 결정할 수 있습니다.

MyScaffold 위젯은 하위 항목을 세로 열로 구성합니다.

열 상단에 MyAppBar 인스턴스를 배치하고 앱 바에 제목으로 사용할 텍스트 위젯을 전달합니다.

위젯을 다른 위젯에 대한 인수로 전달하는 것은 다양한 방법으로 재사용할 수 있는 일반 위젯을 생성할 수 있는 강력한 기술입니다. 마지막으로 MyScaffold는 Expanded를 사용하여 중앙 메시지로 구성된 본문으로 나머지 공간을 채웁니다.

 

Using Material Components

Flutter는 Material Design을 따르는 앱을 빌드하는 데 도움이 되는 여러 위젯을 제공합니다. Material 앱은 "경로routes"라고도 하는 문자열로 식별되는 위젯 스택을 관리하는 Navigator를 포함하여 앱 루트에 여러 유용한 위젯을 빌드하는 MaterialApp 위젯으로 시작합니다. 내비게이터를 사용하면 애플리케이션의 화면 간에 원활하게 전환할 수 있습니다. MaterialApp 위젯을 사용하는 것은 전적으로 선택 사항이지만 좋은 방법입니다.

import 'package:flutter/material.dart';

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

class TutorialHome extends StatelessWidget {
  const TutorialHome({super.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,
      ),
    );
  }
}

이제 코드가 MyAppBar 및 MyScaffold에서 AppBar 및 Scaffold 위젯으로, 그리고 material.dart에서 전환되었으므로 앱이 좀 더 Material로 보이기 시작합니다. 예를 들어 앱 바에는 그림자가 있고 제목 텍스트는 올바른 스타일을 자동으로 상속합니다. 플로팅 액션 버튼도 추가되었습니다. 위젯은 다른 위젯에 인수로 전달됩니다. Scaffold 위젯은 이름이 지정된 인수로 여러 위젯을 사용하며, 각각은 Scaffold 레이아웃의 적절한 위치에 배치됩니다. 마찬가지로 AppBar 위젯을 사용하면 선행 위젯에 대한 위젯과 제목 위젯의 작업을 전달할 수 있습니다. 이 패턴은 프레임워크 전체에서 반복되며 자신의 위젯을 디자인할 때 고려할 수 있는 것입니다. 자세한 내용은 재료 구성 요소 위젯을 참조하세요.

 

참고: Material은 Flutter에 포함된 2개의 번들 디자인 중 하나입니다. iOS 중심 디자인을 만들려면 자체 버전의 CupertinoApp 및 CupertinoNavigationBar가 있는 Cupertino 구성 요소 패키지를 참조하세요.

Handling gestures

대부분의 응용 프로그램에는 시스템과의 사용자 상호 작용 형식이 포함되어 있습니다. 대화형 애플리케이션을 구축하는 첫 번째 단계는 입력 제스처를 감지하는 것입니다. 간단한 버튼을 만들어 작동하는 방법을 확인하십시오.

 

import 'package:flutter/material.dart';

class MyButton extends StatelessWidget {
  const MyButton({super.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 위젯에는 시각적 표현이 없지만 대신 사용자가 만든 제스처를 감지합니다.

사용자가 컨테이너를 탭하면 GestureDetector가 onTap() 콜백을 호출하며 이 경우 콘솔에 메시지를 인쇄합니다. GestureDetector를 사용하여 탭, 드래그 및 스케일을 포함한 다양한 입력 제스처를 감지할 수 있습니다. 많은 위젯이 GestureDetector를 사용하여 다른 위젯에 대한 선택적 콜백을 제공합니다. 예를 들어 IconButton, ElevatedButton 및 FloatingActionButton 위젯에는 사용자가 위젯을 탭할 때 트리거되는 onPressed() 콜백이 있습니다. 자세한 내용은 Flutter의 제스처를 참조하세요.

Changing widgets in response to input

지금까지 이 페이지는 stateless 위젯만 사용했습니다. Stateless 위젯은 상위 위젯에서 인수를 수신하여 최종 멤버 변수에 저장합니다. 위젯이 build()를 요청하면 이 저장된 값을 사용하여 생성하는 위젯에 대한 새 인수를 파생합니다. 더 복잡한 경험을 구축하기 위해(예: 사용자 입력에 더 흥미로운 방식으로 반응하기 위해) 애플리케이션은 일반적으로 일부 상태를 전달합니다. Flutter는 StatefulWidget을 사용하여 이 아이디어를 캡처합니다. StatefulWidget은 상태를 유지하는 데 사용되는 State 객체를 생성하는 방법을 알고 있는 특수 위젯입니다. 앞에서 언급한 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({super.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(),
        ),
      ),
    ),
  );
}

StatefulWidget과 State가 별도의 객체인 이유가 궁금할 것입니다. Flutter에서 이 두 가지 유형의 객체는 수명 주기가 다릅니다위젯은 현재 상태에서 애플리케이션의 프레젠테이션을 구성하는 데 사용되는 임시 개체입니다. 반면에 상태 개체는 build() 호출 간에 영구적이므로 정보를 기억할 수 있습니다. 위의 예는 사용자 입력을 받아들이고 build() 메서드에서 결과를 직접 사용합니다. 더 복잡한 응용 프로그램에서는 위젯 계층 구조의 다른 부분이 다른 문제를 담당할 수 있습니다. 

예를 들어, 한 위젯은 날짜나 위치와 같은 특정 정보를 수집하기 위한 목적으로 복잡한 사용자 인터페이스를 제공할 수 있고 다른 위젯은 전체 프레젠테이션을 변경하기 위해 해당 정보를 사용할 수 있습니다.

Flutter에서 변경 알림은 콜백을 통해 위젯 계층 구조를 "위로" 흐르고 현재 상태는 프레젠테이션을 수행하는 상태 비저장 위젯으로 "아래로" 흐릅니다. 이 흐름을 리디렉션하는 공통 부모상태입니다. 다음의 약간 더 복잡한 예는 이것이 실제로 어떻게 작동하는지 보여줍니다.

 

import 'package:flutter/material.dart';

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

  final int count;

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

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

  final VoidCallback onPressed;

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

class Counter extends StatefulWidget {
  const Counter({super.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(),
        ),
      ),
    ),
  );
}

카운터 표시(CounterDisplay)와 카운터 변경(CounterIncrementor) 문제를 명확하게 분리하는 두 개의 새로운 상태 비저장 위젯 생성에 주목하십시오. 최종 결과는 이전 예제와 동일하지만 책임을 분리하면 상위 위젯에서 단순성을 유지하면서 개별 위젯에 더 큰 복잡성을 캡슐화할 수 있습니다.

자세한 내용은 StatefulWidget, setState()를 참조하세요.

Bringing it all together

다음은 이러한 개념을 결합한 보다 완전한 예입니다. 가상의 쇼핑 애플리케이션은 판매용으로 제공되는 다양한 제품을 표시하고 의도한 구매를 위한 장바구니를 유지합니다. 프레젠테이션 클래스 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 위젯은 상태 비저장 위젯(Stateless widget)에 대한 공통 패턴을 따릅니다. 생성자에서 받은 값을 최종 멤버 변수에 저장한 다음 build() 함수 중에 사용합니다. 예를 들어, inCart 부울은 현재 테마의 기본 색상을 사용하는 것과 회색을 사용하는 두 가지 시각적 모양 사이를 전환합니다. 사용자가 목록 항목을 탭할 때 위젯은 inCart 값을 직접 수정하지 않습니다. 대신 위젯은 상위 위젯에서 받은 onCartChanged 함수를 호출합니다. 이 패턴을 사용하면 상태를 위젯 계층 구조의 상위에 저장할 수 있으므로 상태가 더 오래 지속됩니다. 극단적으로, runApp()에 전달된 위젯에 저장된 상태는 애플리케이션의 수명 동안 지속됩니다. 부모가 onCartChanged 콜백을 수신하면 부모가 내부 상태를 업데이트하여 부모가 새 inCart 값으로 ShoppingListItem의 새 인스턴스를 다시 만들고 생성하도록 트리거합니다. 부모는 다시 빌드할 때 ShoppingListItem의 새 인스턴스를 생성하지만 프레임워크가 새로 빌드된 위젯을 이전에 빌드된 위젯과 비교하고 기본 RenderObject에만 차이점을 적용하기 때문에 해당 작업은 저렴합니다.

 

다음은 변경 가능한 상태를 저장하는 상위 위젯의 예입니다.

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, super.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) {
          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'),
      ],
    ),
  ));
}

ShoppingList 클래스는 StatefulWidget을 확장하므로 이 위젯은 변경 가능한 상태를 저장합니다. ShoppingList 위젯이 처음 트리에 삽입되면 프레임워크는 createState() 함수를 호출하여 트리의 해당 위치와 연결할 _ShoppingListState의 새로운 인스턴스를 생성합니다. (State의 하위 클래스는 일반적으로 개인 구현 세부 정보임을 나타내기 위해 선행 밑줄로 이름이 지정됩니다.) 이 위젯의 ​​상위가 다시 빌드될 때 상위는 ShoppingList의 새 인스턴스를 생성하지만 프레임워크는 이미 트리에 있는 _ShoppingListState 인스턴스를 재사용합니다. createState를 다시 호출하는 대신. 현재 ShoppingList의 속성에 액세스하기 위해 _ShoppingListState는 위젯 속성을 사용할 수 있습니다. 부모가 새 ShoppingList를 다시 작성하고 생성하면 _ShoppingListState가 새 위젯 값으로 다시 작성합니다. 위젯 속성이 변경될 때 알림을 받으려면 이전 위젯을 현재 위젯과 비교할 수 있도록 oldWidget에 전달된 didUpdateWidget() 함수를 재정의하십시오. onCartChanged 콜백을 처리할 때 _ShoppingListState는 _shoppingCart에서 제품을 추가하거나 제거하여 내부 상태를 변경합니다. 프레임워크에 내부 상태가 변경되었음을 알리기 위해 해당 호출을 setState() 호출로 래핑합니다. setState를 호출하면 이 위젯을 더티로 표시하고 다음에 앱에서 화면을 업데이트해야 할 때 다시 빌드하도록 예약합니다. 위젯의 내부 상태를 수정할 때 setState를 호출하는 것을 잊어버리면 프레임워크는 위젯이 더티인지 알지 못하고 위젯의 build() 함수를 호출하지 않을 수 있습니다. 즉, 사용자 인터페이스가 변경된 상태를 반영하도록 업데이트되지 않을 수 있습니다. 이러한 방식으로 상태를 관리하면 자식 위젯을 만들고 업데이트하기 위해 별도의 코드를 작성할 필요가 없습니다. 대신 두 상황을 모두 처리하는 빌드 기능을 구현하기만 하면 됩니다.

Responding to widget lifecycle events

StatefulWidget에서 createState()를 호출한 후 프레임워크는 새 상태 개체를 트리에 삽입한 다음 상태 개체에서 initState()를 호출합니다. State의 하위 클래스는 한 번만 발생해야 하는 작업을 수행하기 위해 initState를 재정의할 수 있습니다. 예를 들어 애니메이션을 구성하거나 플랫폼 서비스를 구독하려면 initState를 재정의합니다. initState 구현은 super.initState를 호출하여 시작해야 합니다. 상태 객체가 더 이상 필요하지 않으면 프레임워크는 상태 객체에 대해 dispose()를 호출합니다. 정리 작업을 수행하려면 dispose 함수를 재정의하십시오. 예를 들어 타이머를 취소하거나 플랫폼 서비스 구독을 취소하려면 dispose를 재정의합니다. dispose 구현은 일반적으로 super.dispose를 호출하여 종료됩니다. 자세한 내용은 상태를 참조하세요.

Keys

위젯이 다시 빌드될 때 프레임워크가 다른 위젯과 일치하는 위젯을 제어하려면 키를 사용하십시오. 기본적으로 프레임워크는 해당 runtimeType 및 표시되는 순서에 따라 현재 및 이전 빌드의 위젯을 일치시킵니다. 키를 사용하면 프레임워크에서 두 위젯에 동일한 키와 동일한 런타임 유형이 있어야 합니다. 키는 동일한 유형의 위젯의 여러 인스턴스를 빌드하는 위젯에서 가장 유용합니다. 예를 들어, 표시 영역을 채우기에 충분한 ShoppingListItem 인스턴스를 빌드하는 ShoppingList 위젯: 키가 없으면 현재 빌드의 첫 번째 항목은 의미론적으로 첫 번째 항목이 이전 빌드의 첫 번째 항목과 항상 동기화됩니다. 목록이 화면 밖으로 스크롤되어 더 이상 뷰포트에 표시되지 않습니다. 목록의 각 항목에 "의미론적" 키를 할당하면 프레임워크가 일치하는 의미론적 키와 유사한(또는 동일한) 시각적 모양과 항목을 동기화하기 때문에 무한 목록이 더 효율적일 수 있습니다. 또한 항목을 의미적으로 동기화한다는 것은 stateful 자식 위젯에 유지된 상태가 뷰포트의 동일한 숫자 위치에 있는 항목이 아니라 동일한 의미론적 항목에 연결된 상태로 유지된다는 것을 의미합니다. 자세한 내용은 키 API를 참조하세요.

Global keys

전역 키를 사용하여 자식 위젯을 고유하게 식별합니다. 전역 키는 형제 간에만 고유해야 하는 로컬 키와 달리 전체 위젯 계층 구조에서 전역적으로 고유해야 합니다. 전역적으로 고유하기 때문에 전역 키를 사용하여 위젯과 연결된 상태를 검색할 수 있습니다. 자세한 내용은 GlobalKey API를 참조하십시오.