好记性不如烂笔头,缘由看了小册就忘,本篇属于笔记整理加自我思考,详情请购买大佬小册 Flutter 布局探索 - 薪火相传
布局中的紧约束
有一个问题困扰我许久,在Flutter中使用Stack定位组件,使用Positioned 的左右定位属性设置为 0,使Positioned 的孩子居中,但是Positioned的child,会被强行拉伸填满左右,自身设置的宽高变的不起作用。每次我只需要套个Center组件就能使其动态居中,我想Center组件还蛮神奇在定位里也能实现居中。后来发现套个 UnconstrainedBox组件也可以实现居中,嗯???
Stack(children:[
Positioned(
left: 0,
right: 0,
child:UnconstrainedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.w),
color: cardColors[index],
),
padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 10.w),
child: Text("工作",style: const TextStyle(color: Colors.white,fontSize: 16),),
),
),
]);
紧约束
什么是紧约束?
紧约束(tight constraints)是指对Widget进行布局时,父Widget给子Widget施加的大小限制。每个Widget在布局时都会遵循紧约束原则,即Widget必须完全适应父Widget给予的约束条件。
常见的有关约束组件的包括:
-
BoxConstraints BoxConstraints定义了一个Widget可以渲染的最大和最小大小。每个Widget在布局时,Flutter会根据父Widget的BoxConstraints计算出子Widget的大小。如果没有指定任何约束,则Widget将尽可能变大。
-
ConstrainedBox ConstrainedBox是一个Widget,它可以给子Widget添加额外的约束条件。通过它可以限制子Widget的最大最小宽高。
-
UnconstrainedBox UnconstrainedBox的作用是暂时移除对子Widget施加的约束,让子Widget可以根据自身需求设置尺寸。
-
Align、 Center、Padding 这些Widget在布局时会改变子Widget的约束条件,使之居中对齐或添加间距等。
想要我的儿子听我话(约束),必须打破我父亲对我的紧约束,不然我的儿子只听我父亲的话(约束),不听我的。
约束对象 BoxConstraints
紧约束 本质是 BoxConstraints 类中的 tight 构造。通过 BoxConstraints 约束可以设置宽高的取值区间。
在布局时 BoxConstraints对象通常由父widget传递给子widget,用于控制子widget的大小和布局行为。子widget需要根据这些约束来调整自身尺寸,确保符合父widget的要求。
BoxConstraints是一个不可变的对象,用于表示父widget对子widget施加的约束条件。它主要包含以下属性:
-
maxWidth和maxHeight: 子widget的最大宽度和高度限制。
-
minWidth和minHeight: 子widget的最小宽度和高度限制。
-
hasBoundedWidth和hasBoundedHeight: 布尔值,表示是否有确定的最大宽度或高度约束。
-
isTight: 布尔值,如果所有约束都不为无限大(
double.infinity),则为true。 -
isNormalized: 布尔值,如果所有约束都在0到
double.infinity范围内,则为true。
BoxConstraints 类的不同构造:
-
BoxConstraints() 创建一个无约束的
BoxConstraints对象,即最小尺寸为 0,最大尺寸为无限大。BoxConstraints() -
BoxConstraints.tight(Size size) 创建一个紧致约束,其最小和最大尺寸都等于指定的
size。BoxConstraints.tight(Size(200.0, 100.0)) -
BoxConstraints.loose(Size size) 创建一个宽松约束,其最小尺寸为 0,最大尺寸为指定的
size。Copy code BoxConstraints.loose(Size(300.0, 150.0)) -
BoxConstraints.expand() 创建一个无限大约束,即最小尺寸为 0,最大尺寸为无限大。
BoxConstraints.expand() -
BoxConstraints(double minWidth, double maxWidth, double minHeight, double maxHeight) 使用指定的最小和最大宽高创建一个
BoxConstraints对象。BoxConstraints(minWidth: 100.0, maxWidth: 300.0, minHeight: 50.0, maxHeight: 200.0) -
BoxConstraints.tightFor(width: double, height: double) 创建一个紧致约束,其最小和最大宽高都等于指定的值。
BoxConstraints.tightFor(width: 150.0, height: 80.0) -
BoxConstraints.tightForFinite(width: double, height: double) 与
tightFor类似,但会确保最小和最大宽高都是有限的。BoxConstraints.tightForFinite(width: 200.0, height: 120.0)
LayoutBuilder 组件
LayoutBuilder 根据渲染时获取到的约束条件,获得到当前区域内的约束信息,动态地构建UI布局。
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// 根据constraints返回不同的布局
if (constraints.maxWidth > 600) {
return BigLayout();
} else {
return SmallLayout();
}
},
)
Flex(指 Row 和 Column 组件) 、Wrap、Stack 组件下的约束
Flex垂直布局和横向布局 ,Wrap流式布局,Stack定位布局
Flex
Flex 轴向为水平时,不会限制孩子的宽度,(如 Row组件)Flex 轴向为垂直时,不会限制孩子的高度。(如 Column)
属性:
-
direction: 主轴方向,取值为
Axis.horizontal或Axis.vertical。 -
mainAxisAlignment: 主轴方向上的对齐方式,例如
MainAxisAlignment.start,MainAxisAlignment.center等。 -
crossAxisAlignment: 交叉轴方向上的对齐方式,例如
CrossAxisAlignment.start,CrossAxisAlignment.center等。 -
verticalDirection: 当
direction为Axis.vertical时,子组件在交叉轴方向上的排列顺序。 -
textDirection: 水平方向的子组件排列顺序。
-
children: Flex 的子组件列表。
Warp
主要属性:
-
direction: 主轴方向,默认为Axis.horizontal,即水平排列。设置为Axis.vertical可以垂直排列。
-
alignment: 主轴方向上的对齐方式。
-
spacing: 主轴方向上相邻子组件之间的间距。
-
runAlignment: 交叉轴方向上每行子组件的对齐方式。
-
runSpacing: 交叉轴方向上相邻行之间的间距。
-
textDirection: 水平排列时,文本方向。
-
verticalDirection: 垂直排列时,每行中子组件的垂直顺序。
-
clipBehavior: 裁剪行为,默认根据需要裁剪。
Wrap 施加约束的特点: 在 [主轴] 方向上 [放松约束],在 [交叉轴] 方向上 [无限约束]。
Stack
-
alignment:子组件在Stack布局中的对齐方式,如中心对齐、左对齐等。
-
overflow: 定义子组件超出Stack布局范围时的处理方式,如裁剪或不受限制。
-
children: Stack布局的子组件列表,按顺序从底到顶叠加。所受到的约束都是一致的。
-
fit: Stack组件的大小如何适应其子组件的大小,有四种模式可选。
StackFit.loose
Stack的大小足够容纳所有子组件,并且没有额外的空间。这是默认值。
StackFit.expand 将尽可能扩大以填充父级提供的约束空间,同时仍然包含所有子组件。如果子组件超出了父级约束,则会裁剪子组件。
StackFit.passthrough
Stack的大小与父级提供的约束空间完全一致,忽略所有子组件的大小。这可能会导致子组件被裁剪。
StackFit.overflow
Stack的大小由最大的子组件决定。如果子组件比父级约束更大,则Stack也会超出父级提供的约束空间。
Flexible 组件
确保行或列内的子Widget按比例占用剩余空间。
主要属性:
-
fit: 控制子Widget如何适应Flexible本身的约束空间。 可选值
tight会尽量缩小至Widget以满足约束。loose会尽量扩大子Widget填充剩余空间。unbounded会完全忽略Flexible的约束,让子Widget自身决定大小。
-
flex: 一个int值,用于确定这个Flexible相对于其它Flexible的flex比例。值越大,占用剩余空间的比例就越大。
Expanded 组件
Expanded 组件的等价于 FlexFit.tight 时的 Flexible 组件, Spacer组件占据剩余空间,是通过封装的Expanded实现的
布局案例
思路 :简简单单使用figma 画个图,仿造一下掘金主页布局。马卡龙配色吼吼!!
代码实现
整体是一个垂直布局,中间部分为纵向布局。由于我想固定每个小模块的比例所以简单写了设计图的值转换,能够在窗口大小改变保证模块的比例不变。
class SizeUtils {
static double w(double w, double screenW) {
return (w * screenW) / meter.width;
}
static double h( h, double screenH) {
return (h * screenH) / meter.height;
}
}
布局代码
import 'package:flutter/material.dart';
import '../../utils/size_util.dart';
class LayoutPage extends StatefulWidget {
final BoxConstraints constraints;
const LayoutPage({Key? key, required this.constraints}) : super(key: key);
@override
State<LayoutPage> createState() => _LayoutPageState();
}
class _LayoutPageState extends State<LayoutPage> {
@override
Widget build(BuildContext context) {
double _width = widget.constraints.maxWidth;
double _height = widget.constraints.maxHeight;
return Container(
//由子填充
color: const Color(0xffB8FFCC),
child: Column(
children: [
Container(
width: double.infinity,
height: SizeUtils.h(93, _height),
color: const Color(0xff50EBCF),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: SizeUtils.w(304, _width),
height:SizeUtils.h(670, _height),
color: const Color(0xffFFDCEE),
),
SizedBox(width:SizeUtils.w(30, _width) ,),
SizedBox(
height: SizeUtils.h(800, _height),
width: SizeUtils.w(600, _width),
child: ListView.builder(
itemBuilder: (context, _) {
return Container(
margin: EdgeInsets.only(top: SizeUtils.h(20, _height)),
color: const Color(0xffffe7b6),
width: double.infinity,
height: SizeUtils.h(60, _height),
);
},
itemCount: 30,
),
),
SizedBox(width:SizeUtils.w(60, _width) ,),
Column(
children: [
Container(
height: SizeUtils.h(139, _height),
width: SizeUtils.w(349, _width),
color: const Color(0xffFFB8C5),
),
SizedBox(height:SizeUtils.h(50, _height) ,),
Container(
height: SizeUtils.h(213, _height),
width: SizeUtils.w(349, _width),
color: const Color(0xffD2D4FD),
),
SizedBox(height:SizeUtils.h(50, _height) ,),
Container(
height: SizeUtils.h(117, _height),
width: SizeUtils.w(349, _width),
color: const Color(0xffFAE396),
),
],
),
],
),
],
));
}
}