StatelessWidget和StatefulWidget是flutter的基础组件,日常开发中自定义Widget都是选择继承这两者之一。

两者的区别在于状态的改变,StatelessWidget面向那些始终不变的UI控件,比如标题栏中的标题;而StatefulWidget则是面向可能会改变UI状态的控件,比如有点击反馈的按钮。

StatelessWidget就没什么好研究的了,StatefulWidget的创建需要指定一个State,在需要更新UI的时候调用setState(VoidCallback fn),并在VoidCallback中改变一些变量数值等,组件会重新build以达到刷新状态也就是刷新UI的效果。

显示在应用程序底部的材料窗口小部件,用于在少量视图中进行选择,通常在三到五之间。

底部导航栏由文本标签,图标或两者形式的多个项目组成,布置在一块材料的顶部。它提供应用程序顶级视图之间的快速导航。对于较大的屏幕,侧面导航可能更适合。

底部导航栏通常与Scaffold结合使用,它作为Scaffold.bottomNavigationBar参数提供。
效果图
#####代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
title: "title",
theme: ThemeData.light(),
home: _home(),
);
}
}
class _home extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _homeState();
}
}
class _homeState extends State<_home>{
final selectionColor=Colors.blue;//选中的颜色
final unSelectionColor=Colors.grey;//未选中的颜色
List<Widget> widgetList=List();//一个装切换页面的集合
int _curIndex=0;//默认选中的页下标
@override
void initState() {//重写initState 添加b布局
widgetList..add(HomeScreen())
..add(EmailScreen())//.. 返回调用者本身
..add(BlankScreen());
super.initState();
}

@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
bottomNavigationBar: BottomNavigationBar(//创建BottomNavigationBar
type: BottomNavigationBarType.fixed,//fixed点击时不移动,默认样式,shifting点击时动画和标签会淡入
currentIndex: _curIndex,//默认页下标
onTap: (int index){//点击时切换下标
setState(() {
_curIndex=index;
});
},
items: [//装入BottomNavigationBarItem 并设置样式
BottomNavigationBarItem(
icon: Icon(Icons.home,color:_curIndex==0?selectionColor:unSelectionColor,),
title: Text("home",style: TextStyle(color: _curIndex==0?selectionColor:unSelectionColor),)
),
BottomNavigationBarItem(
icon: Icon(Icons.account_balance,color: _curIndex==1?selectionColor:unSelectionColor,),
title: Text("blank",style: TextStyle(color: _curIndex==1?selectionColor:unSelectionColor),)
),
BottomNavigationBarItem(
icon: Icon(Icons.email,color: _curIndex==2?selectionColor:unSelectionColor,),
title: Text("email",style: TextStyle(color: _curIndex==2?selectionColor:unSelectionColor),)
),
]
),
body: widgetList[_curIndex],//主页面
);
}
}
//随便生成的3个页面
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text('HOME'),
),
body:Center(
child: Text('HOME'),
)
);
}
}
class EmailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text('EmailScreen'),
),
body:Center(
child: Text('EmailScreen'),
)
);
}
}
class BlankScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text('BlankScreen'),
),
body:Center(
child: Text('BlankScreen'),
)
);
}
}

代码风格

标识符三种类型

大驼峰

类、枚举、typedef和类型参数

1
2
3
4
5
class SliderMenu { ... }

class HttpRequest { ... }

typedef Predicate = bool Function<T>(T value);

包括用于元数据注释的类

1
2
3
4
5
6
7
8
9
class Foo {
const Foo([arg]);
}

@Foo(anArg)
class A { ... }

@Foo()
class B { ... }

使用小写加下划线来命名库和源文件

1
2
3
4
library peg_parser.source_scanner;

import 'file_system.dart';
import 'slider_menu.dart';

不推荐如下写法:

1
2
3
4
library pegparser.SourceScanner;

import 'file-system.dart';
import 'SliderMenu.dart';

