Flutter了解之入门篇8(容器类组件)

目录

  1. Container容器
  2. DecoratedBox装饰容器
  3. Padding内边距留白
  4. 尺寸限制类容器
    ConstrainedBox       给子组件设置最大最小宽高约束
    SizedBox             给子组件设置固定的宽高
    UnconstrainedBox
    AspectRatio          指定子组件的长宽比
    LimitedBox           指定最大宽高
    FractionallySizedBox 根据父容器宽高的百分比来设置子组件宽高等
  5. FittedBox(自定义适配规则)

Flutter官方并没有对Widget进行官方分类,对其分类主要是为了对Widget进行功能区分。

盒子模型

容器类和布局类Widget的区别

相同点:
  1. 都作用于其子Widget

不同点:
  1. 布局类Widget直接或间接继承MultiChildRenderObjectWidget(拥有一个children属性用于接收多个子Widget)。容器类Widget直接或间接继承自(或包含)SingleChildRenderObjectWidget(拥有一个child属性)。
  2. 布局类Widget是按照一定的布局方式来对其子Widget进行布局。容器类Widget是对其子Widget进行了包装修饰(补白、旋转或剪裁、大小限制)。

1. Container容器

对子组件进行装饰、变换、大小限制。
类似于html的div。

// Container本身不对应具体的RenderObject,是由DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合起来的。
Container({
  this.alignment,  // Alignment.left
  this.padding, // 内边距,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, // 前景装饰
  double width, // 宽,设置为double.infinity则等于父容器宽
  double height, // 高
  BoxConstraints constraints, // 容器大小的限制条件
  this.margin,// 外边距,不属于decoration的装饰范围
  this.transform, // 变换
  this.child,
})
注意:
    1. 容器的大小可以通过width、height属性来指定,也可以通过constraints来指定;如果它们同时存在时,width、height优先。本质上Container内部会根据width、height来生成一个constraints。
    2. color和decoration是互斥的,如果同时设置它们则会报错。本质上,当指定color时,Container内会自动创建一个decoration。

示例1

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( // 文本
    "5.20", style: TextStyle(color: Colors.white, fontSize: 40.0),
  ),
);
运行结果

示例2(Padding和Margin)

Container(
  margin: EdgeInsets.all(20.0), 
  color: Colors.orange,
  child: Text("Hello world!"),
),
Container(
  padding: EdgeInsets.all(20.0), 
  color: Colors.orange,
  child: Text("Hello world!"),
),
====================
Container内margin和padding都是通过Padding 组件来实现的,上面的示例代码实际上等价于:
Padding(
  padding: EdgeInsets.all(20.0),
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.orange),
    child: Text("Hello world!"),
  ),
),
DecoratedBox(
  decoration: BoxDecoration(color: Colors.orange),
  child: Padding(
    padding: const EdgeInsets.all(20.0),
    child: Text("Hello world!"),
  ),
),
运行结果

变换(Transform)

Matrix4是一个4D矩阵,通过它可以实现各种矩阵操作.

Transform的变换是应用在绘制阶段,而并不是应用在布局阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的。
由于矩阵变化只会作用在绘制阶段,所以在某些场景下,在UI需要变化时,可以直接通过矩阵变化来达到视觉上的UI改变,而不需要去重新触发build流程,这样会节省layout的开销,所以性能会比较好。如Flow组件,它内部就是用矩阵变换来更新UI,除此之外,Flutter的动画组件中也大量使用了Transform以提高性能。

Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight, // 相对于坐标系原点的对齐方式
    transform: new Matrix4.skewY(0.3), // 沿Y轴倾斜0.3弧度
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.deepOrange,
      child: const Text('Apartment for rent!'),
    ),
  ),
);
运行结果

 Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    DecoratedBox(
      decoration:BoxDecoration(color: Colors.red),
      child: Transform.scale(scale: 1.5,
          child: Text("Hello world") // 缩放
      )
    ),
    Text("你好", style: TextStyle(color: Colors.green, fontSize: 18.0),)
  ],
)
由于第一个Text应用变换(放大)后,其在绘制时会放大,但其占用的空间依然为红色部分,所以第二个Text会紧挨着红色部分,最终就会出现文字重合
运行结果
  1. 平移
Transform.translate接收一个offset参数,可以在绘制时沿x、y轴对子组件平移指定的距离。

