通过 widgets 构建 UI,描述当前的配置和状态,当状态改变时,框架找出前后的变化,以确定底层 Render Tree 要做的最小更改,在内部变成另一个状态来更新外面的 Widget Tree。
一个最简单的 Flutter 程序就是在入口方法 main 中调用 runApp,这个函数将参数的 Widget 作为整个应用的 Widget Tree 的根 Widget,将这个根 Widget 完全覆盖到屏幕上。
Widget 分两种:StatelessWidget
和 StatefulWidget
,主要工作就是实现 build
方法,用于描述更低级的 Widget。
基础的 Widget
- Text
- Row, Column: flex 布局,类似 Web 的 flexbox,Android 的 FlexboxLayout
- Stack:堆叠,类似 Web 的 absolute,也像 Android 的 RelativeLayout,内部使用 Positioned 并控制其在 Stack 内的上下左右的边距
- Container: 创建一个矩形可见元素,可以通过 BoxDecoration 来做样式,如背景,边框,阴影。可以设置 margin,padding 或者尺寸 size。可以通过矩阵转换变成三维的。
下面是一个示例代码,首先 pubspec.yaml
要配置
flutter:
uses-material-design: true
新创建的工程默认就是这个配置。
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Fields in a Widget subclass are always marked "final".
final Widget title;
@override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: new Row(
// <Widget> is the type of items in the list.
children: <Widget>[
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Material is a conceptual piece of paper on which the UI appears.
return new Material(
// Column is a vertical, linear layout.
child: new Column(
children: <Widget>[
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new Center(
child: new Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
现在还不知道怎么加按钮,将上面的代码放到一个新建的 newroute.dart
文件中,去掉 main 方法,在 main.dart
的 AppBar 上添加一个按钮
new IconButton(icon: new Icon(Icons.add), onPressed: _toOther),
void _toOther() {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
return new MyScaffold();
})
);
}
要使用另一个文件的 MyScaffold 类,需要 import
import 'newroute.dart';
结果成功跳转。
由于语法还不懂,先简单的分析下:
-
MyScaffold
是StatelessWidget
,它的build
方法返回一个Material
,这就是页面显示的内容。 -
Material
里只有一个 child,就是一个Column
,一个垂直的线性布局。包含一个MyAppBar
和Expanded
-
Expanded
是Colume
或Row
或Flex
的 child,意思就占据所有剩下的空间,如果多个 child 都是Expanded
,通过flex
来指定各自的比例,有些类似 Android 的weight
-
MyAppBar
是一个StatelessWidget
,内部是Container
,然后设置它的高度,内边距。child 是Row
,和Column
类似,只是水平的。左右各一个IconButton
手势处理
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print('MyButton was tapped!');
Navigator.of(context).pop(this);
},
child: new Container(
height: 36.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: new Center(
child: new Text('Engage'),
),
),
);
}
}
当用户触摸 GestureDector
的 child,即这里的 Container
,就会调用 onTap
,GestureDector
可以发现包括 tap,drag 和 scales 事件。
在 onTap
里让页面返回,效果如下:
交互
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: onPressed,
child: new Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
new RaisedButton(
onPressed: () {
Navigator.of(context).pop(this);
},
child: new Text('返回'),
),
]);
}
}
在前一篇里已经遇到了 StatefulWidget
,首先 Counter
的 createState()
返回一个 _CounterState
实例。
看 CounterIncrementor 里的 CounterIncrementor({this.onPressed});
,这一句应该是构造方法,在 _CounterState
里调用是 new CounterIncrementor(onPressed: _increment)
,因此应该是用 _increment
这个函数赋给 CounterIncrementor
的 onPressed
,类型是 VoidCallback
。
/// Signature of callbacks that have no arguments and return no data.
typedef void VoidCallback();
然后当用户点击按压 RaisedButton 时,调用 onPressed
也就是 _increment
,将 _counter
加 1,setState
会引起再次 build
,于是 CounterDisplay
里的 Text 内容也就变了。
综合例子
// 一个产品类,一个 name 属性
class Product {
const Product({this.name});
final String name;
}
// 这个看后面代码,代表的是一个函数,有点像 C 语言的函数声明,但又不一样,不清楚这个语法
typedef void CartChangedCallback(Product product, bool inCart);
// 一个 StatelessWidget
class ShoppingListItem extends StatelessWidget {
// 这个构造三个参数,但 product 好像是后面的 product = product 来赋值
// this.inCart 应该就是传入的参数就覆盖自己的属性 inCart
ShoppingListItem({Product product, this.inCart, this.onCartChanged})
: product = product,
super(key: new ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// 根据布尔值返回不同的颜色
return inCart ? Colors.black54 : Theme.of(context).primaryColor;
}
TextStyle _getTextStyle(BuildContext context) {
if (!inCart) return null;
return new TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return new ListTile(
onTap: () {
// 触摸的时候,inCart 取反,然后回调外界传入的 onCartChanged
onCartChanged(product, !inCart);
},
leading: new CircleAvatar( // 不太清楚干什么的
backgroundColor: _getColor(context), // 调上面的方法返回不同的背景色
child: new Text(product.name[0]),
),
title: new Text(product.name, style: _getTextStyle(context)), // 这个 item 的文字
);
}
}
这是一个列表的 item,onCartChanged
应该就是一种回调函数,由父 Widget 传入,函数被调用后,父 Widget 更新内部的 State
,然后导致利用这个新的 inCart
值重新创建一个 ShoppingListItem
实例。然后看下这句牛逼哄哄的话:
Although the parent creates a new instance of ShoppingListItem when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying RenderObject.
class ShoppingList extends StatefulWidget {
ShoppingList({Key key, this.products}) : super(key: key);
final List<Product> products;
@override
_ShoppingListState createState() => new _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
Set<Product> _shoppingCart = new Set<Product>();
// 传给 item 的回调函数,setState 去重新 build
void _handleCartChanged(Product product, bool inCart) {
setState(() {
if (inCart)
_shoppingCart.add(product);
else
_shoppingCart.remove(product);
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Shopping List'),
),
body: new ListView(
padding: new EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((Product product) {
return new ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
// void main() {
// runApp(new MaterialApp(
// title: 'Shopping App',
// home: new ShoppingList(
// products: <Product>[
// new Product(name: 'Eggs'),
// new Product(name: 'Flour'),
// new Product(name: 'Chocolate chips'),
// ],
// ),
// ));
}
在前面的页面加个按钮跳过来
new RaisedButton(
onPressed: () {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
new ShoppingList(
products: <Product>[
new Product(name: 'Eggs'),
new Product(name: 'Flour'),
new Product(name: 'Chocolate chips'),
],
);
})
);
},
...
)
如果 ShoppingList
的父 Widget 重新构建一个 ShoppingList
,但是不会再次调用 createState()
去重新创建一个 _ShoppingListState
,而是会复用已经存在的 State 实例。但它会根据新的属性值重新构建,相当于 _ShoppingListState
实例对象只有一个,但是在重新创建 ShoppingList
时会再次调用 State 的 build
方法。嗯,应该是这样。
网友评论