Flutter笔记汇总:布局控件篇

BoxConstraints是Flutter中描述父组件对子组件尺寸约束的重要概念,它包含最小和最大宽度及高度。子组件的大小必须在这些约束内。BoxConstraints提供了一些便利构造函数,如BoxConstraints.tight用于创建固定尺寸约束,BoxConstraints.expand则用于创建能填充父容器的约束。了解并正确使用BoxConstraints对于优化Flutter应用的布局至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:

前些年里重学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,
  })
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值