Open 1more opened 5 years ago
StatefulWidget
은 Widget
, State
두 객체로 나뉘어 있다. 아래처럼 서로 용도가 다르기 때문.
Widget
: 현재 상태의 화면을 구성하는 역할을 하는 일회용 객체State
: 여러번의 build()
함수 호출 동안, 상태값을 저장하는 역할을 하는 객체You might wonder why StatefulWidget and State are separate objects. In Flutter, these two types of objects have different life cycles. Widgets are temporary objects, used to construct a presentation of the application in its current state. State objects, on the other hand, are persistent between calls to build(), allowing them to remember information.
state를 변경할 때는 setState()
를 이용
build()
가 다시 호출되지 않을 수 있음state의 lifecycle은 링크를 참조
오 현욱이 벌써 했구먼 👍 나도 flutter.dev 의 user interface 문서 공부한거 여기에 같이 올린당 ㅎㅎ
import 'package:flutter/material.dart';
void main() { runApp( Center( child: Text( 'Hello, world!', textDirection: TextDirection.ltr, ), ), ); }
- 미니멀한 플러터 앱은 위젯과 함께 `runApp()` 함수를 호출한다.
- 그러면 `runApp()` 이 위젯트리의 루트가 된다.
- 위젯이 상태를 관리하는지의 여부에 따라 `StatelessWidget` 과 `StatefulWidget` 이 있다.
- 위젯의 주된 일은 빌드 함수를 구현하는 것
- Basic widgets
- `Text`: style 지정이 가능하다
- `Row`, `column`: horizontal (row), vertical (column) 으로 좀 헷갈린다. 옆으로 추가해서 하나의 row 가 되면 row 고, 위아래로 추가해서 하나의 column 이 되면 column 인 것으로 이해하면 좋다.
- `Stack`: 가로나 세로로 쌓이는 것이 아니라 앞뒤로, background 에서 부터 foreground 로 쌓이는 것을 말한다. 이걸 이용해서 각 위젯의 포지션을 상대적으로 결정할 수 있다.
- `Container`: 네모난 모양의 요소를 만들 때 사용한다. BoxDecoration 및 3D 트랜스포메이션이 가능하다.
- 예제 코드로 부터 배운 것:
- flutter 에서 height 등 단위가 없는 수치들은 logical pixel 이라고 함. (= device-independent pixel)
- Material 은 UI 를 나타내는 개념적인 도화지 정도로 이해할 수 있다. 여기에 이것저것 붙이면 그게 한 화면이 되는 것이다.
- Colors.blue[500]: 뒤에 붙은 숫자는 색의 강도? 정도로 이해할 수 있다. 100 ~ 900 (pale to darker) 까지의 수로 이루어져있다. 참고로 accect 는 {100, 200, 400, 700} 만 있다고 한다.
- 참고: https://api.flutter.dev/flutter/material/Colors-class.html
- 참고: pubspec.yaml 에 `users-material-design: true` 로 해두면 플러터의 material icon 을 사용할 수 있다. 그리고 이들은 MaterialApp 안에 있어야 하므로 아래 처럼 작성한다.
```dart
void main() {
runApp(MaterialApp(
title: 'My app', // used by the OS task switcher
home: MyScaffold(),
));
}
GestureDetector
로 tap, drag, scale 을 포함한 제스처 입력에 대한 처리를 정의할 수 있다.GestureDetector
를 사용한다.
StatefulWidget
과 StatelessWidget
이 구분되어 있는 이유는 다른 life cycle 을 가지기 때문이다.
StatefulWidget
은 State 객체를 생성할 수 있는 특별한 위젯이다.아래 예제 코드로 부터 배운 것:
_CounterState
클래스의 build 는 모든 setState 가 호출될 때 마다 실행된다.
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".
@override _CounterState createState() => _CounterState(); }
class _CounterState extends State
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 change
// instances of widgets.
return Row(
children:
아래 예시로, State 를 이용해서 displaying 을 담당하는 위젯과 changing 을 담당하는 위젯을 분리하는 예시를 통해 각 위젯의 복잡도를 캡슐화하고 위젯의 부모로 간단하게 운영할 수 있는 것을 보여준다.
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: onPressed,
child: Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(children: <Widget>[
CounterIncrementor(onPressed: _increment),
CounterDisplay(count: _counter),
]);
}
}
inCart
변수 값에 따라 겉 모습을 결정하도록 한다. (incart 면 회색에, 취소선 긋고, 아니면 파란색 theme 을 가짐)inCart
값을 직접 바꾸지 않고, onCartChanged
함수를 호출해 부모 위젯을 호출하도록 한다.onCartChanged
콜백을 받으면 부모 위젯은 내부 상태를 업데이트 하고 새로운 ShoppingListItem
인스턴스를 생성하도록 트리거 한다. 부모 위젯이 자식 인스턴스를 다시 빌드하지만 f/w 이 이전에 빌드 했던 위젯과 새로 빌드하는 위젯을 비교해 달라진 부분만 새로 적용하기 때문에 이 작업의 비용은 매우 낮다.
class Product {
const Product({this.name});
final String name;
}
typedef void CartChangedCallback(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({Product product, this.inCart, this.onCartChanged})
: product = product,
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 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)),
);
}
}
ShoppingList
클래스는 stateful widget 이다. 즉, 이 위젯은 가변 상태를 저장하고 있다.ShoppingList
위젯을 처음 트리에 추가하면 f/w 는 트리에 위치한 _ShoppingListState
의 새로운 인스턴스를 생성하기 위해 createState() 를 호출한다.ShoppingList
의 새로운 인스턴스를 생성한다. 하지만, f/w 는 트리에 이미 있는 _ShoppingListState
인스턴스를 재사용한다. ShoppingList
의 현재 속성에 접근하기 위해서 _ShoppingListState
는 이것의 위젯 속성을 사용한다. ShoppingList
를 리빌드하고 생성하면 _ShoppingListState
는 새로운 위젯 값으로 리빌드 한다. 만약 위젯의 속성이 바뀌었을 때 알아채고 싶다면 이전 위젯을 전달해 현재 위젯과의 차이를 알려주는 didUpdateWidget()
함수를 오버라이드하면 된다.onCartChanged
콜백을 핸들링할 때 _ShoppingListState
는 장바주니에 상품을 추가하거나 삭제하는 것으로 내부 상태를 변경한다. 이 부분은 f/w 에 내부 상태의 변경을 알리기 위해 setState
로 감싼다. setState
를 호출하는 것은 이 위젯이 이미 변경되어 dirty 한 상태이며 스크린을 업데이트하기 위해 리빌딩을 스케줄링하는 것이다. 만약 setState
호출을 사용하지 않으면 f/w 는 이 위젯이 dirty 상태임을 모르며 build() 함수를 호출하지도 않을 것이다. 이런 방식으로 상태를 운영함으로써, 자식위젯의 생성과 업데이트 코드를 분리하지 않아도 된다. 단지 두 상황을 모두 핸들 할 수 있는 빌드 함수를 구현하기만 하면 된다.
class ShoppingList extends StatefulWidget {
ShoppingList({Key key, this.products}) : 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> {
Set<Product> _shoppingCart = Set<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: Text('Shopping List'),
),
body: ListView(
padding: 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(MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: <Product>[
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
정리는 여기까지 ㅠ ㅋㅋㅋ
화면에 ListView만 추가하면 아래 오류가....
났었는데, ListView
에 아래 두 옵션 추가해서 해결함.
scrollDirection: Axis.vertical,
shrinkWrap: true,
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performResize():
flutter: Vertical viewport was given unbounded height.
flutter: Viewports expand in the scrolling direction to fill their container.In this case, a vertical
flutter: viewport was given an unlimited amount of vertical space in which to expand. This situation
flutter: typically happens when a scrollable widget is nested inside another scrollable widget.
flutter: If this widget is always nested in a scrollable widget there is no need to use a viewport because
flutter: there will always be enough vertical space for the children. In this case, consider using a Column
flutter: instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
flutter: the height of the viewport to the sum of the heights of its children.
flutter:
flutter: User-created ancestor of the error-causing widget was:
flutter: ListView file:///Users/hyeonwook/workspace/flutter_app_sample/lib/main.dart:198:23
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 RenderViewport.performResize.<anonymous closure> (package:flutter/src/rendering/viewport.dart:1147:15)
flutter: #1 RenderViewport.performResize (package:flutter/src/rendering/viewport.dart:1200:6)
flutter: #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1624:9)
flutter: #3 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #4 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #5 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #6 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #7 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #8 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #9 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #10 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #11 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #12 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #13 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #14 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #15 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:744:15)
flutter: #16 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #17 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #18 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #19 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:806:17)
flutter: #20 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #21 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #22 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #23 MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:142:11)
flutter: #24 _ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:443:7)
flutter: #25 MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:212:7)
flutter: #26 RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:356:14)
flutter: #27 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #28 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #29 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #30 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #31 _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1214:11)
flutter: #32 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #33 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #34 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #35 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #36 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #37 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #38 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #39 RenderStack.performLayout (package:flutter/src/rendering/stack.dart:510:15)
flutter: #40 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #41 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #42 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #43 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #44 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #45 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #46 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #47 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #48 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #49 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #50 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #51 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #52 RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3076:13)
flutter: #53 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #54 RenderStack.performLayout (package:flutter/src/rendering/stack.dart:510:15)
flutter: #55 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #56 __RenderTheatre&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #57 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #58 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #59 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #60 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #61 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #62 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #63 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #64 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:105:13)
flutter: #65 RenderObject.layout (package:flutter/src/rendering/object.dart:1639:7)
flutter: #66 RenderView.performLayout (package:flutter/src/rendering/view.dart:151:13)
flutter: #67 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1516:7)
flutter: #68 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:783:18)
flutter: #69 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:346:19)
flutter: #70 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:702:13)
flutter: #71 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:285:5)
flutter: #72 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1033:15)
flutter: #73 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:975:9)
flutter: #74 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:784:7)
flutter: #83 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19)
flutter: #84 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5)
flutter: #85 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
flutter: (elided 8 frames from package dart:async and package dart:async-patch)
flutter:
flutter: The following RenderObject was being processed when the exception was fired: RenderViewport#56e3f NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:
flutter: needs compositing
flutter: creator: Viewport ← IgnorePointer-[GlobalKey#a00dd] ← Semantics ← Listener ← _GestureSemantics ←
flutter: RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#88c9c] ← Listener ← _ScrollableScope
flutter: ← _ScrollSemantics-[GlobalKey#a4548] ← Scrollable ← PrimaryScrollController ← ListView ← ⋯
flutter: parentData: <none> (can use size)
flutter: constraints: BoxConstraints(0.0<=w<=414.0, 0.0<=h<=Infinity)
flutter: size: MISSING
flutter: axisDirection: down
flutter: crossAxisDirection: right
flutter: offset: ScrollPositionWithSingleContext#71af3(offset: 0.0, range: null..null, viewport: null,
flutter: ScrollableState, AlwaysScrollableScrollPhysics -> BouncingScrollPhysics, IdleScrollActivity#aa512,
flutter: ScrollDirection.idle)
flutter: anchor: 0.0
flutter: This RenderObject had the following descendants (showing up to depth 5):
flutter: center child: RenderSliverPadding#cf111 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
flutter: child: RenderSliverList#fac8e NEEDS-LAYOUT NEEDS-PAINT
TODO