使用小写加下划线来命名导入前缀

1
2
3
4
import 'dart:math' as math;
import 'package:angular_components/angular_components'
as angular_components;
import 'package:js/js.dart' as js;

不推荐如下写法:

1
2
3
4
import 'dart:math' as Math;
import 'package:angular_components/angular_components'
as angularComponents;
import 'package:js/js.dart' as JS;

使用小驼峰法命名其他标识符

1
2
3
4
5
6
7
var item;

HttpRequest httpRequest;

void align(bool clearItems) {
// ...
}

优先使用小驼峰法作为常量命名

1
2
3
4
5
6
7
const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
static final numberGenerator = Random();
}

不推荐如下写法:

1
2
3
4
5
6
7
const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

class Dice {
static final NUMBER_GENERATOR = Random();
}

不使用前缀字母

因为Dart可以告诉您声明的类型、范围、可变性和其他属性,所以没有理由将这些属性编码为标识符名称。

1
defaultTimeout

不推荐如下写法:

1
kDefaultTimeout

排序

为了使你的文件前言保持整洁,我们有规定的命令,指示应该出现在其中。每个“部分”应该用空行分隔。

在其他引入之前引入所需的dart库

1
2
3
4
5
import 'dart:async';
import 'dart:html';

import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

在相对引入之前先引入在包中的库

1
2
3
4
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'util.dart';

第三方包的导入先于其他包

1
2
3
4
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'package:my_package/util.dart';

在所有导入之后,在单独的部分中指定导出

1
2
3
4
import 'src/error.dart';
import 'src/foo_bar.dart';

export 'src/error.dart';

不推荐如下写法:

1
2
3
import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

所有流控制结构,请使用大括号

这样做可以避免悬浮的else问题

1
2
3
4
5
if (isWeekDay) {
print('Bike to work!');
} else {
print('Go dancing or read a book!');
}

例外

一个if语句没有else子句,其中整个if语句和then主体都适合一行。在这种情况下,如果你喜欢的话,你可以去掉大括号

1
if (arg == null) return defaultValue;

如果流程体超出了一行需要分划请使用大括号:

1
2
3
if (overflowChars != other.overflowChars) {
return overflowChars < other.overflowChars;
}

不推荐如下写法:

1
2
if (overflowChars != other.overflowChars)
return overflowChars < other.overflowChars;

注释

要像句子一样格式化

除非是区分大小写的标识符,否则第一个单词要大写。以句号结尾(或“!”或“?”)。对于所有的注释都是如此:doc注释、内联内容,甚至TODOs。即使是一个句子片段。

1
2
3
4
greet(name) {
// Assume we have a valid name.
print('Hi, $name!');
}

不推荐如下写法:

1
2
3
4
greet(name) {
/* Assume we have a valid name. */
print('Hi, $name!');
}

可以使用块注释(/…/)临时注释掉一段代码,但是所有其他注释都应该使用//

Doc注释

使用///文档注释来记录成员和类型。

使用doc注释而不是常规注释,可以让dartdoc找到并生成文档。

1
2
/// The number of characters in this chunk when unsplit.
int get length => ...

由于历史原因,达特茅斯学院支持道格评论的两种语法:///(“C#风格”)和/* /(“JavaDoc风格”)。我们更喜欢/// 因为它更紧凑。/*/在多行文档注释中添加两个无内容的行。在某些情况下,///语法也更容易阅读,例如文档注释包含使用*标记列表项的项目符号列表。

考虑为私有api编写文档注释

Doc注释并不仅仅针对库的公共API的外部使用者。它们还有助于理解从库的其他部分调用的私有成员

用一句话总结开始doc注释

以简短的、以用户为中心的描述开始你的文档注释,以句号结尾。

1
2
3
4
/// Deletes the file at [path] from the file system.
void delete(String path) {
...
}

不推荐如下写法:

