일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- textfield
- Button
- Compose
- LifeCycle
- 안드로이드
- appbar
- Coroutines
- 앱바
- TEST
- tabbar
- drift
- data
- ScrollView
- 테스트
- activity
- Navigation
- viewmodel
- 계측
- binding
- Kotlin
- scroll
- intent
- DART
- textview
- CustomScrollView
- livedata
- Flutter
- Dialog
- android
- 앱
- Today
- Total
Study Record
[Flutter] key? 본문
✍ key?
key 는 위젯 트리에서 위젯이 움직일 때마다 현 상태를 보존하는 역할을 한다. 현 스크롤의 위치를 기억하거나 수정 상태를 보존하는 것이 될 수 있다.
😶 언제 Key 를 사용해야 하나?
대부분 key 를 사용할 필요 없지만 어떤 상태를 유지하고 있는 같은 종류의 위젯 컬렉션을 더하거나, 제거하거나 정렬해야 할 때 필요하다.
무슨 말인지 모르겠으니 예를 들어,
랜덤한 색상을 가진 Container() 가 두 개 있고 버튼을 누르면 두 개의 Container 가 서로 위치가 바뀌는 앱이 있다. Container() 는 StatelessWidget 으로 설계되어 있고 titles 라는 리스트에 저장되고 플러팅 버튼을 누르면 titles 의 위젯들 위치만 서로 바뀐다.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: _HomeScreen()));
class _HomeScreen extends StatefulWidget {
_HomeScreen({Key? key}) : super(key: key);
@override
State<_HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<_HomeScreen> {
late List<Widget> titles;
@override
void initState() {
super.initState();
titles = [
StatelessColorWidget(),
StatelessColorWidget(),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child: Row(children: titles)),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.swap_horiz), onPressed: swapTitles)
);
}
swapTitles() {
setState(() {
titles.insert(1, titles.removeAt(0));
});
}
}
class StatelessColorWidget extends StatelessWidget {
final color = randomColor();
StatelessColorWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(color: color, width: 100, height: 100);
}
ColorSwatch<int> randomColor() {
final colorList = [
Colors.red, Colors.greenAccent, Colors.blueAccent, Colors.deepOrangeAccent,
Colors.brown, Colors.purple, Colors.amberAccent
];
return colorList[Random().nextInt(7)];
}
이 앱의 실행과정을 설명하기전, 플러터에는 Widget Tree 와 Element Tree 가 있다. Widget Tree 는 말 그대로 현재 앱의 위젯들의 정보와 상하관계를 나타낸 트리이고 Element Tree 는 Widget Tree 의 위젯을 참조하는 정보(위젯 타입 정보)와 부속 정보(ex. State)를 알고 있는 간단한 플러터의 뼈대와 같은 트리이다.
setState() 로 Container 들의 위치를 바뀌면 Widget Tree 의 container 들은 순서가 바뀌며, 플러터는 Element Tree 가 Widget Tree 와 뼈대가 같은지 확인한다. 비교할 때 Element Tree 에서 참조하는 Widget 의 위젯 타입과 key 정보를 확인한다. 비교해서 뼈대가 같지 않으면 Element Tree 를 업데이트한다.
예시 앱에서는 key 에 대한 정보는 없고 위젯의 오리지널 정보를 담고 있는 Widget Tree 는 StatelessWidget 이며 Color 정보를 자체적으로 가지고 있다. 따라서 Element Tree 에서 참조하는 위젯이 다르다는 것을 깨닫고 업데이트되면서 우리가 보이는 것처럼 순서가 바뀐 것을 확인할 수 있다.
😶 StatefulWidget 이라면?
만약 예시 앱에서 Container 가 StatefulWidget 라면 어떻게 될까? 색상 정보를 어디다 선언하는지에 따라 차이가 있을 수 있다.
1. StatefulWidget 에 color 선언할 경우
...
titles = [
StatefulColorWidget(),
StatefulColorWidget(),
]
...
class StatefulColorWidget extends StatefulWidget {
ColorSwatch<int> color = randomColor();
StatefulColorWidget({Key? key}) : super(key: key);
@override
State<StatefulColorWidget> createState() => _StatefulColorWidgetState();
}
class _StatefulColorWidgetState extends State<StatefulColorWidget> {
@override
Widget build(BuildContext context) => Container(color: widget.color, width: 100, height: 100);
}
이 경우에도 StatelessWidget 에서처럼 key 는 없지만 StatefulWidget 자체에 color 정보가 들어있으니 Widget Tree 와 Element Tree 의 뼈대를 비교할 때 다르다는 것을 발견하고 업데이트되면서 정상 작동할 것이다.
2. State 에 color 정보가 있을 경우
...
titles = [
StatefulColorWidget(),
StatefulColorWidget(),
]
...
class StatefulColorWidget extends StatefulWidget {
StatefulColorWidget({Key? key}) : super(key: key);
@override
State<StatefulColorWidget> createState() => _StatefulColorWidgetState();
}
class _StatefulColorWidgetState extends State<StatefulColorWidget> {
final color = randomColor();
@override
Widget build(BuildContext context) => Container(color: color, width: 100, height: 100);
}
이 경우엔 State 에 Color 정보가 담겨있고 StatefulWidget 자체에는 정보가 없다. key 정보도 없다. 따라서 Container 들의 위치가 서로 바뀌고 Element Tree 와 비교될 때 같은 타입의 위젯이라는 점만 확인된다. 따라서 Element Tree 의 엡데이트를 하지 않는다. 이 경우의 실행 결과도 아무런 작동이 없는 것처럼 보인다.
바로 이런 경우 Key 를 활용하면 정상 작동할 수 있다.
3. key + State 에 color 가 있는 경우
...
titles = [
StatefulColorWidget(key: UniqueKey()),
StatefulColorWidget(key: UniqueKey()),
]
...
class StatefulColorWidget extends StatefulWidget {
StatefulColorWidget({Key? key}) : super(key: key);
@override
State<StatefulColorWidget> createState() => _StatefulColorWidgetState();
}
class _StatefulColorWidgetState extends State<StatefulColorWidget> {
final color = randomColor();
@override
Widget build(BuildContext context) => Container(color: color, width: 100, height: 100);
}
Widget Tree 에 Key 값이 추가되면 Widget Tree 의 Container 위젯에 key 값도 같이 저장된다. 따라서 순서가 바뀌고 Row 의 children 들과 비교할 때, 이전에 참조했던 위젯 타입과 key 정보를 확인해보니 순서가 바뀌었다는 것을 깨닫고 Element Tree 의 위젯들이 업데이트되어 정상적으로 보일 것이다.
Widget Tree 와 Element Tree 를 비교하는 것은 트리 레벨단위로 하게 된다. Row 끼리 한번 비교하고 그의 children 인 Container 들을 비교한다. 이것을 잘못 이해하면 예상치 못한 결과가 나올 수 있다.
😶 Padding 위젯 - Container + key
...
titles = [
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorWidget(key: UniqueKey()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorWidget(key: UniqueKey()),
),
]
...
class StatefulColorWidget extends StatefulWidget {
StatefulColorWidget({Key? key}) : super(key: key);
@override
State<StatefulColorWidget> createState() => _StatefulColorWidgetState();
}
class _StatefulColorWidgetState extends State<StatefulColorWidget> {
final color = randomColor();
@override
Widget build(BuildContext context) => Container(color: color, width: 100, height: 100);
}
실행하면 Container 들의 순서를 바꾸는 것이 아니라 랜덤으로 다시 생성되는 것처럼 보인다. 이러한 결과가 나온 이유는 Widget Tree 와 Element Tree 를 비교하는 과정이 레벨 단위이기 때문이다.
순서를 바꾸면(StatefulWidget key1이 자식인 Padding 과 StatefulWidgt key2 가 자식인 Padding 의 순서를 바꾼다),
1번으로 Row 를 확인하고 Row 의 Children 인 Padding 들을 2번째로 비교한다. 2번까지의 과정에서 위젯들의 정보를 비교했을 때 바뀐 점이 없으므로 아무런 변화도 일어나지 않는다. 3번의 과정에서는 이전에 참조했던 key 값이 달라진 걸 알고 아예 새로운 위젯을 생성한다. 그 과정에서 색상도 바뀔 것이다. 4번도 3번의 과정과 같이 새로운 위젯이 생성되고 색상도 바뀐다. 이것을 방지하려면 Padding 위젯에 key 를 추가하면 된다.
😶 Padding + key
...
titles = [
Padding(
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulColorWidget(),
),
Padding(
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulColorWidget(),
),
]
...
class StatefulColorWidget extends StatefulWidget {
StatefulColorWidget({Key? key}) : super(key: key);
@override
State<StatefulColorWidget> createState() => _StatefulColorWidgetState();
}
class _StatefulColorWidgetState extends State<StatefulColorWidget> {
final color = randomColor();
@override
Widget build(BuildContext context) => Container(color: color, width: 100, height: 100);
}
2번 과정에서 Padding 위젯들이 서로 비교될 때 key 값을 보고 서로 위젯이 바뀌었다는 것을 발견하면 Element Tree 에서 Padding 단위끼리 순서를 바꾸고(padding 의 하위 위젯까지 같이 통째로 이동한다.), 3번, 4번에서 서로 같은 위젯 타입(StatefulColorWidget)인 것만 비교해 업데이트하지 않아도 된다고 판단할 것이다. 따라서, 버튼을 누르면 위젯의 위치만 바뀌는 정상 작동을 하게 된다.
😶 GlobalKey
Globalkey 는 상태를 잃지 않으면서 앱 어디서나 위젯이 상관요소를 바꾸는 것을 가능하게 하고 위젯 트리의 전혀 다른 부분에서 다른 위젯의 정보에 접근할 수 있게 해준다. 예를 들어, 두 개의 다른 스크린에 같은 위젯을 띄우는데 서로 같은 상태를 유지해야 할 경우가 될 수 있다.
'Flutter' 카테고리의 다른 글
[Flutter] 위젯이 차지하는 크기 알아보기 (LayoutBuilder) (0) | 2023.03.13 |
---|---|
[Flutter] 앱 바 사이즈 (0) | 2023.03.12 |
[Flutter] 바텀 시트에서 입력받기(TextField) - 키보드 위로 패딩 조절 (0) | 2023.02.28 |
[Flutter] MediaQuery , MediaQueryData (0) | 2023.02.28 |
[Flutter] version solving failed error - 중복된 라이브러리 (0) | 2023.02.22 |