해당 과정은 https://flutter-ko.dev/docs/development/ui/interactive에서 만나볼 수 있습니다.
Managing state
What's the point? |
Who manages the stateful widget’s state? The widget itself? The parent widget? Both? Another object? The answer is… it depends. There are several valid ways to make your widget interactive. You, as the widget designer, make the decision based on how you expect your widget to be used. Here are the most common ways to manage state:
Stateful 위젯의 상태는 누가 관리합니까? 위젯 자체? 부모 위젯? 모두? 또 다른 객체? 대답은 ... 상황에 따라 다릅니다. 위젯을 대화 형으로 만드는 몇 가지 유효한 방법이 있습니다. 위젯 디자이너는 위젯 사용 방식에 따라 결정을 내립니다. 상태를 관리하는 가장 일반적인 방법은 다음과 같습니다.
The widget manages its own state - 위젯은 자체 상태를 관리합니다.
The parent manages the widget’s state - 부모는 위젯의 상태를 관리합니다
A mix-and-match approach - 두가지 방식 섞어서
How do you decide which approach to use? The following principles should help you decide:
If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget.
사용할 접근 방식을 어떻게 결정합니까? 다음 원칙은 결정하는 데 도움이됩니다:
문제의 상태가 사용자 데이터이라면, 예로 들어 : 확인란의 선택, 선택취소 모드 또는 슬라이더 위치인 경우 상태는 상위 위젯에서 가장 잘 관리됩니다.
If the state in question is aesthetic, for example an animation, then the state is best managed by the widget itself. If in doubt, start by managing state in the parent widget.
문제의 상태가 미적이면, 예를 들어 애니메이션인 경우 상태는 위젯 자체에서 가장 잘 관리됩니다. 확실하지 않은 경우 상위 위젯에서 상태를 관리하여 시작하십시오.
We’ll give examples of the different ways of managing state by creating three simple examples: TapboxA, TapboxB, and TapboxC. The examples all work similarly—each creates a container that, when tapped, toggles between a green or grey box. The _active boolean determines the color: green for active or grey for inactive.
TapboxA, TapboxB 및 TapboxC의 세 가지 간단한 예제를 만들어 상태를 관리하는 다양한 방법에 대한 예제를 제공해 드리겠습니다. 모든 예제는 비슷하게 작동합니다. 각각 탭하면 녹색 또는 회색 상자 사이를 전환하는 컨테이너가 생성됩니다. _active비활성 활성 녹색 또는 회색 : 부울 색상을 결정합니다.
These examples use GestureDetector to capture activity on the Container.
이 예에서는 GestureDetector 를 사용하여 컨테이너의 활동을 캡처합니다.
The widget manages its own state
Sometimes it makes the most sense for the widget to manage its state internally. For example, ListView automatically scrolls when its content exceeds the render box. Most developers using ListView don’t want to manage ListView’s scrolling behavior, so ListView itself manages its scroll offset.
때로는 위젯이 내부적으로 상태를 관리하는 것이 가장 합리적입니다. 예를 들어 ListView 는 콘텐츠가 렌더링 상자를 초과하면 자동으로 스크롤됩니다. ListView를 사용하는 대부분의 개발자는 ListView의 스크롤 동작을 관리하지 않기 때문에 ListView 자체가 scroll offset을 관리합니다.
_TapboxAState클래스 :
TapboxA의 상태를 관리합니다.
상자의 현재 색상을 결정하는 boolean 타입의_active변수를 정의합니다
_handleTap()은 상자가 탭될 때 _active를 업데이트하고 setState() 함수를 호출하여 UI를 업데이트하는 기능을 정의합니다.
위젯에 대한 모든 대화식 동작을 구현합니다.
// TapboxA manages its own state.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key);
@override
_TapboxAState createState() => _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
_active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: TapboxA(),
),
),
);
}
}
The parent widget manages the widget’s state
Often it makes the most sense for the parent widget to manage the state and tell its child widget when to update. For example, IconButton allows you to treat an icon as a tappable button. IconButton is a stateless widget because we decided that the parent widget needs to know whether the button has been tapped, so it can take appropriate action.
In the following example, TapboxB exports its state to its parent through a callback. Because TapboxB doesn’t manage any state, it subclasses StatelessWidget.
종종 상위 위젯이 상태를 관리하고 업데이트 시기를 하위 위젯에 알리는 것이 가장 합리적입니다. 예를 들어 IconButton을 사용하면 아이콘을 탭 가능한 버튼으로 취급 할 수 있습니다. IconButton은 stateless 저장 위젯입니다. 부모 위젯이 버튼이 탭되었는지 여부를 알아야 적절한 조치를 취할 수 있기 때문입니다.
다음 예에서 TapboxB는 콜백을 통해 상태를 상위로 내 보냅니다. TapboxB는 어떤 상태도 관리하지 않기 때문에 StatelessWidget을 하위 클래스로 만듭니다.
The TapboxB class:
Extends StatelessWidget because all state is handled by its parent.
When a tap is detected, it notifies the parent.
TapboxB 클래스 :
모든 상태가 부모에 의해 처리되기 때문에 StatelessWidget을 확장합니다.
탭이 감지되면 부모에게 알립니다.
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
myapp의 경우 TapboxA()에서 ParentWidget()로 변경해주었다. 탭이 감지가 되면 부모 개체에서 자식 개체로 데이터가 이동하여 interaction을 진행하는 것을 볼 수 있었다.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key);
@override
_TapboxAState createState() => _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
또한 TapboxA와 TapboxB의 차이점을 간단히 살펴보자면 코드의 길이가 길어진 것도 있겠지만 A에서 하던 작업을 여러 과정에 걸쳐 나누는 것으로 보인다. 자세한 진행 과정은 정확히 설명하기에는 아직 부족하지만 아마도 js가 실행될 때의 기준과 비슷해 보인다.
A mix-and-match approach
For some widgets, a mix-and-match approach makes the most sense. In this scenario, the stateful widget manages some of the state, and the parent widget manages other aspects of the state.
일부 위젯의 경우 a mix-and-match이 가장 적합합니다. 이 시나리오에서 stateful 위젯은 일부 상태를 관리하고 부모 객체 위젯은 상태의 다른 측면을 관리합니다.
In the TapboxC example, on tap down, a dark green border appears around the box. On tap up, the border disappears and the box’s color changes. TapboxC exports its _active state to its parent but manages its _highlight state internally. This example has two State objects, _ParentWidgetState and _TapboxCState.
이 TapboxC 예제에서 아래로 탭하면 상자 주위에 진한 녹색 테두리가 나타납니다. 탭하면 테두리가 사라지고 상자의 색상이 변경됩니다. TapboxC는 부모 개체로 _active상태를 이동시키지만 _highlight 내부 상태를 관리합니다.. 이 예제에는 두 개의 State 객체 _ParentWidgetState와 _TapboxCState를 가집니다.
상자를 탭할 때 호출되는 method인 _handleTapboxChanged()를 실행합니다.
탭이 발생하고 _active 상태가 변경될 때 setState()를 호출하여 UI를 업데이트합니다 .
The _TapboxCState object:
Manages the _highlight state.
_TapboxCState개체 :
_highlight 상태를 관리합니다 .
The GestureDetector listens to all tap events. As the user taps down, it adds the highlight (implemented as a dark green border). As the user releases the tap, it removes the highlight.
Calls setState() to update the UI on tap down, tap up, or tap cancel, and the _highlight state changes.
On a tap event, passes that state change to the parent widget to take appropriate action using the widget property.
GestureDetector는 모든 탭 이벤트를 수신합니다. 사용자가 아래로 탭하면 강조 표시가 추가됩니다 (진한 녹색 테두리로 구현됨). 사용자가 탭을 놓으면 강조 표시가 제거됩니다.
탭 다운, 탭 업, 탭 취소 또는 _highlight 상태 변경시 setState()호출하여 UI를 업데이트합니다.
탭 이벤트에서 해당 상태 변경을 상위 위젯에 전달하여 위젯 속성을 사용하여 적절한 조치를 취합니다.
//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
_TapboxCState createState() => _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: Container(
child: Center(
child: Text(widget.active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white)),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color:
widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
해당 상호작용은 다음과 같이 출력이 되었다. TapboxC는 TapboxB에서 위젯 내부 접근이 이루어진 것으로 보입니다. TapboxB에서는 전체 박스의 색깔을 바꾸는 상호작용에 불과했다면, 다음과 같이 직접적인 State 변환을 해주었습니다. 확실히 두가지를 mix하는 방식이 훨씬 더 유동적으로 표현이 가능하며, 가독성에도 뛰어나 보입니다.
An alternate implementation might have exported the highlight state to the parent while keeping the active state internal, but if you asked someone to use that tap box, they’d probably complain that it doesn’t make much sense. The developer cares whether the box is active. The developer probably doesn’t care how the highlighting is managed, and prefers that the tap box handles those details.
대체 구현은 활성 상태를 내부로 유지하면서 강조 상태를 부모에게 내보낼 수 있지만 누군가에게 해당 탭 상자를 사용하도록 요청하면 그다지 말이되지 않는다고 불평할 것입니다. 개발자는 상자가 활성 상태인지 여부를 관리합니다. 개발자는 강조 표시가 관리되는 방식에 관심이 없으며 탭 상자가 이러한 세부 정보를 처리하는 것을 선호합니다.
Other interactive widgets
Flutter offers a variety of buttons and similar interactive widgets. Most of these widgets implement the Material Design guidelines, which define a set of components with an opinionated UI.
If you prefer, you can use GestureDetector to build interactivity into any custom widget. You can find examples of GestureDetector in Managing state, and in the Flutter Gallery.
Flutter는 다양한 버튼과 유사한 대화형 위젯을 제공합니다. 이러한 위젯의 대부분은 독자적인 UI로 구성 요소 세트를 정의하는 Material Design guidelines을 구현합니다 .
원하는 경우 GestureDetector를 사용 하여 사용자 정의 위젯에 상호 작용을 구축 할 수 있습니다 . 상태 관리 및 Flutter 갤러리 에서 GestureDetector의 예를 찾을 수 있습니다 .
해당 과정은 GitHub를 통해 자세히 볼 수 있습니다.
전체 코드입니다.
문의 및 정정은 언제나 환영입니다.