1
2
3
4
5
6
7
/// Depending on the state of the file system and the user's permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can't be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) {
...
}

“doc注释”的第一句话分隔成自己的段落

在第一个句子之后添加一个空行,把它分成自己的段落

1
2
3
4
5
6
7
/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) {
...
}

Flutter_Go 使用参考

库的引用

flutter go 中,导入lib下文件库,统一指定报名,避免过多的../../

1
package:flutter_go/

字符串的使用

使用相邻字符串连接字符串文字

如果有两个字符串字面值(不是值,而是实际引用的字面值),则不需要使用+连接它们。就像在C和c++中,简单地把它们放在一起就能做到。这是创建一个长字符串很好的方法但是不适用于单独一行。

1
2
3
raiseAlarm(
'ERROR: Parts of the spaceship are on fire. Other '
'parts are overrun by martians. Unclear which are which.');

不推荐如下写法:

1
2
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
'parts are overrun by martians. Unclear which are which.');

优先使用模板字符串

1
'Hello, $name! You are ${year - birth} years old.';

在不需要的时候,避免使用花括号

1
2
'Hi, $name!'
"Wear your wildest $decade's outfit."

不推荐如下写法:

1
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';

不推荐如下写法:

1
2
'Hi, ${name}!'
"Wear your wildest ${decade}'s outfit."

集合

尽可能使用集合字面量

如果要创建一个不可增长的列表,或者其他一些自定义集合类型,那么无论如何,都要使用构造函数。

1
2
3
var points = [];
var addresses = {};
var lines = <Lines>[];

不推荐如下写法:

1
2
var points = List();
var addresses = Map();

不要使用.length查看集合是否为空

1
2
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

不推荐如下写法:

1
2
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');

考虑使用高阶方法转换序列

如果有一个集合,并且希望从中生成一个新的修改后的集合,那么使用.map()、.where()和Iterable上的其他方便的方法通常更短,也更具有声明性

1
2
3
var aquaticNames = animals
.where((animal) => animal.isAquatic)
.map((animal) => animal.name);

避免使用带有函数字面量的Iterable.forEach()

在Dart中,如果你想遍历一个序列,惯用的方法是使用循环。

1
2
3
for (var person in people) {
...
}

不推荐如下写法:

1
2
3
people.forEach((person) {
...
});

不要使用List.from(),除非打算更改结果的类型

给定一个迭代,有两种明显的方法可以生成包含相同元素的新列表

1
2
var copy1 = iterable.toList();
var copy2 = List.from(iterable);

明显的区别是第一个比较短。重要的区别是第一个保留了原始对象的类型参数

1
2
3
4
5
// Creates a List<int>:
var iterable = [1, 2, 3];

// Prints "List<int>":
print(iterable.toList().runtimeType);
1
2
3
4
5
// Creates a List<int>:
var iterable = [1, 2, 3];

// Prints "List<dynamic>":
print(List.from(iterable).runtimeType);

参数的使用

使用=将命名参数与其默认值分割开

由于遗留原因,Dart均允许“:”和“=”作为指定参数的默认值分隔符。为了与可选的位置参数保持一致,使用“=”。

1
void insert(Object item, {int at = 0}) { ... }

不推荐如下写法:

1
void insert(Object item, {int at: 0}) { ... }

不要使用显式默认值null

如果参数是可选的,但没有给它一个默认值,则语言隐式地使用null作为默认值,因此不需要编写它

1
2
3
void error([String message]) {
stderr.write(message ?? '\n');
}

不推荐如下写法:

1
2
3
void error([String message = null]) {
stderr.write(message ?? '\n');
}

变量

不要显式地将变量初始化为空

在Dart中,未显式初始化的变量或字段自动被初始化为null。不要多余赋值null

1
2
3
4
5
6
7
8
9
10
11
12
int _nextId;

class LazyId {
int _id;

int get id {
if (_nextId == null) _nextId = 0;
if (_id == null) _id = _nextId++;

return _id;
}
}