例
DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  // 默认原点为左上角,左移20像素,向上平移5像素  
  child: Transform.translate(
    offset: Offset(-20.0, -5.0),
    child: Text("Hello world"),
  ),
)
运行结果
  1. 旋转
Transform.rotate可以对子组件进行旋转变换
要使用math.pi需先进行导包:import 'dart:math' as math;

例
DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.rotate(
    //旋转90度
    angle:math.pi/2 ,
    child: Text("Hello world"),
  ),
);
运行结果
  1. 缩放
Transform.scale可以对子组件进行缩小或放大

例
DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.scale(
      scale: 1.5, //放大到1.5倍
      child: Text("Hello world")
  )
);
运行结果

RotatedBox

RotatedBox和Transform.rotate功能相似,它们都可以对子组件进行旋转变换,但是有一点不同:RotatedBox的变换是在layout阶段,会影响在子组件的位置和大小。

例
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    DecoratedBox(
      decoration: BoxDecoration(color: Colors.red),
      //将Transform.rotate换成RotatedBox  
      child: RotatedBox(
        quarterTurns: 1, //旋转90度(1/4圈)
        child: Text("Hello world"),
      ),
    ),
    Text("你好", style: TextStyle(color: Colors.green, fontSize: 18.0),)
  ],
),
由于RotatedBox是作用于layout阶段,所以子组件会旋转90度(而不只是绘制的内容),decoration会作用到子组件所占用的实际空间上。
运行结果

2. DecoratedBox装饰容器

修饰子组件(背景、边框、渐变等)

DecoratedBox({
  // BoxDecoration类(继承自Decoration装饰抽象类)的主要职责是通过实现createBoxPainter方法来创建一个画笔,该画笔用于绘制装饰。
  Decoration decoration,
  /*
  在哪绘制Decoration(DecorationPosition枚举类型)
    1. background:在子组件之后绘制,即背景装饰。
    2. foreground:在子组件之上绘制,即前景装饰。
  */
  DecorationPosition position = DecorationPosition.background,
  Widget child
})

  BoxDecoration({
    Color color,   // 背景色
    DecorationImage image,  // 背景图片,DecorationImage(image: fit:)
    BoxBorder border, // 边框,BoxBorder.all(color: width:)
    BorderRadiusGeometry borderRadius, // 圆角,BorderRadius.circular(宽/2)切圆,BorderRadius.all(Radius.circular(宽/2))切圆
    List<BoxShadow> boxShadow, // 阴影,可以指定多个
    Gradient gradient, // 渐变,LinearGradient类用于定义线性渐变的类,还有其它渐变配置类:RadialGradient、SweepGradient
    BlendMode backgroundBlendMode, // 背景混合模式
    BoxShape shape = BoxShape.rectangle, // 背景形状,默认是矩形。
  })

/*
例(线性渐变)
          LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Color(0xFF56AF6D),
              Color(0xFF56AA6D),
            ]
          ),

*/
/*
  DecorationImage({
    required this.image,
    this.onError,
    this.colorFilter,
    this.fit,
    this.alignment = Alignment.center,
    this.centerSlice,
    this.repeat = ImageRepeat.noRepeat,
    this.matchTextDirection = false,
    this.scale = 1.0
  }) 
  Border({  // 继承自BoxBorder
    this.top = BorderSide.none,
    this.right = BorderSide.none,
    this.bottom = BorderSide.none,
    this.left = BorderSide.none,
  }) 
  BorderSide({
    this.color = const Color(0xFF000000),
    this.width = 1.0,
    this.style = BorderStyle.solid,
  })
  RadialGradient({  // 继承自Gradient
    this.center = Alignment.center,
    this.radius = 0.5,
    required List<Color> colors,
    List<double>? stops,
    this.tileMode = TileMode.clamp,
    this.focal,
    this.focalRadius = 0.0,
    GradientTransform? transform,
  })
  LinearGradient({  // 继承自Gradient。
    this.begin = Alignment.centerLeft,
    this.end = Alignment.centerRight,
    required List<Color> colors,
    List<double>? stops,
    this.tileMode = TileMode.clamp,
    GradientTransform? transform,
  })
*/

示例

 DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), // 背景渐变。渐变配置类:LinearGradient、RadialGradient、SweepGradient
      borderRadius: BorderRadius.circular(3.0), // 3像素圆角
      boxShadow: [ // 阴影
        BoxShadow(
            color:Colors.black54,  // 阴影色
            offset: Offset(2.0,2.0),  // x轴偏移,y轴偏移
            blurRadius: 4.0  // 模糊程度,值越大越明显
        )
      ]
    ),
  child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
    child: Text("Login", style: TextStyle(color: Colors.white),),
  )
)
运行结果

