#####流式布局

#####Wrap
在介绍Row和Colum时,如果子widget超出屏幕范围,则会报溢出错误,如:

1
2
3
4
5
Row(
children: <Widget>[
Text("xxx"*100)
],
);

超出警告
可以看到,右边溢出部分报错。这是因为Row默认只有一行,如果超出屏幕不会折行。我们把超出屏幕显示范围会自动折行的布局称为流式布局。Flutter中通过Wrap和Flow来支持流式布局,将上例中的Row换成Wrap后溢出部分则会自动折行。下面是Wrap的定义:

1
2
3
4
5
6
7
8
9
10
11
12
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})

我们可以看到Wrap的很多属性在Row(包括Flex和Column)中也有,如direction、crossAxisAlignment、textDirection、verticalDirection等,这些参数意义是相同的,我们不再重复介绍,读者可以查阅前面介绍Row的部分。读者可以认为Wrap和Flex(包括Row和Column)除了超出显示范围后Wrap会折行外,其它行为基本相同。下面我们看一下Wrap特有的几个属性:

  • spacing:主轴方向子widget的间距
  • runSpacing:纵轴方向的间距
  • runAlignment:纵轴方向的对齐方式
    下面看一个示例子:
    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
    Widget _body(){
    return Wrap(
    spacing: 8.0, // 主轴(水平)方向间距
    runSpacing: 4.0, // 纵轴(垂直)方向间距
    alignment: WrapAlignment.center, //沿主轴方向居中
    children: <Widget>[
    new Chip(
    avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('A')),
    label: new Text('Hamilton'),
    ),
    new Chip(
    avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
    label: new Text('Lafayette'),
    ),
    new Chip(
    avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('H')),
    label: new Text('Mulligan'),
    ),
    new Chip(
    avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('J')),
    label: new Text('Laurens'),
    ),
    ],
    );
    }

#####Flow
我们一般很少会使用Flow,因为其过于复杂,需要自己实现子widget的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。
######Flow有如下优点:

  • 性能好;Flow是一个对child尺寸以及位置调整非常高效的控件,Flow用转换矩阵(transformation matrices)在对child进行位置调整的时候进行了优化:在Flow定位过后,如果child的尺寸或者位置发生了变化,在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘,而context.paintChild在重绘时使用了转换矩阵(transformation matrices),并没有实际调整Widget位置。
  • 灵活;由于我们需要自己实现FlowDelegate的paintChildren()方法,所以我们需要自己计算每一个widget的位置,因此,可以自定义布局策略。

######缺点:

  • 使用复杂.
  • 不能自适应子widget大小,必须通过指定父容器大小或实现TestFlowDelegate的getSize返回固定大小。
    示例:
    我们对六个色块进行自定义流式布局并实现TestFlowDelegate::
    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
    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: Flow(
    delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
    children: <Widget>[
    new Container(width: 80.0, height:80.0, color: Colors.red,),
    new Container(width: 80.0, height:80.0, color: Colors.green,),
    new Container(width: 80.0, height:80.0, color: Colors.blue,),
    new Container(width: 80.0, height:80.0, color: Colors.yellow,),
    new Container(width: 80.0, height:80.0, color: Colors.brown,),
    new Container(width: 80.0, height:80.0, color: Colors.purple,),
    ],
    ),
    );
    }
    }

    class TestFlowDelegate extends FlowDelegate {
    EdgeInsets margin = EdgeInsets.zero;
    TestFlowDelegate({this.margin});
    @override
    void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    //计算每一个子widget的位置
    for (int i = 0; i < context.childCount; i++) {
    var w = context.getChildSize(i).width + x + margin.right;
    if (w < context.size.width) {
    context.paintChild(i,
    transform: new Matrix4.translationValues(
    x, y, 0.0));
    x = w + margin.left;
    } else {
    x = margin.left;
    y += context.getChildSize(i).height + margin.top + margin.bottom;
    //绘制子widget(有优化)
    context.paintChild(i,
    transform: new Matrix4.translationValues(
    x, y, 0.0));
    x += context.getChildSize(i).width + margin.left + margin.right;
    }
    }
    }
    getSize(BoxConstraints constraints){
    //指定Flow的大小
    return Size(double.infinity,200.0);
    }
    @override
    bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
    }
    }

    可以看到我们主要的任务就是实现paintChildren,它的主要任务是确定每个子widget位置。由于Flow不能自适应子widget的大小,我们通过在getSize返回一个固定大小来指定Flow的大小。