不推荐如下写法:

1
2
3
4
5
6
7
8
9
10
11
12
int _nextId = null;

class LazyId {
int _id = null;

int get id {
if (_nextId == null) _nextId = 0;
if (_id == null) _id = _nextId++;

return _id;
}
}

避免储存你能计算的东西

在设计类时,您通常希望将多个视图公开到相同的底层状态。通常你会看到在构造函数中计算所有视图的代码,然后存储它们:

应该避免的写法:

1
2
3
4
5
6
7
8
9
10
class Circle {
num radius;
num area;
num circumference;

Circle(num radius)
: radius = radius,
area = pi * radius * radius,
circumference = pi * 2.0 * radius;
}

如上代码问题:

  • 浪费内存
  • 缓存的问题是无效——如何知道何时缓存过期需要重新计算?

推荐的写法如下:

1
2
3
4
5
6
7
8
class Circle {
num radius;

Circle(this.radius);

num get area => pi * radius * radius;
num get circumference => pi * 2.0 * radius;
}

类成员

不要把不必要地将字段包装在getter和setter中

不推荐如下写法:

1
2
3
4
5
6
7
class Box {
var _contents;
get contents => _contents;
set contents(value) {
_contents = value;
}
}

优先使用final字段来创建只读属性

尤其对于 StatelessWidget

在不需要的时候不要用this

不推荐如下写法:

1
2
3
4
5
6
7
8
9
10
11
class Box {
var value;

void clear() {
this.update(null);
}

void update(value) {
this.value = value;
}
}

推荐如下写法:

1
2
3
4
5
6
7
8
9
10
11
class Box {
var value;

void clear() {
update(null);
}

void update(value) {
this.value = value;
}
}

构造函数

尽可能使用初始化的形式

不推荐如下写法:

1
2
3
4
5
6
7
class Point {
num x, y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}

推荐如下写法:

1
2
3
4
class Point {
num x, y;
Point(this.x, this.y);
}

不要使用new

Dart2使new 关键字可选

推荐写法:

1
2
3
4
5
6
7
8
9
10
Widget build(BuildContext context) {
return Row(
children: [
RaisedButton(
child: Text('Increment'),
),
Text('Click!'),
],
);
}

不推荐如下写法:

1
2
3
4
5
6
7
8
9
10
Widget build(BuildContext context) {
return new Row(
children: [
new RaisedButton(
child: new Text('Increment'),
),
new Text('Click!'),
],
);
}

异步

优先使用async/await代替原始的futures

async/await语法提高了可读性,允许你在异步代码中使用所有Dart控制流结构。

1
2
3
4
5
6
7
8
9
10
11
12
Future<int> countActivePlayers(String teamName) async {
try {
var team = await downloadTeam(teamName);
if (team == null) return 0;

var players = await team.roster;
return players.where((player) => player.isActive).length;
} catch (e) {
log.error(e);
return 0;
}
}

当异步没有任何用处时,不要使用它

如果可以在不改变函数行为的情况下省略异步,那么就这样做。、

1
2
3
Future afterTwoThings(Future first, Future second) {
return Future.wait([first, second]);
}

不推荐写法:

1
2
3
Future afterTwoThings(Future first, Future second) async {
return Future.wait([first, second]);
}

BottomAppBar 是 底部工具栏的意思,这个要比BottomNavigationBar widget灵活很多,可以放置文字和图标,当然也可以放置容器。
#####BottomAppBar的常用属性:

  • color:这个不用多说,底部工具栏的颜色。
  • shape:设置底栏的形状,一般使用这个都是为了和floatingActionButton融合,所以使用的值都是CircularNotchedRectangle(),有缺口的圆形矩形。
  • child : 里边可以放置大部分Widget,让我们随心所欲的设计底栏。