3. Padding 内边距留白

内边距留白。

Padding({
  EdgeInsetsGeometry padding,
  Widget child,
})

说明:
1. EdgeInsets类(继承自EdgeInsetsGeometry抽象类)的便捷方法:
    1. fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
    2. all(double value) : 所有方向均使用相同数值的填充。
    3. only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
    4. symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right。

示例

class PaddingTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      //上下左右各添加16像素补白
      padding: EdgeInsets.all(16.0),
      child: Column(
        // 显式指定对齐方式为左对齐,排除对齐干扰
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Padding(
            // 左边添加8像素补白
            padding: const EdgeInsets.only(left: 8.0),
            child: Text("Hello world"),
          ),
          Padding(
            // 上下各添加8像素补白
            padding: const EdgeInsets.symmetric(vertical: 8.0),
            child: Text("I am Jack"),
          ),
          Padding(
            // 分别指定四个方向的补白
            padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0),
            child: Text("Your friend"),
          )
        ],
      ),
    );
  }
}
运行结果

4. 尺寸限制类容器

用于限制容器大小

1. ConstrainedBox
  给子组件设置最大最小宽高约束
2. SizedBox
  给子组件设置固定的宽高
3. UnconstrainedBox
4. AspectRatio  
  指定子组件的长宽比
5. LimitedBox 
  指定最大宽高
6. FractionallySizedBox 
  根据父容器的宽高百分比来设置子组件宽高
Flutter中有两种布局模型:
  1. 基于RenderBox的盒模型布局。
    1. 组件对应的渲染对象都继承自 RenderBox 类。
    2. 在布局过程中父级传递给子级的约束信息由 BoxConstraints 描述。
  2. 基于RenderSliver(Sliver)按需加载列表布局。

两种布局方式在细节上略有差异,但大体流程相同,布局流程如下:
    1. 上层组件向下层组件传递约束条件。
    2. 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
    3. 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。

例:
父组件传递给子组件的约束是“最大宽高不能超过100,最小宽高为0”,如果给子组件设置宽高都为200,则子组件最终的大小是100*100。
因为任何时候子组件都必须先遵守父组件的约束,在此基础上再应用子组件约束(相当于父组件的约束和自身的大小求一个交集)。
  1. ConstrainedBox

给子组件设置最大最小宽高约束(BoxConstraints约束)。

BoxConstraints({
  this.minWidth = 0.0, // 最小宽度
  this.maxWidth = double.infinity, // 最大宽度
  this.minHeight = 0.0, // 最小高度
  this.maxHeight = double.infinity // 最大高度
})
盒模型布局过程中父渲染对象传递给子渲染对象的约束信息(包含最大最小宽高),子组件大小需要在约束的范围内。

BoxConstraints的便捷构造函数:
  1. BoxConstraints.tight(Size size) 生成给定大小的BoxConstraints。
  2. BoxConstraints.expand() 生成尽可能占用另一个容器的BoxConstraints。

示例1(让子组件的最小高度是80像素)

使用BoxConstraints(minHeight: 80.0)作为子组件的约束。

示例2

定义一个redBox,它是一个背景颜色为红色的盒子,不指定它的宽度和高度
Widget redBox=DecoratedBox(
  decoration: BoxDecoration(color: Colors.red),
);
实现一个最小高度为50,宽度尽可能大的红色容器
ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, // 宽度尽可能大
    minHeight: 50.0 // 最小高度为50像素
  ),
  child: Container(
      height: 5.0, 
      child: redBox 
  ),
)
虽然将Container的高度设置为5像素,但是最终却是50像素,这正是ConstrainedBox的最小高度限制生效了。
运行结果
  1. SizedBox

给子组件设置固定的宽高

ConstrainedBox和SizedBox的createRenderObject方法都返回一个用来渲染的RenderConstrainedBox对象:
  @override
  RenderConstrainedBox createRenderObject(BuildContext context) {
    return new RenderConstrainedBox(
      additionalConstraints: ...,
    );
  }

示例

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限制)

对于minWidth和minHeight,取父子中相应数值较大的。只有这样才能保证父限制与子限制不冲突。

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    )
)
最终显示效果是宽90,高60,也就是说是子ConstrainedBox的minWidth生效,而minHeight是父ConstrainedBox生效。

