본문 바로가기
프로그래밍/Flutter

Flutter 커스텀 위젯 (Stateless, Stateful)

by drogrammer 2021. 1. 3.
반응형

기본적으로 플러터 UI 는 ListView, Column, Text 등 기본 위젯을 이용해서 구성한다. 하지만, 어플리케이션의 구현이 복잡해 질수록 재사용 가능한 커스텀 위젯의 개발이 불가피하다. 이번 포스트에서는 어떻게 커스텀 위젯을 만들 수 있는지 간략히 설명하도록 하겠다.

1. 배경

플러터는 기본적으로 렌더링을 실제로 수행하는 RenderObjectWidget 류의 위젯도 제공하지만, 여러 위젯을 조합해서 새로운 위젯을 만들 수 있는 방식도 제공을 하고 있다. 조합 가능한 위젯의 대표적인 것이 Stateless, Stateful 위젯이며, 이 컨셉을 앱 내에서 커스텀 위젯 개발 시에 활용 가능하다.

2. Stateless 위젯

Stateless 위젯은 한마디로 상태 변경이 불가능한 (immutable) 위젯이다. 따라서,

  • 클래스 내부 변수는 모두 final 변수고,
  • build() 함수를 구현 해서 위젯을 조합한다.

아래 예제는, 'hello' 라는 텍스트 화면 가운데 출력하는 위젯이다.

class Sample extends StatelessWidget {
  final String txt = 'hello';

  @override
  Widget build(BuildContext context) {
    return Center(child: Text(txt));
  }
}

 

3. Stateful 위젯

반면, Stateful 위젯은 상태 변경이 가능한 (mutable) 위젯이다. 약간 복잡하지만 핵심은,

  • Stateful 위젯 클래스 내에서 State 클래스를 생성해야 한다. (createState() 오버라이드)
  • State 클래스 내에서 initState(), dispose() 등 오버라이드 함수를 활용하여 상태를 관리하고, 
  • State 클래스 내에서 setState() 함수 호출을 통해 변경된 내용을 UI에 반영한다. (내부적으로 build 호출됨)
  • State 클래스 내에서 build() 함수를 구현하여 위젯을 조합한다.

State 클래스의 주요 함수를 조금만 더 설명하자면,

함수 이름 호출 되는 시점
initState()  위젯이 최초 생성 될 때 한번 불린다. 
dispose()  위젯이 제거 될 때 불린다.
build()  커스텀 위젯을 조합하여 return 하는 함수로 아래 두 상황에서 호출된다.
 - initState() 호출 후 
 - setState() 호출 후

 

어렵다.. 예제를 보자..

아래 예제는, 10초 마다 'hello' 와 'bye' 라는 글자를 번갈아 가면서 화면 중앙에 보여주는 위젯이다. _SampleState 클래스 내부를 간략히 설명하면,

  • initState() 에서 10초 짜리 주기적인 타이머를 초기화 했고, 만기시 마다 txt 라는 변수의 값을 'hello' 혹은 'bye'로 변경후 setState() 를 호출한다.
  • dispose() 에서는 타이머를 취소 시켰다.
  • build() 에서 현재 설정되어 있는 txt 변수의 텍스트를 화면 중앙에 보여준다.

결국, initState 에서 등록한 타이머가 만기 될때 마다 txt 변수를 변경하고 setState()를 호출하게 되고, setState() 호출에 의해서 build() 가 불리게 되어 UI가 업데이트 된다.

import 'dart:async';

import 'package:flutter/material.dart';

class Sample extends StatefulWidget {
  @override
  _SampleState createState() => _SampleState();
}

class _SampleState extends State<Sample> {
  String txt = 'hello';
  Timer timer;

  @override
  void initState() {
    timer = Timer.periodic(Duration(seconds: 10), (timer) {
      setState(() {
        if (txt == 'hello')
          txt = 'bye';
        else
          txt = 'hello';
      });
    });
    super.initState();
  }

  @override
  void dispose() {
    timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(child: Text(txt));
  }
}

 

반응형

댓글