#####代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
title: "flutter demo",
theme: ThemeData(
primarySwatch: Colors.lightBlue //primarySwatch :现在支持18种主题样本。
),
home: _home(),
);
}
}
class _home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _homeState();
}
}
class _homeState extends State<_home> {
List<Widget> _viewList; //创建视图数组
int _index = 0; //数组索引,通过改变索引值改变视图
@override
void initState() {
super.initState();
_viewList = List();
_viewList..add(EachView("HOME"))..add(EachView("CLOCK"));
}
_openNewPage() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (BuildContext context) {
return EachView("new Pager");
}));
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _openNewPage,
child: Icon(
Icons.add,
color: Colors.white,
)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
//和底栏进行融合
bottomNavigationBar: BottomAppBar(
color: Colors.lightBlue, //底部工具栏的颜色。
shape: CircularNotchedRectangle(),
//设置底栏的形状,一般使用这个都是为了和floatingActionButton融合,
// 所以使用的值都是CircularNotchedRectangle(),有缺口的圆形矩形。
child: Row(
//里边可以放置大部分Widget,让我们随心所欲的设计底栏
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: Icon(
Icons.home,
color: Colors.white,
),
color: Colors.white,
onPressed: () {
setState(() {
_index = 0;
});
},
),
IconButton(
icon: Icon(Icons.access_alarms, color: Colors.white),
color: Colors.white,
onPressed: () {
setState(() {
_index = 1;
});
},
),
],
),
),
body: _viewList[_index],
);
}
}
//子页面
//代码中设置了一个内部的_title变量,这个变量是从主页面传递过来的,然后根据传递过来的具体值显示在APP的标题栏和屏幕中间。
class EachView extends StatefulWidget {
String _title;
EachView(this._title);
@override
_EachViewState createState() => _EachViewState();
}
class _EachViewState extends State<EachView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget._title)),
body: Center(child: Text(widget._title)),
);
}
}

######Chip
中文翻译为碎片的意思,一般也是用作商品或者一些东西的标签。

好吧,还是看看怎么使用吧

#####构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Chip({
Key key,
this.avatar,//标签左侧Widget,一般为小图标
@required this.label,//标签
this.labelStyle,
this.labelPadding,//padding
this.deleteIcon//删除图标,
this.onDeleted//删除回调,为空时不显示删除图标,
this.deleteIconColor//删除图标的颜色,
this.deleteButtonTooltipMessage//删除按钮的tip文字,
this.shape//形状,
this.clipBehavior = Clip.none,
this.backgroundColor//背景颜色,
this.padding,
this.materialTapTargetSize//删除图标material点击区域大小,
})

#####代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(
home: MyApp(),
));
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Chip"),
),
body: Center(
child: Chip(
label: Text("flyou"),
),
),
);
}
}


纳尼,这是什么鬼
这跟前面讲的ToolTip显示效果也好像啊,像吗?确实很像,确实也不是很像,接着往下看吧。

好吧,刚才构造方法中有那么多的属性,我们来看下怎么使用吧。

我们给我们的Chip加上一个 avatar再来看下效果
avatar: Icon(Icons.add_location, color: Colors.lightBlue)

那么再来看下这几个与delete相关的属性吧

我们先仅仅给Chip添加 onDeleted属性
onDeleted: (){}

可以看到,我们仅仅给Chip添加了一个onDeleted属性它便给我们多出了一个删除的按钮和长按的Tooltip提示(没错,就是我们上篇文章讲到的Tooltip,感兴趣的童鞋可以去看下源码哈)。

那么,我们尝试修改与delete相关的其他属性再来看下效果
#####代码:

1
2
3
deleteIcon: Icon(Icons.delete_forever),
deleteIconColor: Colors.red,
deleteButtonTooltipMessage: "删除该条",


修改背景颜色
shape:RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0))

当然,你还可以修改labelStyle来修改文字显示效果,修改padding来控制chip的大小。