Material widget库中提供了多种按钮Widget如RaisedButton、FlatButton、OutlineButton等,它们都是直接或间接对RawMaterialButton的包装定制,所以他们大多数属性都和RawMaterialButton一样。在介绍各个按钮时我们先介绍其默认外观,而按钮的外观大都可以通过属性来自定义,我们在后面统一介绍这些属性。另外,所有Material 库中的按钮都有如下相同点:

  • 按下时都会有“水波动画”。
  • 有一个onPressed属性来设置点击回调,当按钮按下时会执行该回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击。
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
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',
home: _home(),
);
}
}

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 Column(
children: <Widget>[
RaisedButton(
child: Text("RaisedButton"),
onPressed: _pressed,//onPressed点击回调 这里设置的是一个空的方法 默认是null,为null时会禁用点击事件
elevation: 2.0,
//正常状态下的阴影
highlightElevation: 4.0,
//按下时的阴影
disabledElevation: 0.0,
// 禁用时的阴影
),
FlatButton(
child: Text("FlatButton"),
onPressed: _pressed,
),
OutlineButton(
child: Text("OutlineButton"),
onPressed: _pressed,
),
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: _pressed,
),
FlatButton(
child: Text("自定义样式"),
//child按钮中的内容
textColor: Colors.white,
//文字颜色
disabledTextColor: Colors.red,
//按钮禁用时的文字颜色
color: Colors.lightBlue,
//背景颜色
disabledColor: Colors.grey,
//按钮禁用时的背景颜色
highlightColor: Colors.amber,
//按钮按下时的背景颜色
splashColor: Colors.black12,
//点击时,水波动画中水波的颜色
padding: EdgeInsets.all(2.0),
//内边距
colorBrightness: Brightness.dark,
////按钮主题,默认是浅色主题
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0)),
//外形
onPressed: _pressed,
),
],
),
);
}
void _pressed() {}
}

效果图
#####监听文本变化
1.设置onChange回调

1
2
3
4
5
6
7
8
9
10
11
12
TextField(
autofocus: false,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "用户密码",
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
onChanged: (value){//输入内容改变的回调监听
print(value);
},
),

2.通过controller监听

1
2
3
 controller: textEditingController,//点击提交时的回调
TextEditingController textEditingController=new TextEditingController();
//在用户点击提交或其他情况下通过textEditingController.text获取输入的值

#####线性布局Row和Column
所谓线性布局,即指沿水平或垂直方向排布子Widget。Flutter中通过Row和Column来实现线性布局,类似于Android中的LinearLayout控件。Row和Column都继承自Flex,我们将在弹性布局一节中详细介绍Flex。

#####主轴和纵轴
对于线性布局,有主轴和纵轴之分,如果布局是沿水平方,那么主轴就指是水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。

#####Row
Row可以在水平方向排列其子widget。定义如下:

1
2
3
4
5
6
7
8
9
Row({
...
TextDirection textDirection,
MainAxisSize mainAxisSize = MainAxisSize.max,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
VerticalDirection verticalDirection = VerticalDirection.down,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
List<Widget> children = const <Widget>[],
})
  • textDirection:表示水平方向子widget的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)。

  • mainAxisSize:表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子widgets实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子widgets没有占满水平剩余空间,则Row的实际宽度等于所有子widgets占用的的水平空间;

  • mainAxisAlignment:表示子Widgets在Row所占用的水平空间内对齐方式,如果mainAxisSize值为MainAxisSize.min,则此属性无意义,因为子widgets的宽度等于Row的宽度。只有当mainAxisSize的值为MainAxisSize.max时,此属性才有意义

  • MainAxisAlignment.start表示沿textDirection的初始方向对齐,如textDirection取值为TextDirection.ltr时,则MainAxisAlignment.start表示左对齐,textDirection取值为TextDirection.rtl时表示从右对齐。而MainAxisAlignment.end和MainAxisAlignment.start正好相反;MainAxisAlignment.center表示居中对齐。读者可以这么理解:textDirection是mainAxisAlignment的参考系。

  • verticalDirection:表示Row纵轴(垂直)的对齐方向,默认是VerticalDirection.down,表示从上到下。

  • crossAxisAlignment:表示子Widgets在纵轴方向的对齐方式,Row的高度等于子Widgets中最高的子元素高度,它的取值和MainAxisAlignment一样(包含start、end、 center三个值),不同的是crossAxisAlignment的参考系是verticalDirection,即verticalDirection值为VerticalDirection.down时crossAxisAlignment.start指顶部对齐,verticalDirection值为VerticalDirection.up时,crossAxisAlignment.start指底部对齐;而crossAxisAlignment.end和crossAxisAlignment.start正好相反;

  • children :子Widgets数组。
    示例:

    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
    Widget _body(){
    return Column(
    //测试Row对齐方式,排除Column默认居中对齐的干扰
    crossAxisAlignment: CrossAxisAlignment.start,//垂直方向 向左靠齐
    children: <Widget>[
    Row(
    mainAxisAlignment: MainAxisAlignment.center,//主轴居中
    children: <Widget>[
    Text(" hello world "),
    Text(" I am Jack "),
    ],
    ),
    Row(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.center,
    //,由于mainAxisSize值为MainAxisSize.min,Row的宽度等于两个Text的宽度和,
    // 所以对齐是无意义的,所以会从左往右显示
    children: <Widget>[
    Text(" hello world "),
    Text(" I am Jack "),
    ],
    ),
    Row(
    mainAxisAlignment: MainAxisAlignment.end,
    textDirection: TextDirection.rtl,
    //Row设置textDirection值为TextDirection.rtl,
    // 所以子widget会从右向左的顺序排列,而此时MainAxisAlignment.end表示左对齐
    children: <Widget>[
    Text(" hello world "),
    Text(" I am Jack "),
    ],
    ),
    Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    verticalDirection: VerticalDirection.up,
    //由于两个子Text字体不一样,所以其高度也不同,我们指定了verticalDirection值为VerticalDirection.up,
    // 即从低向顶排列,而此时crossAxisAlignment值为CrossAxisAlignment.start表示底对齐
    children: <Widget>[
    Text(" hello world ", style: TextStyle(fontSize: 30.0),),
    Text(" I am Jack "),
    ],
    ),
    ],
    );
    }

    解释:第一个Row很简单,默认为居中对齐;第二个Row,由于mainAxisSize值为MainAxisSize.min,Row的宽度等于两个Text的宽度和,所以对齐是无意义的,所以会从左往右显示;第三个Row设置textDirection值为TextDirection.rtl,所以子widget会从右向左的顺序排列,而此时MainAxisAlignment.end表示左对齐,所以最终显示结果就是图中第三行的样子;第四个Row测试的是纵轴的对齐方式,由于两个子Text字体不一样,所以其高度也不同,我们指定了verticalDirection值为VerticalDirection.up,即从低向顶排列,而此时crossAxisAlignment值为CrossAxisAlignment.start表示底对齐。
    Column
    Column可以在垂直方向排列其子widget。参数和Row一样,不同的是布局方向为垂直,主轴纵轴正好相反,读者可类比Row来理解,在此不再赘述。

#####特殊情况
如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有对最外面的Row或Column会占用尽可能大的空间,里面Row或Column所占用的空间为实际大小,下面以Column为例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Widget _body(){
return Container(
color: Colors.green,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
children: <Widget>[
Container(
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.max,//无效,内层Colum高度为实际高度
children: <Widget>[
Text("hello world "),
Text("I am Jack "),
],
),
)
],
),
),
);
}

所谓线性布局,即指沿水平或垂直方向排布子Widget。Flutter中通过Row和Column来实现线性布局,类似于Android中的LinearLayout控件。Row和Column都继承自Flex,我们将在弹性布局一节中详细介绍Flex
#####主轴和纵轴
对于线性布局,有主轴和纵轴之分,如果布局是沿水平方,那么主轴就指是水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。

#####简单的应用:

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
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 Column(
children: <Widget>[
TextField(//输入框Widget
autofocus: true,//是否自动获取焦点
decoration: InputDecoration(//InputDecoration:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
hintText: "用户名昵称",
prefixIcon: Icon(Icons.person),
),
),
TextField(
autofocus: false,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "用户密码",
prefixIcon: Icon(Icons.lock),
),
obscureText: true,//是否隐藏输入内容
)
],
)
);
}
}

