No more than code.
Flutter 常用 Widget 详解
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(按宽高自动换行布局)
| MaterialApp
- title : 在任务管理窗口中所显示的应用名字
- theme : 应用各种 UI 所使用的主题颜色
- color : 应用的主要颜色值(primary color),也就是安卓任务管理窗口中所显示的应用颜色
- home : 应用默认所显示的界面 Widget
- routes : 应用的顶级导航表格,这个是多页面应用用来控制页面跳转的,类似于网页的网址
- initialRoute :第一个显示的路由名字,默认值为 Window.defaultRouteName
- onGenerateRoute : 生成路由的回调函数,当导航的命名路由的时候,会使用这个来生成界面
- onLocaleChanged : 当系统修改语言的时候,会触发 å 这个回调
- navigatorObservers : 应用 Navigator 的监听器
- debugShowMaterialGrid : 是否显示 纸墨设计 基础布局网格,用来调试 UI 的工具
- showPerformanceOverlay : 显示性能标签,https://flutter.io/debugging/#performanceoverlay
| Row & Column
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 & Flexible
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 提供的属性:
- key:Container 唯一标识符,用于查找更新。
- alignment:控制 child 的对齐方式
- padding:decoration 内部的空白区域,如果有 child 的话,child 位于 padding 内部。
- color:用来设置 container 背景色,如果 foregroundDecoration 设置的话,可能会遮盖 color 效果。
- decoration:绘制在 child 后面的装饰。container 背景色 color 和 decoration 不能同时设置。
- foregroundDecoration:绘制在 child 前面的装饰。
- width:container 的宽度,设置为 double.infinity 可以强制在宽度上撑满
- height:container 的高度,设置为 double.infinity 可以强制在高度上撑满
- constraints:你可以使用 BoxConstraints 添加到 child 上额外的约束条件
- margin:围绕在 decoration 和 child 之外的空白区域。
- transform:设置 container 的变换矩阵,类型为 Matrix4。
- child:container 中的内容 widget。
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 的绘制的过程如下:
- 首先会绘制 transform 效果;
- 接着绘制 decoration;
- 然后绘制 child;
- 最后绘制 foregroundDecoration。
Container 自身尺寸的调节分两种情况:
- Container 在没有子节点(children)的时候,会试图去变得足够大。除非 constraints 是 unbounded 限制,在这种情况下,Container 会试图去变得足够小。
- 带子节点的 Container,会根据子节点尺寸调节自身尺寸,但是 Container 构造器中如果包含了 width、height 以及 constraints,则会按照构造器中的参数来进行尺寸的调节。
尺寸限制
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,
)
)
- Container 会遵循如下顺序去尝试布局
- 对齐方式
- 调整自身的尺寸来适应子节点
- 采用 width、height 以及 constraints 布局
- 调整自身去适应父节点
- 调整自身到尽可能的小
- 如果没有子节点、没有设置 width、height 以及 constraints,并且父节点没有设置 unbounded 的限制,Container 会将自身调整到足够小。
- 如果没有子节点、对齐方式(alignment),但是提供了 width、height 或者 constraints,那么 Container 会根据自身以及父节点的限制,将自身调节到足够小。
- 如果没有子节点、width、height、constraints 以及 alignment,但是父节点提供了 bounded 限制,那么 Container 会按照父节点的限制,将自身调整到足够大。
- 如果有 alignment,父节点提供了 unbounded 限制,那么 Container 将会调节自身尺寸来包住 child;
- 如果有 alignment,并且父节点提供了 bounded 限制,那么 Container 会将自身调整的足够大(在父节点的范围内),然后将 child 根据 alignment 调整位置;
- 含有 child,但是没有 width、height、constraints 以及 alignment,Container 会将父节点的 constraints 传递给 child,并且根据 child 调整自身。
- margin,padding 也会影响布局
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Container
| FittedBox
使用场景: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
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)的元素。
| Align
设置 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'),
),
)
| Center
A widget that centers its child within itself.
| Stack 和 IndexedStack
Stack 可以类比 CSS 中的 absolute(绝对布局),一般情况这个 Widget 只会用于显示比如图片之上的一些信息。它的布局行为对于 child Widget 来说只分 positioned 还是 non-positioned ,处理方式不同。
- 如果是 positioned 的 child Widget 它的布局行为会根据设置的 top bottom right left 来确定
- 如果是 non-positioned 的 child Widget 它的布局行为会根据 Stack 的 aligment 对齐方式来处理
- 对于 child Widget 的叠加处理是 List 第一个 child Widget 在最下层,后面的依次在前一个 child 的上面,last child Widget 在最上层,位置的顺序非常类似 CSS 中的 z-index。如果想调整显示的顺序,则可以通过摆放 child 的顺序来进行。
继承关系
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 有如下几种:
- loose:子节点宽松的取值,可以从 min 到 max 的尺寸;
- expand:子节点尽可能的占用空间,取 max 尺寸;
- passthrough:不改变子节点的约束条件。
// 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,
),
)
],
),
)
| OverflowBox
继承关系
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,
})
| Dialog
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'
)
],
)
)
]
)
);
}
}
| Scaffold
它提供了诸如:抽屉(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);
});
},
)
],
),
),
);
}
}