当然,与Chip相关的还有InputChip、ChoiceChip、ActionChip和FilterChip,用法和Chip类似,只不过会稍微多几个属性而已,大家感兴趣的可以尝试下。
#####小结

  • Chip是一个很常见的标签组件
  • 使用Chip的一些属性可以很方便的完成对Chip效果的自定义
  • Chip自带删除按钮和删除监听,可以方便做其他操作

ExpansionTile Widget就是一个可以展开闭合的组件,常用的属性有如下几个。

  • title:闭合时显示的标题,这个部分经常使用Text Widget。
  • leading:标题左侧图标,多是用来修饰,让界面显得美观。
  • backgroundColor: 展开时的背景颜色,当然也是有过度动画的,效果非常好。
  • children: 子元素,是一个数组,可以放入多个元素。
  • trailing : 右侧的箭头,你可以自行替换但是我觉的很少替换,因为谷歌已经表现的很完美了。
  • initiallyExpanded: 初始状态是否展开,为true时,是展开,默认为false,是不展开。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import 'package:flutter/material.dart';
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return new MaterialApp(
    title: "flutter demo",
    theme: new ThemeData.dark(),
    home: _home(),
    );
    }
    }
    class _home extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
    // TODO: implement createState
    return _homeState();
    }
    }
    class _homeState extends State<_home> {
    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
    appBar: new AppBar(
    title: new Text("title"),
    ),
    body: new Center(
    child: ExpansionTile(
    title: Text("title"),
    leading: Icon(Icons.favorite,color: Colors.white,),
    backgroundColor: Colors.lightBlue,
    initiallyExpanded: false,//默认是否展开
    children: <Widget>[
    ListTile(
    title: Text("ListTile"),
    leading: Icon(Icons.favorite_border,color: Colors.white,),
    ),
    ],
    ),
    ),
    );
    }
    }

#####Flexible和 Expanded的区别是:
Flexible是一个控制Row、Column、Flex等子组件如何布局的组件。
Flexible组件可以使Row、Column、Flex等子组件在主轴方向有填充可用空间的能力(例如,Row在水平方向,Column在垂直方向),但是它与Expanded组件不同,它不强制子组件填充可用空间。
Flexible组件必须是Row、Column、Flex等组件的后裔,并且从Flexible到它封装的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidgets)。
Row、Column、Flex会被Expanded撑开,充满主轴可用空间。
使用方式:

1
2
3
4
5
6
7
8
9
10
Row(
children: <Widget>[
Container( /// 此组件在主轴方向占据48.0逻辑像素
width: 48.0
),
Expanded(
child: Container() /// 此组件会填满Row在主轴方向的剩余空间,撑开Row
)
]
)


Expanded组件可以使Row、Column、Flex等子组件在其主轴方向上展开并填充可用空间(例如,Row在水平方向,Column在垂直方向)。如果多个子组件展开,可用空间会被其flex factor(表示扩展的速度、比例)分割。

Expanded组件必须用在Row、Column、Flex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸了,因此Expanded不能放进RenderObjectWidget)。

 下面一个例子展示Flexible和Expanded之间的区别
Expanded的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import 'package:flutter/material.dart';
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('水平方向布局'),
),
body: new Row(
children: <Widget>[
new RaisedButton(
onPressed: () {
print('点击红色按钮事件');
},
color: const Color(0xffcc0000),
child: new Text('红色按钮'),
),
new Expanded(
flex: 1,
child: new RaisedButton(
onPressed: () {
print('点击黄色按钮事件');
},
color: const Color(0xfff1c232),
child: new Text('黄色按钮'),
),
),
new RaisedButton(
onPressed: () {
print('点击粉色按钮事件');
},
color: const Color(0xffea9999),
child: new Text('粉色按钮'),
),
]
),
);
}
}
void main() {
runApp(
new MaterialApp(
title: 'Flutter教程',
home: new LayoutDemo(),
),
);
}