效果图
#####常用属性:

  • controller:编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数情况下我们都需要显式提供一个controller来与文本框交互。如果没有提供controller,则TextField内部会自动创建一个
  • focusNode:用于控制TextField是否占有当前键盘的输入焦点。它是我们和键盘交互的一个handle。
  • InputDecoration:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
  • prefixIcon:输入框内侧左面的控件
  • suffixIcon: 输入框内侧右面的图标.
  • keyboardType:用于设置该输入框默认的键盘输入类型,取值如下:
    1.TextInputType.text(普通完整键盘)
    2.TextInputType.number(数字键盘)
    3.TextInputType.emailAddress(带有“@”的普通键盘)
    4.TextInputType.datetime(带有“/”和“:”的数字键盘)
    5.TextInputType.multiline(带有选项以启用有符号和十进制模式的数字键盘)
  • style:正在编辑的文本样式。
  • textAlign: 输入框内编辑文本在水平方向的对齐方式。
  • autofocus: 是否自动获取焦点。
  • obscureText:是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
  • maxLines:输入框的最大行数,默认为1;如果为null,则无行数限制。
  • maxLength和maxLengthEnforced :maxLength代表输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数
  • maxLengthEnforced决定当输入文本长度超过maxLength时是否阻止输入,为true时会阻止输入,为false时不会阻止输入但输入框会变红。
  • onChange:输入框内容改变时的回调函数;注:内容改变事件也可以通过controller来监听。
  • onEditingComplete和onSubmitted:这两个回调都是在输入框输入完成时触发,比如按了键盘的完成键(对号图标)或搜索键(🔍图标)。不同的是两个回调签名不同,onSubmitted回调是ValueChanged类型,它接收当前输入内容做为参数,而onEditingComplete不接收参数。
    inputFormatters:用于指定输入格式;当用户输入内容改变时,会根据指定的格式来校验。
    enable:如果为false,则输入框会被禁用,禁用状态不接收输入和事件,同时显示禁用态样式(在其decoration中定义)。
    cursorWidth、cursorRadius和cursorColor:这三个属性是用于自定义输入框光标宽度、圆角和颜色的。

#####未完待续…

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
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 Container(
height: 200.0,
decoration: new BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.amberAccent, Colors.lightBlue, Colors.red]),
border: Border.all(width: 5.0,color: Colors.green)

),
),
)
);
}

效果图

######快速开发
毫秒级的热重载,修改后,您的应用界面会立即更新。使用丰富的、完全可定制的widget在几分钟内构建原生界面。
######富有表现力和灵活的UI
快速发布聚焦于原生体验的功能。分层的架构允许您完全自定义,从而实现难以置信的快速渲染和富有表现力、灵活的设计。
######原生性能
Flutter包含了许多核心的widget,如滚动、导航、图标和字体等,这些都可以在iOS和Android上达到原生应用一样的性能。
######统一的应用开发体验
Flutter拥有丰富的工具和库,可以帮助您轻松地同时在iOS和Android系统中实现您的想法和创意。 如果您没有任何移动端开发体验,Flutter是一种轻松快捷的方式来构建漂亮的移动应用程序。 如果您是一位经验丰富的iOS或Android开发人员,则可以使用Flutter作为视图(View)层, 并可以使用已经用Java / ObjC / Swift完成的部分(Flutter支持混合开发)。
官方文档:https://flutterchina.club/

`

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
import 'package:flutter/material.dart';//引入包
//main.dart 程序第一个类 类似于android的MyApplication 系统默认入口 可通过studio自定义配置
//void main 当前页面的主入口类似于android中的onStart
void main() => runApp(MyApp());
//Stateless widgets 是不可变的, 这意味着它们的属性不能改变 - 所有的值都是最终的,适用于方法内部不会动态改变的时候使用.
//Stateful widgets 持有的状态可能在widget生命周期中发生变化,适用于方法内部需要动态改变的时候使用.
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
title:'flutter demo',//程序切换到后台时显示的标题
home: _home(),//程序主体
);
}
}
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(
//Scaffold 实现了基本的纸墨设计布局结构。可参考:https://www.jianshu.com/p/a08f43173b72
appBar: new AppBar(//标题栏
title: Text("title"),//标题
centerTitle: true,//标题居中
),
body: new Center(//body 页面布局 Center内的内容页面居中
child: new Text("hello word"),//child 子控件
)
);
}
}

flutter基础使用

叠在一起的fragment上层会响应下层的按钮点击事件,而且还会响应activity中的点击事件。在每个fragment布局的根节点加一条android:clickable=”true”可破。
这在个别手机上会出现这种情况,需要适配