widgets是一套响应式框架, 灵感来自React. 使用Widgets构建UI. Widgets描述view长什么样, 当前结构和状态. 当widget的状态改变, flutter框架会比较之前的描述信息, 以最小的改变, 重新构建widget
最简单的Flutter app, runApp()中传入一个widget
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
runApp()函数需要传入一个widget, 作为widget树的根. 上面例子中widget树有两个widget, Center
widget和它的子widgetText
. flutter框架强制根widget满屏.
app开发中, 通过创建一个新的widget继承自StatelessWidget(无状态) 或 StatefulWidget(有状态). 取决于是不是需要管理widget的状态. widget主要工作是实现build函数, 以此为基础来描述其他更低级别的widget. flutter框架依次构建那些widget直到最底部的RenderObject, 用来计算并描述widget的几何形状.
Text: 文本
Row, Column: 水平 (Row行) 和 垂直 (Column列) .
Stack: 非线性布局, Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置
Container: 创建矩形框, 可以由BoxDecoration进行装饰, 如背景, 边框和阴影. Container有边距(margins)、填充(padding)和约束(constraints)来确定尺寸. 可以通过矩阵将Container转成三维.
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Widget子类中的字段都必须标记为final
// Fields in a Widget subclass are always marked "final".
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
// 行, 水平线性布局
// Row is a horizontal, linear layout.
child: Row(
// <Widget> is the type of items in the list.
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null可以禁用按钮 null disables the button
),
// Expanded 填充余下的可用空间
// expands its child to fill the available space.
Expanded(
child: title,
),
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Material 一张纸
// Material is a conceptual piece of paper on which the UI appears.
return Material(
// Column 垂直的线性布局
// Column is a vertical, linear layout.
child: Column(
children: <Widget>[
MyAppBar(
title: Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
Expanded(
child: Center(
child: Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(
title: 'My app', // used by the OS task switcher
home: MyScaffold(),
));
}
在pubspec.yaml文件中, 将uses-material-design: true, 来使用Material icons.
name: my_app
flutter:
uses-material-design: true
许多widget需要放在MaterialApp中才能合理的显示, 继承主题数据. 因此在runApp中使用MaterialApp.
Flutter提供一些遵循Material设计的widgets. 一个Material app, 以MaterialApp widget开始, 构建一些基础的widget, 包括Navigator, 用于管理一堆以字符串为标识的widgets(也称routes). 使用MaterialApp widget不是必要的, 但是一个不错的练习.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter Tutorial',
home: TutorialHome(),
));
}
class TutorialHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: Text('Example title'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
// body is the majority of the screen.
body: Center(
child: Text('Hello, world!'),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
child: Icon(Icons.add),
onPressed: null,
),
);
}
}
material.dart库中的AppBar和Scaffold widgets.
用户交互第一步是监听手势的输入.
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 36.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: Center(
child: Text('Engage'),
),
),
);
}
}
GestureDetector用于监听用户的手势, 没有任何视觉效果. 当用户点击Container, GestureDetector调用onTap回调. GestureDetector可以监听很多输入手势, 如点击, 拖动, 拉伸.
许多widget通过GestureDetector提供一个可选的回调. 如 IconButton, RaisedButton, and FloatingActionButton widgets 的 onPressed 回调.
StatelessWidget(无状态的widget), 接收父widget的参数, 并存储在final成员变量中. 当一个widget被要求构建时,它使用这些存储的值作为参数来构建widget
StatefulWidget(有状态的widget), 特殊的widget,它知道如何生成State对象来保持状态。
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".
// 状态的结构
// 保留父widget提供的数值, 来构建widget
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
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 we changed _counter without calling
// setState(), then the build method would not be called again,
// and so nothing would appear to happen.
// setState高度Flutter框架, 有状态改变
// widget会重新运行下面的build方法, 来展示更新后的值
// 如果只改变_counter, 没有调用setState(), 则build放发不会被调用, 不会显示新的状态
_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.
// 每次setState调用时, 会重新运行该方法, 如调用上面的_increment方法
// Flutter框架对重新运行build方法做了最佳优化, 因此在每次需要更新的时候都可以直接rebuild
return Row(
children: <Widget>[
RaisedButton(
onPressed: _increment,
child: Text('Increment'),
),
Text('Count: $_counter'),
],
);
}
}
StatefulWidget和State是独立的对象, 有不同的生命周期.
- StatefulWidget是临时的对象, 用来显示应用程序的当前状态
- State对象在多次调用build()中依旧保持不变,使他们能够保存信息
上面的例子接受用户点击,并在点击时使_counter自增,然后直接在其build方法中使用_counter值。在更复杂的应用程序中,widget结构层次的不同部分可能有不同的职责; 例如,一个widget可能呈现一个复杂的用户界面,其目标是收集特定信息(如日期或位置),而另一个widget可能会使用该信息来更改整体的显示。
在Flutter中,状态改变的通知, 通过回调的形式“向上”流动,而当前状态“向下”流动到stateless widget来做展示.重定向这一流程的共同父元素是State。
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),
]);
}
}
通过两个无状态widget分离 展示(CounterDisplay) 和 计算(CounterIncrementor) 的逻辑. 尽管最终效果与前一个示例相同,但责任分离允许将复杂性逻辑封装在各个widget中,同时保持父项的简单性。
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)),
);
}
}
ShoppingListItem widget后跟着一些常见的无状态stateless widget. 通过构造函数, 将传入的值保存到final成员变量中. 这些变量在build函数中用到, 如inCart布尔值, 控制两种显示样式的切换.
当用户点击 list item, widget不会直接修改inCart的值. widget调用从父widget传过来的onCartChanged函数. 这种模式让你可以将状态保存在更高层级, 允许状态保持更长的时间. 极端情况下, widget的状态传到runApp(), 在app的生命周期中都会保存.
但父widget收到onCartChanged回调之后, 父widget会更新它的内部状态, 触发rebuild, 并创建一个新的ShoppingListItem, 传入一个新的inCart值.
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.
// widget第一次出现在给定位置时, 框架会调用createState.
// 如果父widget重新build, 并且使用了同一个类型的widget(key值一样)
// 框架不会创建一个新的对象, 而是会重用同一个State对象
@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, we 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.
// 当用户改变了cart中的内容, 需要改变_shoppingCart
// setState中会触发rebuild. framework会触发build方法更新UI
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'),
],
),
));
}
ShoppingList类继承自StatefulWidget, 意味着该widget中保存着可变的状态state. 当ShoppingList widget 第一次插入widget树中, 框架会调用createState函数来创建一个新的_ShoppingListState实例来关联树的位置.(通常命名State子类时带一个下划线,表示其是私有的). 当这个widget的父widget重新build, 父widget会创建一个新的ShoppingList实例, 但是框架会重用存在树上的_ShoppingListState实例, 而不是调用createState重新创建.
_ShoppingListState可以使用它的widget属性来访问当前的ShoppingList. 如果父级widget重建一个新的ShoppingList. _ShoppingListState的widget值也会重建. 如果希望监听widget属性的改变, 可以重写didUpdateWidget函数, 该函数传了一个oldWidget值让你与当前widget进行比较.
处理onCartChanged回调时, _ShoppingListState的内部状态随着_shoppingCart中的产品增加/减少改变. 将这些改变写在setState函数中来通知框架. 调用setState标记这些widget为脏widget, 并且计划在下次应用程序需要更新屏幕时重建. 当修改内部状态时, 忘记调用setState, 框架不知道你的widget已经脏了, 可能就不会调用widget的build函数, 意味着用户页面不会反应更新后的状态.
通过以这种方式管理状态,不需要分别写创建和更新子widget的代码。相反,只需实现可以处理这两种情况的build函数即可。
当StatefulWidget调用createState后, 框架将新的state对象插入树种, 然后调用state对象的initState. 如果想处理只需执行一次的事情, 可以在State的子类中重写initState方法. 例如, 配置动画或者订阅平台服务. initState实现中需要调用super.initState.
当一个状态对象不再需要时,框架调用状态对象的dispose。 您可以覆盖该dispose方法来执行清理工作。例如,您可以覆盖dispose取消定时器或取消订阅platform services。 dispose典型的实现是直接调用super.dispose。
可以使用key来控制框架将在widget重建时与哪些其他widget匹配。默认情况下,框架根据它们的runtimeType和它们的显示顺序来匹配, 有了key,框架要求两个widget具有相同的key和runtimeType。
Key在构建相同类型widget的多个实例时很有用。例如,ShoppingList构建足够的ShoppingListItem实例以填充其可见区域:
-
如果没有key,当前构建中的第一个条目将始终与前一个构建中的第一个条目同步,即使在语义上,列表中的第一个条目如果滚动出屏幕,那么它将不会再在窗口中可见。
-
通过给列表中的每个条目分配为“语义” key,无限列表可以更高效,因为框架将同步条目与匹配的语义key并因此具有相似(或相同)的可视外观。 此外,语义上同步条目意味着在有状态子widget中,保留的状态将附加到相同的语义条目上,而不是附加到相同数字位置上的条目。
您可以使用全局key来唯一标识子widget。全局key在整个widget层次结构中必须是全局唯一的,这与局部key不同,后者只需要在同级中唯一。由于它们是全局唯一的,因此可以使用全局key来检索与widget关联的状态。