Flexible的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import 'package:flutter/material.dart';
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('水平方向布局'),
),
body: new Row(
children: <Widget>[
new RaisedButton(
onPressed: () {
print('点击红色按钮事件');
},
color: const Color(0xffcc0000),
child: new Text('红色按钮'),
),
new Flexible(
flex: 1,
child: new RaisedButton(
onPressed: () {
print('点击黄色按钮事件');
},
color: const Color(0xfff1c232),
child: new Text('黄色按钮'),
),
),
new RaisedButton(
onPressed: () {
print('点击粉色按钮事件');
},
color: const Color(0xffea9999),
child: new Text('粉色按钮'),
),
]
),
);
}
}
void main() {
runApp(
new MaterialApp(
title: 'Flutter教程',
home: new LayoutDemo(),
),
);
}

#####Padding
Padding可以给其子节点添加补白(填充),我们在前面很多示例中都已经使用过它了,现在来看看它的定义:

1
2
3
4
5
Padding({
...
EdgeInsetsGeometry padding,
Widget child,
})

EdgeInsetsGeometry是一个抽象类,开发中,我们一般都使用EdgeInsets,它是EdgeInsetsGeometry的一个子类,定义了一些设置补白的便捷方法。

#####EdgeInsets
我们看看EdgeInsets提供的便捷方法:

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的补白。
  • all(double value) : 所有方向均使用相同数值的补白。
  • only({left, top, right ,bottom }):可以设置具体某个方向的补白(可以同时指定多个方向)。
  • symmetric({ vertical, horizontal }):用于设置对称方向的补白,vertical指top和bottom,horizontal指left和right。

#####示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class _home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _homeState();
}
}

class _homeState extends State<_home> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: new Text("title"),
centerTitle: true,
),
body: //通过ConstrainedBox来确保Stack占满屏幕
Padding(
//上下左右各添加16像素补白
padding: EdgeInsets.all(16.0),
child: Column(
//显式指定对齐方式为左对齐,排除对齐干扰
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
//左边添加8像素补白
padding: const EdgeInsets.only(left: 8.0),
child: Text("Hello world"),
),
Padding(
//上下各添加8像素补白
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text("I am Jack"),
),
Padding(
// 分别指定四个方向的补白
padding: const EdgeInsets.fromLTRB(20.0, .0, 20.0, 20.0),
child: Text("Your friend"),
)
],
),
));
}
}

一个固定高度的行,通常包含一些文本,以及一个行前或行尾图标。
构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ListTile({
Key key,
this.leading,//左侧widget
this.title,//标题
this.subtitle,//副标题
this.trailing,//右侧widget
this.isThreeLine = false,//是否默认3行高度,subtitle不为空时才能使用
this.dense,//设置为true后字体变小
this.contentPadding,//内边距
this.enabled = true,//能否被点击
this.onTap,//点击事件
this.onLongPress,//长按事件
this.selected = false,//展开是否默认显示选中
})

效果图

Stack即层叠布局控件,能够将子控件层叠排列。

Stack控件的每一个子控件都是定位或不定位,定位的子控件是被Positioned控件包裹的。Stack控件本身包含所有不定位的子控件,其根据alignment定位(默认为左上角)。然后根据定位的子控件的top、right、bottom和left属性将它们放置在Stack控件上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class _home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _homeState();
}
}

class _homeState extends State<_home> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: Text("title"),
centerTitle: true,
),
body: new Center(
child: new Stack(
children: <Widget>[
Image.network(
"http://a.hiphotos.baidu.com/image/h%3D300/sign=ca66f12cffd3572c79e29adcba116352/3b87e950352ac65cd08fc0b6f6f2b21192138a69.jpg"),
new Positioned(
top: 20.0,
left: 10.0,
right: 0.0,
bottom: 30.0,
child: new Text("Positioned",style:TextStyle(fontSize:18.0,color: Colors.white),)),
],
),
));
}
}