学习要点
- 如果响应点击
- 如果自定义widget
- 区分无状态(stateless)和有状态(stateful)的widget
widget分为有状态(stateful)和无状态(stateless)两种. 如果一个部件与用户交互时可以改变, 它就是有状态的部件.
无状态的部件永远不会改变. 如 Icon, IconButton, Text. 继承自StatelessWidget
有状态的部件是动态的. 当用户触发交互或接收到数据时, 它的显示会进行改变. 如Checkbox, Radio, Slider, InkWell, Form, and TextField. 继承自StatefulWidget.
widget的状态存在一个State对象中, 根据显示来区分不同的状态. state对象中包含可变的值, 如slider当前的值, 或者一个checkbox是否勾选. 当widget的状态改变, state对象会调用setState()通知框架重新绘制widget.
- stateful widget由两个类实现: StatefulWidget的子类, 和State的子类
- state类中包含widget的可变状态和build方法
- 当widget的状态发生变化, state对象会调用setState(), 通知框架重新绘制该widget
widget的状态有多种管理方式.
自定义stateful widget需要创建两个类
- 定义一个widget, 继承自StatefulWidget
- 定义一个State, 包含了这个widget的状态, 并定义build()方法创建这个widget
- 多种方式可以管理状态
- 选择哪种方式进行状态管理
- 如果有疑惑, 先由父widget进行管理
由谁来管理widget的状态, 父部件? 自己? 一起? 或者其他部件? 看情况决定. 有多种方式可以进行widget的交互. 作为widget的设计者, 依据widget的使用方式而定. 下面几种最通用的管理状态的方式:
- widget管理自己的状态. The widget manages its own state
- 父部件管理该widget的状态. The parent manages the widget’s state
- 混合匹配方式. A mix-and-match approach
如果决定使用哪种方式? 可以根据以下规则:
- 如果state对象关心的是用户数据, 如checkbox的勾选状态, slider的位置, 此时最好由父widget管理该状态
- 如果state对象关心的是式样, 如动画, 此时最好由自己管理
如果有疑惑, 最开始可以由父widget进行管理.
有时候, 由widget自己管理内部的状态最合适. 如当ListView的内容超过渲染盒子后, 它自动可滚动. 许多开发者不想要管理ListView的滚动行为, 因此ListView自行管理它滚动的偏移量.
// 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(),
),
),
);
}
}
有时候由父widget告诉子widget何时需要更新比较合适. 如 IconButton允许你将一个图标当成一个可点击的按钮. IconButton是stateless的, 因为父widget需要知道按钮合适被点击来做合适的操作.
// 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],
),
),
);
}
}
使用 @required 注释你代码所依赖的参数. 使用它需要到foundation library
import 'package:flutter/foundation.dart';
有些widget, 使用混合搭配的方式. 该案例中, stateful widget管理一些状态, 父widget管理其他方面的状态.
TapboxC 案例. 按下的时候有一个深绿色边框. 按回的时候, 边框消失, 盒子的颜色改变.
_ParentWidgetState
- 管理_active状态
- 实现_handleTapboxChanged(), 在盒子被点击的时候调用
- 当触发点击事件, _active状态改变时, 调用setState()更新UI
_TapboxCState
- 管理_highlight状态
- GestureDetector监听所有点击事件. 当用户往下点击时, 会增加高亮效果(深绿色边框). 当用户释放点击, 移除高亮效果.
- 当用户向下点击和释放时, 更改_highlight的状态, 调用setState()来更新UI
- 在点击事件. 通过widget属性, 将状态的改变传给父widget来做合适的操作.
//---------------------------- 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,
),
),
);
}
}
你也可以将高亮状态交给父部件管理, 将有效状态放在内部, 但是这样做没有意义. 开发者关心的是盒子的有效状态, 不关心高亮.
Flutter提供多种按钮和类似交互的部件. 大部分widgets在 Material Design guidelines中实现, 定义一套UI.
Flutter 也提供iOS风格的部件
Cupertino
如果需要交互, 可以使用其中一个预设好的部件更加方便
- Form
- FormField
- Checkbox
- DropdownButton
- FlatButton
- FloatingActionButton
- IconButton
- Radio
- RaisedButton
- Slider
- Switch
- TextField