
No more than code.
Flutter 中拥有 30 多种预定义的布局 widget,常用的有 Container、Padding、Center、Flex、Row、Colum、ListView、GridView。按照《Flutter 技术入门与实战》上面来说的话,大概分为四类
基础布局组件:Container(容器布局),Center(居中布局),Padding(填充布局),Align(对齐布局),Colum(垂直布局),Row(水平布局),Expanded(配合 Colum,Row 使用),FittedBox(缩放布局),Stack(堆叠布局),overflowBox(溢出父视图容器)。 宽高尺寸处理:SizedBox(设置具体尺寸),ConstrainedBox(限定最大最小宽高布局),LimitedBox(限定最大宽高布局),AspectRatio(调整宽高比),FractionallySizedBox(百分比布局) 列表和表格处理:ListView(列表),GridView(网格),Table(表格) 其它布局处理:Transform(矩阵转换),Baseline(基准线布局),Offstage(控制是否显示组件),Wrap(按宽高自动换行布局)
Row 和 Column 都是 Flex 的子类,只是 direction 参数不同。
Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
Column({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
mainAxisSize 主轴方向占用空间,只有两个属性 max 和 min;
在主轴(水平)方向占用的空间,默认是 MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子 widgets 实际占用多少水平空间,Row 的宽度始终等于水平方向的最大宽度;而 MainAxisSize.min 表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则 Row 的实际宽度等于所有子组件占用的的水平空间
mainAxisAlignment 设置主轴方向上排列展示
MainAxisAlignment.start : 默认值,将子控件放在主轴的开始位置 Column 靠上,Row 靠左; MainAxisAlignment.end : 将子控件放在主轴的结束位置,Column 靠下,Row 靠右; MainAxisAlignment.center : 将子控件放在主轴的中间位置 MainAxisAlignment.spaceBetween : 将主轴空白位置进行均分,排列子元素,手尾没有空隙 MainAxisAlignment.spaceAround : 将主轴空白区域均分,使中间各个子控件间距相等,首尾子控件间距为中间子控件间距的一半 MainAxisAlignment.spaceEvenly :将主轴空白区域均分,使各个子控件间距相等
crossAxisAlignment 设置交叉轴方向上排列展示
CrossAxisAlignment.end : Column 中会使子控件向右对齐,Row 中会使子控件向下对齐 CrossAxisAlignment.center : 默认值,子控件居中 CrossAxisAlignment.start : Column 中会使子控件向左对齐,Row 中会使子控件向上对齐 CrossAxisAlignment.stretch : Column 中会使子控件宽度调到最大,Row 则使子控件高度调到最大 CrossAxisAlignment.baseline : 按文本水平线对齐。与 TextBaseline 搭配使用
verticalDirection 文字的垂直布局方向,只有两个属性 down 和 up。VerticalDirection.down : 向下
TextDirection 文字方向从左往右(TextDirection.ltr);从右往左(TextDirection.rtl)
TextBaseline TextBaseline.alphabetic 用于对齐字母字符底部的水平线 TextBaseline.ideographic 用于对齐表意字符的水平线。
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row
Expanded 组件可以使 Row、Column、Fiex 等子组件在其主轴上方向展开并填充可用的空间,这里注意:Expanded 组件必须用在 Row、Column、Fiex 内,并且从 Expanded 到封装它的 Row、Column、Flex 的路径必须只包括 StatelessWidgets 或者 StatefulWidgets(不能是其他类型的组件,像 RenderObjectWidget,它是渲染对象,不再改变尺寸,因此 Expanded 不能放进 RenderObjectWidget)。 注意一点:在 Row 中使用 Expanded 的时候,无法指定 Expanded 中的子组件的宽度 width,但可以指定其高度 height。同理,在 Column 中使用 Expanded 的时候,无法指定 Expanded 中的子组件的高度 height,可以指定宽度 width。
Flexible 是一个控制 Row、Column、Flex 等子组件如何布局的组件。但不强制子组件填充可用空间。
Row(
children: <Widget>[
Container( /// 此组件在主轴方向占据48.0逻辑像素
width: 48.0
),
Expanded(
child: Container() /// 此组件会填满Row在主轴方向的剩余空间,撑开Row
)
]
)
// 使用Expanded控件,将一行的宽度分成四个等分,第一、三个child占1/4的区域,第二个child占1/2区域,由flex属性控制。
class MyColumn extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Expanded(
child: new Container(
color: Colors.red,
padding: EdgeInsets.all(5.0),
),
flex: 1,
),
new Expanded(
child: new Container(
color: Colors.yellow,
padding: EdgeInsets.all(5.0),
),
flex: 2,
),
new Expanded(
child: new Container(
color: Colors.blue,
padding: EdgeInsets.all(5.0),
),
flex: 1,
)
],
);
}
}
Container 提供的属性:
Container(
width: 100,
height: 110,
margin: EdgeInsets.fromLTRB(5, 5, 5, 30),
padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
child: Text("22")
alignment: Alignment.center,
transform: Matrix4.rotationZ(.2), //卡片倾斜变换
decoration: BoxDecoration(
color: Colors.white,
gradient: RadialGradient( //背景径向渐变
colors: [Colors.red, Colors.orange],
center: Alignment.topLeft,
radius: .98
),
boxShadow: [ //卡片阴影
BoxShadow(
color: Colors.black54,
offset: Offset(2.0, 2.0),
blurRadius: 4.0
)
],
border: Border.all(color: Color(0xFFFF0000), width: 0.5), // 设置边框
borderRadius: BorderRadius.circular((5.0)), // 设置圆角
image: DecorationImage( // 设置背景图片
image: AssetImage('images/blockBg/bg4.png'),
fit: BoxFit.cover)),
)
Container 的绘制的过程如下:
Container 自身尺寸的调节分两种情况:
尺寸限制
ConstrainedBox 用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是 80 像素,你可以使用 const BoxConstraints(minHeight: 80.0)作为子组件的约束。
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity, // 宽度尽可能大
minHeight: 50.0 // 最小高度为50像素
),
child: Container(
height: 5.0, // 即使将Container的高度设置为5像素,但是最终却是50像素,这正是ConstrainedBox的最小高度限制生效了
child: redBox
),
)
BoxConstraints 用于设置限制条件,它的定义如下:
const BoxConstraints({
this.minWidth = 0.0, //最小宽度
this.maxWidth = double.infinity, //最大宽度
this.minHeight = 0.0, //最小高度
this.maxHeight = double.infinity //最大高度
})
BoxConstraints 还定义了一些便捷的构造函数,用于快速生成特定限制规则的 BoxConstraints,如 BoxConstraints.tight(Size size)它可以生成给定大小的限制。
SizedBox 用于给子元素指定固定的宽高
SizedBox((width: 80.0), (height: 80.0), (child: redBox));
// 实际上SizedBox只是ConstrainedBox的一个定制,上面代码等价于:
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
child: redBox,
)
// 也等价于:
BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)
多重限制
多重限制时,对于 minWidth 和 minHeight 来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。
// 最终显示效果是宽90,高60
ConstrainedBox(
constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
child: redBox,
)
)
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Container
使用场景:FittedBox 在目前的项目中还未用到过。对于需要缩放调整位置处理的,一般都是图片。
const FittedBox({
Key key,
this.fit: BoxFit.contain,
this.alignment: Alignment.center,
Widget child,
})
参数含义:
fit:缩放的方式,默认的属性是 BoxFit.contain,child 在 FittedBox 范围内,尽可能的大,但是不超出其尺寸。这里注意一点,contain 是保持着 child 宽高比的大前提下,尽可能的填满,一般情况下,宽度或者高度达到最大值时,就会停止缩放。
BoxFit.none BoxFit.fill BoxFit.cover BoxFit.fitHeight BoxFit.fitWidth BoxFit.scaleDown
alignment:对齐方式,默认的属性是 Alignment.center,居中显示 child。
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FittedBox
Padding 作为一个基础 Widget 被使用的频率还是蛮高的,它的功能非常的单一,就是给子节点设置 padding 属性,你可以把它理解为前端的 css padding 属性。
Container(
color: Colors.blue,
width: 100.0,
height: 100.0,
child: new Padding(
padding: EdgeInsets.all(8.0),
child: new Card(
child: new Text('icepy'),
),
),
)
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Padding
SingleChildRenderObjectWidget : SingleChildRenderObjectWidget 是 RenderObjectWidgets 的一个子类,用于限制只能有一个子节点。它只提供 child 的存储,而不提供实际的更新逻辑。
RenderObjectWidgets : RenderObjectWidgets 为 RenderObjectElement 提供配置,而 RenderObjectElement 包含着(wrap)RenderObject,RenderObject 则是在应用中提供实际的绘制(rendering)的元素。
设置 child 的对齐方式。当设置了 widthFactor 或 heightFactor 时,Align 会根据 factor 属性来调整自身宽高,比如当 widthFactor 是 3 的时候,Align 会将自身的 width 调整为 child 的 3 倍。
参数含义:
Alignment 实际上是包含了两个属性的,其中第一个参数,-1.0 是左边对齐,1.0 是右边对齐,第二个参数,-1.0 是顶部对齐,1.0 是底部对齐。 widthFactor:宽度因子,如果设置的话,Align 的宽度就是 child 的宽度乘以这个值,不能为负数。 heightFactor:高度因子,如果设置的话,Align 的高度就是 child 的高度乘以这个值,不能为负数。
// The top left corner.
static const Alignment topLeft = const Alignment(-1.0, -1.0);
// 居右高于底部1/4处.
static const Alignment rightHalfBottom = alignment: const Alignment(1.0, 0.5),
Container(
width: 200,
height: 50,
color: Colors.green,
child: new Align(
alignment: Alignment.center,
child: new Text('icepy'),
),
)
Container(
color: Colors.green,
child: new Align(
widthFactor: 2.0,
heightFactor: 2.0,
child: new Text('icepy'),
),
)
A widget that centers its child within itself.
Stack 可以类比 CSS 中的 absolute(绝对布局),一般情况这个 Widget 只会用于显示比如图片之上的一些信息。它的布局行为对于 child Widget 来说只分 positioned 还是 non-positioned ,处理方式不同。
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Stack
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
overflow:超过的部分是否裁剪掉(clipped) alignment:对齐方式,默认是左上角(topStart)。 textDirection:文本的方向,绝大部分不需要处理。 fit:定义如何设置 non-positioned 节点尺寸,默认为 loose。 其中 StackFit 有如下几种:
// non-positioned
body: new Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
child: new Stack(
alignment: Alignment(0, 0),
children: <Widget>[
new Text(
'icepy'
),
],
),
)
// positioned
body: new Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
child: new Stack(
children: <Widget>[
new Positioned(
top: 10.0,
right: 10.0,
child: new Text(
'icepy'
),
)
],
),
)
IndexedStack 继承自 Stack,它的作用是显示第 index 个 child,其他 child 都是不可见的。所以 IndexedStack 的尺寸永远是跟最大的子节点尺寸一致。
// 构造函数
IndexedStack({
Key key,
AlignmentGeometry alignment = AlignmentDirectional.topStart,
TextDirection textDirection,
StackFit sizing = StackFit.loose,
this.index = 0,
List<Widget> children = const <Widget>[],
}) : super(key: key, alignment: alignment, textDirection: textDirection, fit: sizing, children: children);
// IndexedStack
body: new Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
child: new IndexedStack(
index: 1,
children: <Widget>[
new Container(
width: 20.0,
height: 20.0,
color: Colors.grey,
),
new Positioned(
top: 10.0,
right: 10.0,
child: new Container(
width: 50.0,
height: 50.0,
color: Colors.red,
),
)
],
),
)
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > OverflowBox
布局行为 当 OverflowBox 的最大尺寸大于 child 的时候,child 可以完整显示,当其小于 child 的时候,则以最大尺寸为基准,当然,这个尺寸都是可以突破父节点的。最后加上对齐方式,完成布局。
const OverflowBox({
Key key,
this.alignment = Alignment.center,
this.minWidth,
this.maxWidth,
this.minHeight,
this.maxHeight,
Widget child,
})
import 'package:flutter/material.dart';
class AVDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('AVAlertDialog'),
),
body: Column(
children:[
new RaisedButton(
child: new Text('click me'),
color: Colors.green,
onPressed: (){
showDialog(
context: context,
child: new SimpleDialog( // 没有按钮
title: new Text('Hello icepy'),
children: <Widget>[
new Text('dialog children')
],
)
);
},
),
new RaisedButton(
child: new Text('click me'),
color: Colors.green,
onPressed: (){
showDialog(
context: context,
child: new AlertDialog( // 带按钮
title: new Text('alert'),
actions: <Widget>[
new FlatButton(
child: new Text('确定'),
onPressed: (){
Navigator.pop(context);
},
)
],
)
)
}),
new RaisedButton(
child: new Text('click me'),
color: Colors.green,
onPressed: (){
showDialog(
context: context,
child: new AboutDialog( // 不常用,只用于显示应用
applicationName: 'Flutter',
applicationVersion: 'v1.0.0',
applicationIcon: new Icon(
Icons.android,
color: Colors.green,
),
children: <Widget>[
new Text(
'更新摘要 \n Update 1.0.0'
)
],
)
)
]
)
);
}
}
它提供了诸如:抽屉(drawers)底部按钮(bottom sheets)和 底部通知(snack bars)。你可以认为它是一基本快速实现某些布局的容器 Widget。
import 'package:flutter/material.dart';
class AVScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar( // AppBar
title: new Text('Home'),
),
body: new Center(
child: new RaisedButton(
child: new Text('click me'),
color: Colors.blue,
onPressed: () {
Scaffold.of(context).showSnackBar(new SnackBar( // snack bars 的效果
content: new Text('SnackBar'),
));
}
)
),
drawer: new Drawer( // Drawers 布局框架
child: new ListView(
padding: const EdgeInsets.only(),
children: <Widget>[
new ListTile(
title: new Text('Drawer'),
onTap: () => {},
)
],
),
),
bottomSheet: new Container( // bottom sheets ,这个效果其实和前端里的 position: fixed 很类似
child: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
controller: textCV,
),
),
new RaisedButton(
child: new Text('发表'),
onPressed: () {
setState(() {
data.add(textCV.text);
});
},
)
],
),
),
);
}
}