将上例中父子限制条件换一下
ConstrainedBox(
    constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0),
      child: redBox,
    )
)
最终的显示效果仍然是90,高60,效果相同,但意义不同,因为此时minWidth生效的是父ConstrainedBox,而minHeight是子ConstrainedBox生效。
  1. UnconstrainedBox

UnconstrainedBox不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。
很少直接使用此组件,经常用于"去除"多重限制。

在实际开发中,当使用SizedBox或ConstrainedBox给子元素指定了宽高,但是仍然没有效果时,几乎可以断定:已经有父元素已经设置了限制。

UnconstrainedBox对父组件限制的“去除”并非真正去除,仍然占有相应的空间。

没有办法可以彻底去除父ConstrainedBox的限制。任何时候子组件都必须遵守其父组件的约束。在定义一个通用的组件时,如果要对子组件指定限制,那么一定要注意,因为一旦指定限制条件,子组件如果要进行相关自定义大小时将可能非常困难,因为子组件在不更改父组件的代码的情况下无法彻底去除其限制条件。

需要注意,UnconstrainedBox虽然在其子组件布局时可以取消约束(子组件可以为无限大),但是 UnconstrainedBox 自身是受其父组件约束的,所以当 UnconstrainedBox 随着其子组件变大后,如果UnconstrainedBox 的大小超过它父组件约束时,也会导致溢出报错。

示例

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  // 父
    child: UnconstrainedBox( // “去除”父级限制
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),// 子
        child: redBox,
      ),
    )
)
上面代码中,如果没有中间的UnconstrainedBox,那么根据上面所述的多重限制规则,那么最终将显示一个90×100的红色框。但是由于UnconstrainedBox “去除”了父ConstrainedBox的限制,则最终会按照子ConstrainedBox的限制来绘制redBox,即90×20。

UnconstrainedBox对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×20,但上方仍然有80的空白空间。也就是说父限制的minHeight(100.0)仍然是生效的,只不过它不影响最终子元素redBox的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制。
/*
Column(
  children: <Widget>[
    UnconstrainedBox(
      alignment: Alignment.topLeft,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(children: [Text('xx' * 30)]),
      ),
    ),
 ]
),
*/
运行结果

示例

Material组件库中的AppBar(导航栏)的右侧菜单中,使用SizedBox指定了loading按钮的大小,代码如下:
 AppBar(
   title: Text(title),
   actions: <Widget>[
         SizedBox(
             width: 20, 
             height: 20,
             child: CircularProgressIndicator(
                 strokeWidth: 3,
                 valueColor: AlwaysStoppedAnimation(Colors.white70),
             ),
         )
   ],
)
右侧loading按钮大小并没有发生变化!这正是因为AppBar中已经指定了actions按钮的限制条件,所以要自定义loading按钮大小,就必须通过UnconstrainedBox来“去除”父元素的限制,代码如下:
AppBar(
  title: Text(title),
  actions: <Widget>[
      UnconstrainedBox(
            child: SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(
                strokeWidth: 3,
                valueColor: AlwaysStoppedAnimation(Colors.white70),
              ),
          ),
      )
  ],
)
未使用UnconstrainedBox

使用UnconstrainedBox

AspectRatio 指定子组件的长宽比

LimitedBox 指定最大宽高

FractionallySizedBox 根据父容器的宽高百分比来设置子组件宽高

5. FittedBox(自定义适配规则)

子组件大小超出父组件大小时,会显示溢出警告并在控制台打印错误日志。

示例(溢出)

Padding(
  padding: const EdgeInsets.symmetric(vertical: 30.0),
  child: Row(children: [Text('xx'*30)]), //文本长度超出 Row 的最大宽度会溢出
)

根据Flutter布局协议,适配算法在容器或布局组件的layout中实现。
FittedBox组件(快速自定义适配规则)。

FittedBox({
  Key? key,
  this.fit = BoxFit.contain, // 适配方式
  this.alignment = Alignment.center, // 对齐方式
  this.clipBehavior = Clip.none, // 是否剪裁
  Widget? child,
})

适配原理
    1. FittedBox在布局子组件时会忽略其父组件传递的约束,可以允许子组件无限大。
        FittedBox传递给子组件的约束为(0<=width<=double.infinity, 0<= height <=double.infinity)。
    2. FittedBox对子组件布局结束后就可以获得子组件真实的大小。
    3. FittedBox知道子组件的真实大小也知道他父组件的约束,那么FittedBox 就可以通过指定的适配方式(BoxFit枚举中指定),让其子组件在FittedBox父组件的约束范围内按照指定的方式显示。

