一、Stack Widget
Stack
Widget 可以用来设置多个子 Widget ,这些子 Widget 以堆叠的方式进行排列。Stack
的子 Widget 可以分为已定位和未定位,定位使用 Positioned
Widget 配合 Stack
一起使用。Stack
本身的大小将包含所有未定位的子 Widget ,这些子 Widget 根据对齐方式定位(在从左到右的环境中,默认为左上角,在从右到左的环境中,默认为右上角)。然后根据已定位的子 Widget 的上、右、下、左的属性相对于 Stack
放置它们。
Stack
的构造方法如下:
Stack({
Key key,
//AlignmentGeometry类型可选命名参数,如何对齐Stack中未定位和部分定位的子Widget
//使用AlignmentDirectional
this.alignment = AlignmentDirectional.topStart,
//TextDirection类型可选命名参数,文本对齐方向设置
this.textDirection,
//StackFit类型可选命名参数,如何调整Stack中未定位的子Widget
this.fit = StackFit.loose,
//Overflow类型可选命名参数,设置溢出部分如何剪裁
this.overflow = Overflow.clip,
//List<Widget>类型可选命名参数,要显示的Widget
List<Widget> children = const <Widget>[],
})
AlignmentDirectional
用于设置如何对齐 Stack
中未定位和部分定位的子 Widget 。对于未定位的子 Widget 项彼此相对放置,以使通过对齐确定的点位于同一位置。部分定位的子 Widget 指的是未在特定轴上指定对齐方式的子 Widget ,使用对齐方式来确定应如何在未指定的轴上定位它们。AlignmentDirectional
的构造方法如下:
//两个参数均为double类型
const AlignmentDirectional(this.start, this.y)
可以使用构造方法设置,也可以直接使用提供好的位置静态属性,如下:
/// The top corner on the "start" side.
static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
/// The center point along the top edge.
///
/// Consider using [Alignment.topCenter] instead, as it does not need
/// to be [resolve]d to be used.
static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
/// The top corner on the "end" side.
static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
/// The center point along the "start" edge.
static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
/// The center point, both horizontally and vertically.
///
/// Consider using [Alignment.center] instead, as it does not need to
/// be [resolve]d to be used.
static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
/// The center point along the "end" edge.
static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
/// The bottom corner on the "start" side.
static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
/// The center point along the bottom edge.
///
/// Consider using [Alignment.bottomCenter] instead, as it does not
/// need to be [resolve]d to be used.
static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
/// The bottom corner on the "end" side.
static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
位置属性简单易懂,不做中文说明了。AlignmentDirectional
的设置与 TextDirection
是关系的,对于不同的环境方向,AlignmentDirectional
的起始位置也不同。
StackFit
用于设置如何调整Stack中未定位的子 Widget ,其是一个枚举类型值,如下:
enum StackFit {
//从其父级传递到Stack的约束被放宽了
//例如:如果Stack具有强制其为350x600的约束,那么这将允许Stack的未定位子Widget
//具有从0到350的任何宽度和从0到600的任何高度
loose,
//从其父级传递到Stack的约束被收紧到允许的最大大小。
//例如:如果Stack具有宽度在10到100范围内、高度在0到600范围内的松散约束,则Stack中未
//定位的子级的大小都将为100像素宽、600像素高
expand,
//从其父级传递到Stack的约束未修改地传递到未定位的子级
//例如:如果Stack是行的扩展子级,则水平约束将是紧的,而垂直约束将是松的
passthrough,
}
Stack
的使用方式如下:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
width: 500,
height: 200,
color: Colors.yellow,
child:Stack( //Stack
alignment: AlignmentDirectional.topStart,
textDirection: TextDirection.ltr,
fit: StackFit.loose,
overflow: Overflow.clip,
children: <Widget>[
Image.network("http://www.mwpush.com/uploads/avatar.png"),
Text("Stack1", style: TextStyle(color: Colors.blue, fontSize: 30),),
Text("Stack2",style: TextStyle(color: Colors.red, fontSize: 20),),
],
),
),
);
}
}
效果如下:
定位 Widget 使用 Positioned
,用于控制 Stack
的子 Widget 的位置。如果一个 Widget 包装在 Positioned
中,那么这个 Widget 就是 Stack
中的一个定位 Widget 。如果单独设置 top
则表示该子 Widget 距离 Stack
顶部的距离,同时设定 top
和 bottom
则子 Widget 会根据设置调整子 Widget 的高度以使其满足顶部和底部的距离,左右也是一样。其中 top
、bottom
和 height
只能同时设置其中的两项,另外一项必须为空,如果都为空,则使用 Stack
的 alignment
在垂直方向进行定位。 left
、right
和 width
也只能设置其中两项,另外一项必须为空,如果所有都为空,则使用 Stack
的 alignment
在水平方向进行定位。
Positioned
的构造方法如下:
const Positioned({
Key key,
//double类型可选命名参数,子Widget底部距离Stack左边的距离
this.left,
//double类型可选命名参数,子Widget底部距离Stack顶部的距离
this.top,
//double类型可选命名参数,子Widget底部距离Stack右边的距离
this.right,
//double类型可选命名参数,子Widget底部距离Stack底部的距离
this.bottom,
//double类型可选命名参数,子Widget的宽度
this.width,
//double类型可选命名参数,子Widget的高度
this.height,
//Widget类型必传参数,要显示的Widget
@required Widget child,
})
使用方法如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
width: 500,
height: 200,
color: Colors.yellow,
child:Stack( //Stack
alignment: AlignmentDirectional.topStart,
textDirection: TextDirection.ltr,
fit: StackFit.loose,
overflow: Overflow.clip,
children: <Widget>[
Image.network("http://www.mwpush.com/uploads/avatar.png"),
Positioned( //Positioned
bottom: 10,
top: 20,
left: 120,
width: 100,
child: Container(child: Text("Stack1", style: TextStyle(color: Colors.blue, fontSize: 30,), ), color: Colors.red,),
),
Text("Stack2",style: TextStyle(color: Colors.red, fontSize: 20),),
],
),
),
);
}
}
效果如下:
二、IndexedStack Widget
Stack
可以显示一组 Widget ,IndexedStack
继承自 Stack
,用于选择性的只显示一组 Widget 中的一个。要显示的 Widget 通过 index
索引设置,编号从 0 开始。IndexedStack
的大小总是和一组 Widget 中的最大的 Widget 一样大。构造方法如下:
IndexedStack({
Key key,
//AlignmentGeometry类型可选命名参数,如何对齐Stack中未定位和部分定位的子Widget
//使用AlignmentDirectional
AlignmentGeometry alignment = AlignmentDirectional.topStart,
//TextDirection类型可选命名参数,文本对齐方向设置
TextDirection textDirection,
//StackFit类型可选命名参数,如何调整Stack中未定位的子Widget
StackFit sizing = StackFit.loose,
//int类型可选参数,要显示的Widget
this.index = 0,
//List<Widget>类型可选命名参数,要显示的Widget
List<Widget> children = const <Widget>[],
})
IndexedStack
只比 Stack
多了一个 index
属性,其他属性相同,默认显示 Widget 列中的第一个 Widget ,下标为 0 。IndexedStack
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
width: 500,
height: 200,
color: Colors.yellow,
child:IndexedStack( //IndexedStack
alignment: AlignmentDirectional.topStart,
textDirection: TextDirection.ltr,
sizing: StackFit.loose,
index: 2, //要显示的Widget的下标
children: <Widget>[
Image.network("http://www.mwpush.com/uploads/avatar.png"),
Positioned(
bottom: 10,
top: 20,
left: 120,
width: 100,
child: Container(child: Text("Stack1", style: TextStyle(color: Colors.blue, fontSize: 30,), ), color: Colors.red,),
),
Text("Stack2",style: TextStyle(color: Colors.red, fontSize: 20),),
],
),
),
);
}
}
三、SizedBox Widget
SizedBox
是设置固定尺寸的 Widget ,如果设置了宽高,则子 Widget 会被强制为 SizedBox
的尺寸,如果不设置宽高,则 SizedBox
的尺寸将与子 Widget 相同。 如果没有给定子 Widget ,SizedBox
将尝试在给定父对象约束的情况下,将自身的大小调整到尽可能接近指定的高度和宽度。如果高度或宽度为空或未指定,将被视为零。
有以下几个构造方法:
//创建固定尺寸的SizedBox,宽高可以为空,此时表示大小不受限制
const SizedBox({
Key key,
//double类型可选命名参数,设置SizedBox的宽度,子Widget也会具有此宽度
this.width,
//double类型可选命名参数,设置SizedBox的高度,子Widget也会具有此高度
this.height,
//Widget类型可选命名参数,要显示的Widget
Widget child
})
//创建一个将变得与其父级允许一样大的SizedBox,等效于将width和height设置为double.infinity
const SizedBox.expand({
Key key,
//Widget类型可选命名参数,要显示的Widget
Widget child
})
//创建一个将变得尽可能小的SizeBox
const SizedBox.shrink({
Key key,
//Widget类型可选命名参数,要显示的Widget
Widget child
})
//创建一个指定尺寸的SizeBox
SizedBox.fromSize({
Key key,
//Widget类型可选命名参数,要显示的Widget
Widget child,
//Size类型可选命名参数,设置SizedBox的尺寸,子Widget也具有此尺寸
Size size
})
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: SizedBox(
height: 100,
width: 300,
child: Container(
color: Colors.red,
width: 10,
height: 20,
),
),
);
}
}
这里的子 Widget 是 Container
,虽然 Container
也设置了宽高,但是没有生效,其宽高与 SizedBox
相同。但是如果子 Widget 不能设置宽高属性,则子 Widget 的大小不会与 SizedBox
设置的相同,但是会受其最大尺寸约束。上述代码效果如下:
四、Flex Widget
Flex
使用一维数组来设置一组子 Widget ,其可以通过设置主轴的方向来控制水平或垂直显示 Widget ,它是上篇文章中介绍过的 Row
和 Column
的父类。如果在使用前已知要显示的 Widget 的方向,应考虑使用 Row
或 Column
。
Flex
是一个不可以滚动的 Widget ,当要显示的 Widget 超过可用范围则会抛出异常。其构造方法如下:
Flex({
Key key,
//Axis类型必传参数,设置主轴的方向(垂直或者水平),不设置会抛出异常
@required this.direction,
//MainAxisAlignment类型可选命名参数,如何沿着主轴放置子Widget
this.mainAxisAlignment = MainAxisAlignment.start,
//MainAxisSize类型可选命名参数,主轴上应占用多少空间,该值传入最大化还是最小化可用空间
this.mainAxisSize = MainAxisSize.max,
//CrossAxisAlignment类型可选命名参数,如何沿着次轴放置子Widget
this.crossAxisAlignment = CrossAxisAlignment.center,
//TextDirection类型可选命名参数,设置子Widget横向的排列方向,默认为环境方向
this.textDirection,
//VerticalDirection类型可选命名参数,设置子Widget纵向的排列顺序以及如何解释垂直方向的开始和结束
this.verticalDirection = VerticalDirection.down,
//TextBaseline类型可选命名参数,果根据基线对齐项目,使用哪个基线
this.textBaseline,
//List<Widget>类型可选命名参数,要显示的Widgets
List<Widget> children = const <Widget>[],
})
Axis
用于设置主轴的方向,即要显示的 Widgets 的排列方向,是一个枚举类型值,如下:
enum Axis {
//水平排列
horizontal,
//垂直排列
vertical,
}
其他属性在上一篇文章做过说明,可查看上篇文章的 Row
部分。 Flex
的使用方式如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Flex(
direction: Axis.horizontal,
children: <Widget>[
Text("Text1"),
Text("Text2"),
Text("Text3"),
Text("Text4"),
Text("Text5"),
],
),
);
}
}
五、Expanded Widget
Expanded
是用来扩展 Row
、Column
或 Flex
的子 Widget ,其作用是按一定比例填充子 Widgets 间的可用空间。如果要扩展多个 Widget ,可以通过设置扩展因子在可用空间进行空间分配。其构造方法如下:
const Expanded({
Key key,
//int类型可选命名参数,扩展因子
int flex = 1,
//Widget类型必传参数,要显示的Widget
@required Widget child,
})
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
child: Container(child: Text("Text1"), color: Colors.red, height: 50,),
flex: 1,
),
Expanded(
child: Container(child: Text("Text2"), color: Colors.blue, height: 50,),
flex: 2,
),
Expanded(
child: Container(child: Text("Text3"), color: Colors.yellow, height: 50,),
flex: 3,
),
],
),
);
}
}
效果如下:
上面的代码中设置了扩展因子,是倍数关系,所以显示的结果宽度也是倍数关系,如果是垂直排列 Widget ,则高度是倍数关系。不设置扩展因子则子 Widget 会平均分配可用空间。也可以单独设置某个子 Widget 的扩展因子。
六、Wrap Widget
Wrap
也可以显示多个子 Widget ,其可以设置排列方向(水平或者垂直)显示 Widgets ,与 Row
和 Column
的不同之处在于,Row
和 Column
在可用空间无法显示全部 Widget 时会抛出异常,而 Warp
显示不下时会自动换行。
构造方法如下:
Wrap({
Key key,
//Axis类型可选命名参数,设置主轴的方向(垂直或者水平)
this.direction = Axis.horizontal,
//WrapAlignment类型可选命名参数,在主轴上如何排列子Widgets
this.alignment = WrapAlignment.start,
//double类型可选命名参数,主轴上排列的每个Widget之间的间隙
this.spacing = 0.0,
//WrapAlignment类型可选命名参数,!!!没有整明白此参数的作用,使用也没有看到效果
this.runAlignment = WrapAlignment.start,
//double类型可选命名参数,如果是横向排列则是每行之间的间距,纵向排列则是每列之间的间距
this.runSpacing = 0.0,
//WrapCrossAlignment类型可选命名参数,如何沿着次轴放置子Widget
this.crossAxisAlignment = WrapCrossAlignment.start,
//TextDirection类型可选命名参数,设置子Widget横向的排列方向,默认为环境方向
this.textDirection,
//VerticalDirection类型可选命名参数,设置子Widget纵向的排列顺序以及如何解释垂直方向的开始和结束
this.verticalDirection = VerticalDirection.down,
//List<Widget>类型可选命名参数,要显示的Widgets
List<Widget> children = const <Widget>[],
})
使用如下
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
color: Colors.amber,
child: Wrap( //Wrap
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.end,
spacing: 10,
runSpacing: 20,
runAlignment: WrapAlignment.center,
children: <Widget>[
Container(child: Text("Text1"), color: Colors.red, height: 100, width: 50,),
Container(child: Text("Text2"), color: Colors.red, height: 50, width: 100,),
Container(child: Text("Text3"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text4"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text5"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text6"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text7"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text8"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text9"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text10"), color: Colors.red, height: 50, width: 50,),
Container(child: Text("Text11"), color: Colors.red, height: 30, width: 120,),
],
),
)
);
}
}
效果如下:
七、FittedBox Widget
FittedBox
用于管理子 Widget 在其父级 Widget 如何对齐和缩放。构造方法如下:
const FittedBox({
Key key,
//BoxFit类型可选命名参数,如何适配子Widget
this.fit = BoxFit.contain,
//AlignmentGeometry类型可选命名参数,如何在父级范围内对齐子Widget,使用Alignment
this.alignment = Alignment.center,
//Widget类型可选命名参数,要显示的Widget
Widget child,
})
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
color: Colors.yellow,
height: 200,
width: 400,
child: FittedBox(
fit: BoxFit.fitHeight,
alignment: Alignment.topLeft,
child: Container(
height: 100,
width: 100,
color: Colors.red,
child: Image.network("http://www.mwpush.com/uploads/avatar.png"),
),
),
)
);
}
}
BoxFit
的设置和样式可以查看官网,地址为:https://api.flutter.dev/flutter/painting/BoxFit-class.html 。
上述代码效果如下:
八、OverflowBox Widget
通常,当一个子 Widget 在 父 Widget 中时,子 Widget 是受父 Widget 的约束限制的,超出父 Widget 的部分一般会被截掉。如果想子 Widget 不受父 Widget 的限制,可以使用 OverflowBox
,它是一个溢出 Widget ,可以使用与父 Widget 不同的约束。其构造方法如下:
const OverflowBox({
Key key,
//AlignmentGeometry类型可选命名参数,如何在父级范围内对齐子Widget,使用Alignment
this.alignment = Alignment.center,
//double类型可选命名参数,最小宽度,不设置(默认值),使用父级约束
this.minWidth,
//double类型可选命名参数,最大宽度,不设置(默认值),使用父级约束
this.maxWidth,
//double类型可选命名参数,最小高度,不设置(默认值),使用父级约束
this.minHeight,
//double类型可选命名参数,最大高度,不设置(默认值),使用父级约束
this.maxHeight,
//Widget类型可选命名参数,要显示的Widget
Widget child,
})
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
color: Colors.yellow,
height: 200,
width: 400,
child: OverflowBox( //OverflowBox
minHeight: 100,
maxHeight: 300,
minWidth: 100,
maxWidth: 300,
alignment: Alignment.topRight,
child: Container(
color: Colors.red,
),
),
)
);
}
}
效果如下:
九、Offstage Widget
在开发中,有时候需要根据需要来对某个 Widget 进行显示或隐藏,就可以使用 Offstage
。当 Offstage
设置为隐藏时,其不接收任何事件,也不占用父级的任何空间。但如果在 Offstage
中有动画执行,即便其为隐藏状态,动画都会在后台继续执行,会消耗电量和 CPU 。其构造方法如下:
const Offstage({
Key key,
//bool类型可选命名参数,是否隐藏子Widget
this.offstage = true,
//Widget类型可选命名参数,要显示或隐藏的Widget
Widget child
})
使用如下:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
color: Colors.yellow,
height: 200,
width: 400,
child: Offstage( //Offstage
offstage: false, //当设置为true时,在界面上不会显示Text
child: Text("Offstage"),
)
)
);
}
}