这里写目录标题
引言:
前些年里重学flutter的时候,整理了好多篇Flutter相关的文章,有些文章现在看来,太过基础了,整理得也比较简单。因为现在看着感觉太占整体文章数量了,想删掉算了。但是回头想想,这样浪费了当时整理的心意,索性将比较简单的一些删掉之后汇总成一篇出来吧。
约束(一)BoxConstraints
BoxConstraints 是盒模型布局过程中父渲染对象传递给子渲染对象的约束信息,包含最大宽高信息,子组件大小需要在约束的范围内,BoxConstraints 默认的构造函数如下:
const BoxConstraints({
this.minWidth = 0.0, //最小宽度
this.maxWidth = double.infinity, //最大宽度
this.minHeight = 0.0, //最小高度
this.maxHeight = double.infinity //最大高度
})
它包含 4 个属性,BoxConstraints还定义了一些便捷的构造函数,用于快速生成特定限制规则的BoxConstraints,如
- BoxConstraints.tight(Size size),它可以生成固定宽高的限制;
- BoxConstraints.expand()可以生成一个尽可能大的用以填充另一个容器的BoxConstraints。
- 除此之外还有一些其它的便捷函数。
父级组件是通过 BoxConstraints 来描述对子组件可用的空间范围。
约束(二)限制子组件大小的组件
ConstrainedBox
用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是80像素,你可以使用const BoxConstraints(minHeight: 80.0)作为子组件的约束。
box.dart 源码
class BoxConstraints extends Constraints {
/// Creates box constraints with the given constraints.
const BoxConstraints({
this.minWidth = 0.0,
this.maxWidth = double.infinity,
this.minHeight = 0.0,
this.maxHeight = double.infinity,
})
示例
//一个背景颜色为红色的盒子,不指定它的宽度和高度:
Widget redBox = DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
);
//ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity, //宽度尽可能大
minHeight: 50.0 //最小高度为50像素
),
child: Container(
height: 5.0,
child: redBox ,
),
)
效果:将Container的高度设置为5像素,但是最终却是50像素,这正是ConstrainedBox的最小高度限制生效了。
SizedBox
basic.dart源码
class SizedBox extends SingleChildRenderObjectWidget {
const SizedBox({ Key? key, this.width, this.height, Widget? child })
: super(key: key, child: child);
/// 创建一个长方体,该长方体将尽可能大。
const SizedBox.expand({ Key? key, Widget? child })
: width = double.infinity,
height = double.infinity,
super(key: key, child: child);
/// 创建一个将尽可能小的长方体。
const SizedBox.shrink({ Key? key, Widget? child })
: width = 0.0,
height = 0.0,
super(key: key, child: child);
/// Creates a box with the specified size.
SizedBox.fromSize({ Key? key, Widget? child, Size? size })
: width = size?.width,
height = size?.height,
super(key: key, child: child);
/// 创建具有指定大小的长方体。
const SizedBox.square({Key? key, Widget? child, double? dimension})
: width = dimension,
height = dimension,
super(key: key, child: child);
final double? width;
final double? height;
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.tightFor(width: 80.0,height: 80.0)等价于:
BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)
而实际上ConstrainedBox和SizedBox都是通过RenderConstrainedBox来渲染的,我们可以看到ConstrainedBox和SizedBox的
createRenderObject()方法都返回的是一个RenderConstrainedBox对象:
@override
RenderConstrainedBox createRenderObject(BuildContext context) {
return RenderConstrainedBox(
additionalConstraints: ...,
);
}
UnconstrainedBox
- UnconstrainedBox 的子组件将不再受到约束,大小完全取决于自己。
- 任何时候子组件都必须遵守其父组件的约束。
- 因此UnconstrainedBox依旧受父组件的约束。
其他的尺寸限制类容器
- AspectRatio,它可以指定子组件的长宽比
- LimitedBox 用于指定最大宽高
- FractionallySizedBox 可以根据父容器宽高的百分比来设置子组件宽高等。
线性布局
- Flutter 中通过Row和Column来实现线性布局,类似于Android 中的LinearLayout控件。Row和Column都继承自Flex。
- 在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。
Row 水平布局
定义如下:
Row({
...
//表示水平方向子组件的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)。
TextDirection textDirection,
//表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子 widgets
//实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,
MainAxisSize mainAxisSize = MainAxisSize.max,
//表示子组件在Row所占用的水平空间内对齐方式,如果mainAxisSize值为MainAxisSize.min,则此属性无意义,因为子组件的宽度等于Row的宽度。
//只有当mainAxisSize的值为MainAxisSize.max时,此属性才有意义,MainAxisAlignment.start表示沿textDirection的初始方向对齐,
//如textDirection取值为TextDirection.ltr时,则MainAxisAlignment.start表示左对齐,textDirection取值为TextDirection.rtl时表示从右对齐。
//而MainAxisAlignment.end和MainAxisAlignment.start正好相反;MainAxisAlignment.center表示居中对齐。
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
//表示Row纵轴(垂直)的对齐方向,默认是VerticalDirection.down,表示从上到下。
VerticalDirection verticalDirection = VerticalDirection.down,
//表示子组件在纵轴方向的对齐方式,Row的高度等于子组件中最高的子元素高度,它的取值和MainAxisAlignment一样(包含start、end、 center三个值),
//不同的是crossAxisAlignment的参考系是verticalDirection,即verticalDirection值为VerticalDirection.down时crossAxisAlignment.start指顶部对齐,
//verticalDirection值为VerticalDirection.up时,crossAxisAlignment.start指底部对齐;而crossAxisAlignment.end和crossAxisAlignment.start正好相反;
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
//子组件数组。
List<Widget> children = const <Widget>[],
})
Column 垂直布局
和Row参数基本一样,不赘述。
流式布局 Wrap
basic.dart
Wrap({
Key? key,
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0, //spacing:主轴方向子widget的间距
this.runAlignment = WrapAlignment.start, //runAlignment:纵轴方向的对齐方式
this.runSpacing = 0.0, //runSpacing:纵轴方向的间距
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.clipBehavior = Clip.none,
List<Widget> children = const <Widget>[],
})
还有 Flow 控件也是做流式布局的
弹性布局 Flex
弹性布局允许子组件按照一定比例来分配父容器空间。
Flex组件
可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row或Column会方便一些,因为Row和Column都继承自Flex,参数基本相同,
所以能使用Flex的地方基本上都可以使用Row或Column。Flex本身功能是很强大的,它也可以和Expanded组件配合实现弹性布局。
Flex({
...
required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
List<Widget> children = const <Widget>[],
})
Flex继承自MultiChildRenderObjectWidget,对应的RenderObject为RenderFlex,RenderFlex中实现了其布局算法。
Expanded
Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex子组件所占用的空间。因为 Row和Column 都继承自 Flex,所以 Expanded 也可以作为它们的孩子。
const Expanded({
int flex = 1,
required Widget child,
})
flex参数为弹性系数,如果为 0 或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其 flex 的比例来分割主轴的全部空闲空间。
eg:
Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
层叠布局 Stack
Android 中的 Frame 布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。层叠布局允许子组件按照代码中声明的顺序堆叠起来。
Flutter中使用Stack和Positioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。
Stack 的 basic.dart源码
class Stack extends MultiChildRenderObjectWidget {
/// Creates a stack layout widget.
///
/// By default, the non-positioned children of the stack are aligned by their
/// top left corners.
Stack({
Key? key,
//此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件。所谓部分定位,
//在这里特指没有在某一个轴上定位:left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位。
this.alignment = AlignmentDirectional.topStart, //子Widgets们的对齐方式 eg:const FractionalOffset(0.5, 0.8),
//和Row、Wrap的textDirection功能一样,都用于确定alignment对齐的参考系,即:textDirection的值为TextDirection.ltr,
//则alignment的start代表左,end代表右,即从左往右的顺序;textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左,即从右往左的顺序。
this.textDirection, //文本的方向,默认当然是 left-to-right
//参数用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。
this.fit = StackFit.loose, //fit 子Widgets的放置方式,默认loose
this.overflow = Overflow.clip, //被弃用了,用下面的clipBehavior去替换
this.clipBehavior = Clip.hardEdge, //子Widgets溢出的处理方式。Clip.hardEdge 表示直接剪裁,不应用抗锯齿
List<Widget> children = const <Widget>[],
})
Stack 的 Children 可以是 Positioned,然后具体的子widget由参数child来包装
Positioned 的 basic.dart源码
const Positioned({
Key? key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
required Widget child,
})
- bottom: 距离层叠组件下边的距离
- left:距离层叠组件左边的距离
- top:距离层叠组件上边的距离
- right:距离层叠组件右边的距离
- width: 层叠定位组件的宽度
- height: 层叠定位组件的高度
ps: 注意,Positioned的width、height 和其它地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位组。
举个例子,在水平方向时,你只能指定left、right、width三个属性中的两个,如指定left和width后,right会自动算出(left+width),如果同时指定三个属性则会报错,垂直方向同理。
相对布局 Align
basic.dart 源码
class Align extends SingleChildRenderObjectWidget {
const Align({
Key? key,
//alignment : 需要一个AlignmentGeometry类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry 是一个抽象类,它有两个常用的子类:Alignment和 FractionalOffset
this.alignment = Alignment.center, //设置对齐方向
//widthFactor和heightFactor是用于确定Align 组件本身宽高的属性;它们是两个缩放因子(倍数),会分别乘以子元素的宽、高,
//最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。
this.widthFactor,
this.heightFactor,
Widget? child,
})
Center组件
Center组件定义如下:
class Center extends Align {
const Center({ Key? key, double widthFactor, double heightFactor, Widget? child })
: super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
可以看到Center继承自Align,它比Align只少了一个alignment 参数;由于Align的构造函数中alignment 值为Alignment.center,
所以,我们可以认为Center组件其实是对齐方式确定(Alignment.center)了的Align。
Alignment
Alignment继承自AlignmentGeometry,表示矩形内的一个点,他有两个属性x、y,分别表示在水平和垂直方向的偏移,Alignment定义如下:
Alignment(this.x, this.y)
- Alignment Widget会以矩形的中心点作为坐标原点,即Alignment(0.0, 0.0) 。
- x、y的值从-1到1分别代表矩形左边到右边的距离和顶部到底边的距离,因此2个水平(或垂直)单位则等于矩形的宽(或高),
- 如Alignment(-1.0, -1.0) 代表矩形的左侧顶点,
- 而Alignment(1.0, 1.0)代表右侧底部终点,
- 而Alignment(1.0, -1.0) 则正是右侧顶点,即Alignment.topRight。
- 为了使用方便,矩形的原点、四个顶点,以及四条边的终点在Alignment类中都已经定义为了静态常量。
Alignment可以通过其坐标转换公式将其坐标转为子元素的具体偏移坐标:
(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)
其中childWidth为子元素的宽度,childHeight为子元素高度。
FractionalOffset
FractionalOffset 继承自 Alignment,它和 Alignment唯一的区别就是坐标原点不同:FractionalOffset 的坐标原点为矩形的左侧顶点,
这和布局系统的一致,所以理解起来会比较容易。FractionalOffset的坐标转换公式为:
实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)
卡片布局 Card
卡片式布局默认是撑满整个外部容器的,如果你想设置卡片的宽高,需要在外部容器就进行制定。
card.dart 源码
class Card extends StatelessWidget {
/// Creates a material design card.
///
/// The [elevation] must be null or non-negative. The [borderOnForeground]
/// must not be null.
const Card({
Key? key,
this.color,
this.shadowColor,
this.elevation,
this.shape,
this.borderOnForeground = true,
this.margin,
this.clipBehavior,
this.child,
this.semanticContainer = true,
})