界面布局主要分为两大类:布局类组件和定位装饰权重组件,咱们布局的时候基本都是相互嵌套的git
如下是这个 UI 的 widget 树形图:github
咱们这里讲由浅及深的聊一下Flutter布局问题markdown
Container是flutter中普遍使用的容器类组件less
构造函数ide
Container({
this.alignment,
this.padding, //容器内补白,属于decoration的装饰范围
Color color, // 背景色
Decoration decoration, // 背景装饰
Decoration foregroundDecoration, //前景装饰
double width,//容器的宽度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制条件
this.margin,//容器外补白,不属于decoration的装饰范围
this.transform, //变换
this.child,
})
复制代码
属性函数
Container自身尺寸的调节分两种状况:oop
Container(
child: Text(
"Hello Flutter", // 文字内容
style: TextStyle(fontSize: 20.0,color: Colors.amber), // 字体样式 字体大小
),
alignment: Alignment.topLeft, // 字内容的对齐方式 center居中,centerLeft居中左侧 centerRight居中右侧
// bottomCenter 下居中对齐 ,bottomLeft 下左对齐,bottomRight 下右对齐
// topCenter 上居中对齐,topLeft 上左对齐,topRight 上右对齐
width: 200, // 宽
height: 200, // 高
color: Colors.red, //颜色 color和decoration不能够同时存在
padding: const EdgeInsets.fromLTRB(20.0,20.0,20.0,20.0), // 边距 all 包括上下左右 fromLTRB 上下左右分别设置边距fromLTRB(20.0,20.0,20.0,20.0)
margin: const EdgeInsets.all(30.0), // 外间距
);
复制代码
decoration的属性很强大,能够支持背景图线性或者径向的渐变,边框,圆角,阴影等属性布局
Flutter的Decoration能够设置:背景色 背景图 边框 圆角 阴影 渐变色 的等属性,Decoration 是基类,它的子类有下面这些字体
const BoxDecoration({
this.color,//背景色
this.image,//图片
this.border,//描边
this.borderRadius,//圆角大小
this.boxShadow,//阴影
this.gradient,//渐变色
this.backgroundBlendMode,//图像混合模式
this.shape = BoxShape.rectangle,//形状,BoxShape.circle和borderRadius不能同时使用
})
复制代码
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 50.0, left: 120.0), //容器外填充
constraints: BoxConstraints.tightFor(width: 200.0, height: 150.0), //卡片大小
decoration: BoxDecoration(//背景装饰
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
)
]
),
transform: Matrix4.rotationZ(.2), //卡片倾斜变换
alignment: Alignment.center, //卡片内文字居中
child: Text( //卡片文字
"Flutter Demo",
style: TextStyle(color: Colors.white, fontSize: 40.0),
),
);
}
}
复制代码
SizedBox: 两种用法:一是可用来设置两个widget之间的间距,二是能够用来限制子组件的大小。flex
Column(
children: <Widget>[
SizedBox(height: 30,),
SizedBox(width: 200,height: 200,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
SizedBox(height: 30,),
SizedBox(width: 100,height: 100,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
)
],
);
复制代码
能够看到,代码中共有4个SizedBox组件,两个是设置间距的功能,两个是具备设置约束的功能
所谓线性布局,即指沿水平或垂直方向排布子组件。Flutter中经过Row和Column来实现线性布局。Row和Column都继承自Flex,咱们将在弹性布局一节中详细介绍Flex。
项目中 90% 的页面布局均可以经过 Row 和 Column 来实现。
Row(
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 50,
width: 100,
color: Colors.green,
),
Container(
height: 50,
width: 100,
color: Colors.blue,
),
],
);
复制代码
Column(
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 50,
width: 100,
color: Colors.green,
),
Container(
height: 50,
width: 100,
color: Colors.blue,
),
],
)
复制代码
在 Row 和 Column 中有一个很是重要的概念:主轴( MainAxis ) 和 交叉轴( CrossAxis ),主轴就是与组件布局方向一致的轴,交叉轴就是与主轴方向垂直的轴。
具体到 Row 组件,主轴 是水平方向,交叉轴 是垂直方向。而 Column 与 Row 正好相反,主轴 是 垂直方向,交叉轴 是水平方向。
明白了 主轴 和 交叉轴 概念,咱们来看下 mainAxisAlignment 属性,此属性表示主轴方向的对齐方式,默认值为 start,表示从组件的开始处布局,此处的开始位置和 textDirection 属性有关,textDirection 表示文本的布局方向,其值包括 ltr(从左到右) 和 rtl(从右到左),当 textDirection = ltr 时,start 表示左侧,当 textDirection = rtl 时,start 表示右侧,
spaceAround 和 spaceEvenly 区别是:
Container(
decoration: BoxDecoration(border: Border.all(color: Colors.black)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 100,
width: 100,
color: Colors.green,
),
Container(
height: 150,
width: 100,
color: Colors.blue,
),
],
),
)
复制代码
Row与Column是继承自Flex的,Flex的大部分功能都在上一个线性布局中介绍过了。咱们这里在补充几个知识点。
Flexible 组件能够控制 Row、Column、Flex 的子控件占满父组件,好比,Row 中有3个子组件,两边的宽是100,中间的占满剩余的空间
Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
)
复制代码
仍是有3个子组件,第一个占1/6,第二个占2/6,第三个占3/6,代码以下
Column(
children: <Widget>[
Flexible(
flex: 1,
child: Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('1 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
Flexible(
flex: 2,
child: Container(
color: Colors.red,
alignment: Alignment.center,
child: Text('2 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
Flexible(
flex: 3,
child: Container(
color: Colors.green,
alignment: Alignment.center,
child: Text('3 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
],
)
复制代码
子组件占比 = 当前子控件 flex / 全部子组件 flex 之和。 Flexible中 fit 参数表示填满剩余空间的方式,说明以下:
这2个看上去不是很好理解啊,什么叫尽量大的填满剩余空间?何时填满,看下面的例子
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
child: Text('Container',style: TextStyle(color: Colors.white),),
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
);
}
}
复制代码
这段代码是在最上面代码的基础上给中间的红色Container添加了Text子控件,此时红色Container就不在充满空间,再给Container添加对齐方式,代码以下:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
alignment: Alignment.center,
child: Text('Container',style: TextStyle(color: Colors.white),),
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
);
}
}
复制代码
class Expanded extends Flexible {
/// Creates a widget that expands a child of a [Row], [Column], or [Flex]
/// so that the child fills the available space along the flex widget's
/// main axis.
const Expanded({
Key key,
int flex = 1,
@required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
复制代码
Expanded 继承字 Flexible,fit 参数固定为 FlexFit.tight,也就是说 Expanded 必须(强制)填满剩余空间
Spacer 也是一个权重组件,源代码以下:
@override
Widget build(BuildContext context) {
return Expanded(
flex: flex,
child: const SizedBox.shrink(),
);
}
复制代码
Spacer 的本质也是 Expanded 的实现的,和Expanded的区别是:Expanded 能够设置子控件,而 Spacer 的子控件尺寸是0,所以Spacer适用于撑开 Row、Column、Flex 的子控件的空隙,用法以下
Row(
children: <Widget>[
Container(width: 100,height: 50,color: Colors.green,),
Spacer(flex: 2,),
Container(width: 100,height: 50,color: Colors.blue,),
Spacer(),
Container(width: 100,height: 50,color: Colors.red,),
],
)
复制代码
三个权重组建总结以下:
Wrap 为子组件进行水平或者垂直方向布局,且当空间用完时,Wrap 会自动换行,也就是流式布局
Wrap(
children: List.generate(10, (i) {
double w = 50.0 + 10 * i;
return Container(
color: Colors.primaries[i],
height: 50,
width: w,
child: Text('$i'),
);
}),
)
复制代码
咱们查看Wrap源码,咱们发现了一个runAlignment属性,感受和alignment好像同样。
runAlignment 属性控制 Wrap 的交叉抽方向上每一行的对齐方式,下面直接看 runAlignment 6中方式对应的效果图
runAlignment 和 alignment 的区别:
spacing 和 runSpacing 属性控制Wrap主轴方向和交叉轴方向子控件之间的间隙
Wrap(
spacing: 30,
runSpacing: 10,
children: List.generate(10, (i) {
double w = 50.0 + 10 * i;
return Container(
color: Colors.primaries[i],
height: 50,
width: w,
alignment: Alignment.center,
child: Text('$i'),
);
}),
)
复制代码
叠加布局组件包含 Stack 和 IndexedStack,Stack 组件将子组件叠加显示,根据子组件的顺利依次向上叠加
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
复制代码
Stack(
children: <Widget>[
Container(
height: 300,
width: 300,
color: Colors.red,
),
Container(
height: 200,
width: 200,
color: Colors.blue,
),
Container(
height: 100,
width: 100,
color: Colors.yellow,
)
],
);
复制代码
IndexedStack 是 Stack 的子类,Stack 是将全部的子组件叠加显示,而 IndexedStack 经过 index 只显示指定索引的子组件,用法以下:
class IndexedStackDemo extends StatefulWidget {
@override
_IndexedStackDemoState createState() => _IndexedStackDemoState();
}
class _IndexedStackDemoState extends State<IndexedStackDemo> {
int _index = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(height: 50,),
_buildIndexedStack(),
SizedBox(height: 30,),
_buildRow(),
],
);
}
_buildIndexedStack() {
return IndexedStack(
index: _index,
children: <Widget>[
Center(
child: Container(
height: 300,
width: 300,
color: Colors.red,
alignment: Alignment.center,
child: Text('1'),
),
),
Center(
child: Container(
height: 300,
width: 300,
color: Colors.green,
alignment: Alignment.center,
child: Text('2'),
),
),
Center(
child: Container(
height: 300,
width: 300,
color: Colors.yellow,
alignment: Alignment.center,
child: Text('3'),
),
),
],
);
}
_buildRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('1'),
onPressed: (){
setState(() {
_index = 0;
});
},
),
RaisedButton(
child: Text('2'),
onPressed: (){
setState(() {
_index = 1;
});
},
),
RaisedButton(
child: Text('3'),
onPressed: (){
setState(() {
_index = 2;
});
},
),
],
);
}
}
复制代码
AspectRatio 是固定宽高比的组件
Container(
height: 300,
width: 300,
color: Colors.blue,
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: 2 / 1,
child: Container(color: Colors.red,),
),
)
复制代码
FittedBox 组件主要作两件事,缩放(Scale)和位置调整(Position)。
FittedBox 会在本身的尺寸范围内缩放并调整 child 的位置,使 child 适合其尺寸。FittedBox 和 Android 中的 ImageView 有些相似,将图片在其范围内按照规则进行缩放和位置调整。
布局分为两种状况:
const FittedBox({
Key key,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.clipBehavior = Clip.hardEdge,
Widget child,
})
复制代码
这里有一个新的属性fit
Container(
color: Colors.amberAccent,
width: 300.0,
height: 300.0,
child: FittedBox(
fit: BoxFit.none,
alignment: Alignment.topLeft,
child: new Container(
color: Colors.red,
child: new Text("FittedBox"),
)
),
);
复制代码
FractionallySizedBox 是一个相对父组件尺寸的组件,用途是基于宽度缩放因子和高度缩放因子来调整布局大小,大小可能超过父组件位置。
const FractionallySizedBox({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
复制代码
1.widthFactor:FractionallySizedBox组件的宽度因子
2.heightFractor: FractionallySizedBox组件的高度因子
Container(
height: 200,
width: 200,
color: Colors.blue,
child: FractionallySizedBox(
widthFactor: .8,
heightFactor: .3,
child: Container(
color: Colors.red,
),
),
)
复制代码
ContainedBox是一种有约束限制的布局,在其约定的范围内,好比最大高度,最小宽度,其子组件是不能逾越的
ConstrainedBox({
Key key,
@required this.constraints,
Widget child,
})
复制代码
constraints:添加到child上的额外限制条件,其类型为BoxConstraints。BoxConstraints的做用是干啥的呢?其实很简单,就是限制各类最大最小宽高。说到这里插一句,double.infinity在widget布局的时候是合法的,也就说,例如想最大的扩展宽度,能够将宽度值设为double.infinity。
这个案例来自:flutter.cn/docs/develo…
咱们准备作一下这个界面
具体代码以下
class demoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 300,
child: Card(
color: Colors.white,
margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
topWidget(),
starWidget(),
iconWidget()],
),
)
);
}
}
class topWidget extends StatelessWidget {
final mainImage = Container(
margin: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.asset(
'images/pavlova.jpg',
fit: BoxFit.fill,
width: 130,
height: 130,
),
);
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
mainImage,
Flexible(
child: rightWidget()
),
],
);
}
}
class rightWidget extends StatelessWidget {
final titleText = Container(
padding: EdgeInsets.fromLTRB(10, 15, 0, 0),
child: Text(
'Strawberry Pavlova',
style: TextStyle(
letterSpacing: 0.5,
fontSize: 17,
),
),
);
final subTitle = Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Text(
'Pavlova is a meringue-based dessert named after the Russian ballerina '
'Anna Pavlova. Pavlova features a crisp crust and soft, light inside, '
'topped with fruit and whipped cream.',
style: TextStyle(
fontSize: 12,
),
),
);
@override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [titleText,subTitle],
),
);
}
}
class starWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Row(
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
],
),
);
}
}
class iconWidget extends StatelessWidget {
final descTextStyle = TextStyle(
color: Colors.black,
fontSize: 18,
height: 1.2,
);
@override
Widget build(BuildContext context) {
return Container(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
Text('PREP:',style: descTextStyle),
Text('25 min',style: descTextStyle),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
Text('COOK:',style: descTextStyle),
Text('1 hr',style: descTextStyle),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
Text('FEEDS:',style: descTextStyle),
Text('4-6',style: descTextStyle),
],
),
],
),
),
);
}
}
复制代码