示例(BoxFit.none、BoxFit.contain)

Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: [
        wContainer(BoxFit.none),
        Text('Wendux'),
        wContainer(BoxFit.contain),
        Text('Flutter中国'),
      ],
    ),
  );
}
Widget wContainer(BoxFit boxFit) {
  return Container(
    width: 50,
    height: 50,
    color: Colors.red,
    child: FittedBox(
      fit: boxFit,
      // 子容器超过父容器大小
      child: Container(width: 60, height: 70, color: Colors.blue),
    ),
  );
}

因为父Container要比子Container 小,所以当没有指定任何适配方式时,子组件会按照其真实大小进行绘制,所以第一个蓝色区域会超出父组件的空间,因而看不到红色区域。第二个指定了适配方式为 BoxFit.contain,含义是按照子组件的比例缩放,尽可能多的占据父组件空间,因为子组件的长宽并不相同,所以按照比例缩放适配父组件后,父组件能显示一部分。

要注意一点,在未指定适配方式时,虽然 FittedBox 子组件的大小超过了 FittedBox 父 Container 的空间,但FittedBox 自身还是要遵守其父组件传递的约束,所以最终 FittedBox 的本身的大小是 50×50,这也是为什么蓝色会和下面文本重叠的原因,因为在布局空间内,父Container只占50×50的大小,接下来文本会紧挨着Container进行布局,而此时Container 中有子组件的大小超过了自己,所以最终的效果就是绘制范围超出了Container,但布局位置是正常的,所以就重叠了。如果我们不想让蓝色超出父组件布局范围,那么可以可以使用 ClipRect 对超出的部分剪裁掉即可:

 ClipRect( // 将超出子组件布局范围的绘制内容剪裁掉
  child: Container(
    width: 50,
    height: 50,
    color: Colors.red,
    child: FittedBox(
      fit: boxFit,
      child: Container(width: 60, height: 70, color: Colors.blue),
    ),
  ),
);

示例2(单行缩放布局)

有三个数据指标,需要在一行显示,因为换行的话就会将页面布局打乱,所以换行是不能接受的。因为不同设备的屏幕宽度不同,且不同人的数据也不同,所以就会出现数据太长或屏幕太窄时三个数据无法在一行显示,因此,希望当无法在一行显示时能够对组件进行适当的缩放以确保一行能够显示的下:

 @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children:  [
          wRow(' 90000000000000000 '),  // 溢出
          FittedBox(child: wRow(' 90000000000000000 ')),  // 按比例缩放
          wRow(' 800 '), // BoxConstraints(0.0<=w<=396.0, 0.0<=h<=Infinity)
          FittedBox(child: wRow(' 800 ')),  // 会挤在一起,FittedBox传给Row的约束的 maxWidth为无限大(double.infinity)BoxConstraints(unconstrained)
            ]
        .map((e) => Padding(
              padding: EdgeInsets.symmetric(vertical: 20),
              child: e,
            ))
        .toList();,
      ),
    );
  }
  Widget wRow(String text) {
    Widget child = Text(text);
    child = Row(
      // spaceEvenly:Row在进行布局时会拿到父组件的约束,如果约束的 maxWidth 不是无限大,则 Row 会根据子组件的数量和它们的大小在主轴方向来根据 spaceEvenly 填充算法来分割水平方向的长度,最终Row 的宽度为 maxWidth;但如果 maxWidth 为无限大时,就无法在进行分割了,会将子组件的宽度之和作为自己的宽度。
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,  // 均分
      children: [child, child, child],
    );
    return child;
  }

封装一个SingleLineFittedBox来替换FittedBox以达到预期效果(避免挤在一起):
class SingleLineFittedBox extends StatelessWidget {
 const SingleLineFittedBox({Key? key,this.child}) : super(key: key);
 final Widget? child;
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (_, constraints) {
        return FittedBox(
          child: ConstrainedBox(
            constraints: constraints.copyWith(
              // maxWidth: constraints.maxWidth
              minWidth: constraints.maxWidth, // 不会出现缩在一起的情况
              maxWidth: double.infinity,  // 无限大,处理超出屏幕的情况
            ),
            child: child,
          ),
        );
      },
    );
  }
}
将上例中FittedBox改为SingleLineFittedBox。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335