刚开端触摸flutter的时分,Container组件是用得最多的。它就像HTML中的div一样遍及,专门用来布局页面的。

可是运用Container嵌套布局的时分,经常出现一些令人无法理解的问题。就如下面代码,在一个固定的容器中,子组件却铺满了全屏。

/// 例一
@override
Widget build(BuildContext context) {
   return Container(
     width: 300,
     height: 300,
     color: Colors.amber,
     child: Container(width: 50, height: 50, color: Colors.red,),
  );
}
深入flutter布局约束原理

然后要加上alignment特点,子组件正常显现了,但容器仍是铺满全屏。

/// 例二
@override
Widget build(BuildContext context) {
   return Container(
     width: 300,
     height: 300,
     color: Colors.amber,
     alignment: Alignment.center,
     child: Container(width: 50, height: 50, color: Colors.red,),
  );
}
深入flutter布局约束原理

而在容器外层添加一个Scaffold组件,它就正常显现了。

/// 例三
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      width: 300,
      height: 300,
      color: Colors.amber,
      alignment: Alignment.center,
      child: Container(width: 50, height: 50, color: Colors.red,),
    ),
  );
}
深入flutter布局约束原理

这一切的奇怪行为困扰了我很久,直到我深入了flutter布局的学习,才渐渐解开这些疑惑。

1、flutter的widget类型

flutter的widget能够分为三类,组合类ComponentWidget署理类ProxyWidget制作类RenderObjectWidget

深入flutter布局约束原理
组合类:如ContainerScaffoldMaterialApp还有一系列经过承继StatelessWidgetStatefulWidget的类。组合类是咱们开发过程中用得最多的组件。

署理类InheritedWidget,功用型组件,它能够高效快捷的完成同享数据的跨组件传递。如常见的ThemeMediaQuery便是InheritedWidget的运用。

制作类:屏幕上看到的UI简直都会经过RenderObjectWidget完成。经过承继它,能够进行界面的布局和制作。如AlignPaddingConstrainedBox等都是经过承继RenderObjectWidget,并经过重写createRenderObject办法来创建RenderObject目标,完成终究的布局(layout)和制作(paint)。

2、Container是个组合类

清楚明了Container承继StatelessWidget,它是一个组合类,同时也是一个由DecoratedBoxConstrainedBoxTransformPaddingAlign等组件组合的多功用容器。能够经过检查Container类,看出它实际便是经过不同的参数判别,再进行组件的层层嵌套来完成的。

@override
Widget build(BuildContext context) {
  Widget? current = child;
  if (child == null && (constraints == null || !constraints!.isTight)) {
    current = LimitedBox(
      maxWidth: 0.0,
      maxHeight: 0.0,
      child: ConstrainedBox(constraints: const BoxConstraints.expand()),
    );
  } else if (alignment != null) {
    current = Align(alignment: alignment!, child: current);
  }
  final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
  if (effectivePadding != null) {
    current = Padding(padding: effectivePadding, child: current);
  }
  if (color != null) {
    current = ColoredBox(color: color!, child: current);
  }
  if (clipBehavior != Clip.none) {
    assert(decoration != null);
    current = ClipPath(
      clipper: _DecorationClipper(
        textDirection: Directionality.maybeOf(context),
        decoration: decoration!,
      ),
      clipBehavior: clipBehavior,
      child: current,
    );
  }
  if (decoration != null) {
    current = DecoratedBox(decoration: decoration!, child: current);
  }
  if (foregroundDecoration != null) {
    current = DecoratedBox(
      decoration: foregroundDecoration!,
      position: DecorationPosition.foreground,
      child: current,
    );
  }
  if (constraints != null) {
    current = ConstrainedBox(constraints: constraints!, child: current);
  }
  if (margin != null) {
    current = Padding(padding: margin!, child: current);
  }
  if (transform != null) {
    current = Transform(transform: transform!, alignment: transformAlignment, child: current);
  }
  return current!;
}

组合类根本不参加ui的制作,都是经过制作类的组合来完成功用。

3、flutter布局束缚

flutter中有两种布局束缚BoxConstraints盒束缚和SliverConstraints线性束缚,如Align、Padding、ConstrainedBox运用的是盒束缚。

BoxConstraints盒束缚是指flutter结构在运行时遍历整个组件树,在这过程中 「向下传递束缚,向上传递尺度」,以此来确认每个组件的尺度和巨细。

深入flutter布局约束原理

BoxConstraints类由4个特点组成,最小宽度minWidth、最大宽度maxWidth、最小高度minHeight、最大高度maxHeight

BoxConstraints({
    this.minWidth,
    this.maxWidth,
    this.minHeight,
    this.maxHeight,
});

依据这4个特点的变化,能够分为“紧束缚(tight)”、“松束缚(loose)”、“无界束缚”、“有界束缚”。

紧束缚:最小宽(高)度和最大宽(高)度值相等,此刻它是一个固定宽高的束缚。

BoxConstraints.tight(Size size)
    : minWidth = size.width,
      maxWidth = size.width,
      minHeight = size.height,
      maxHeight = size.height;

松束缚:最小宽(高)值为0,最大宽(高)大于0,此刻它是一个束缚规模。

BoxConstraints.loose(Size size)
    : minWidth = 0.0,
      maxWidth = size.width,
      minHeight = 0.0,
      maxHeight = size.height;

无界束缚:最小宽(高)和最大宽(高)值存在double.infinity(无限)。

BoxConstraints.expand({double? width, double? height})
: minWidth = width ?? double.infinity,
     maxWidth = width ?? double.infinity,
     minHeight = height ?? double.infinity,
     maxHeight = height ?? double.infinity;

有界束缚:最小宽(高)和最大宽(高)值均为固定值。

BoxConstraints(100, 300, 100, 300)

4、Container布局行为解惑

了解了BoxConstraints布局束缚,回到本文最开端的问题。

/// 例一
@override
Widget build(BuildContext context) {
   return Container(
     width: 300,
     height: 300,
     color: Colors.amber,
     child: Container(width: 50, height: 50, color: Colors.red,),
  );
}

例一中,两个固定宽高的Container,为什么子容器铺满了全屏?

依据BoxConstraints布局束缚,遍历整个组件树,最开端的root是树的起点,它向下传递的是一个紧束缚。由于是移动设备,root即是屏幕的巨细,假设屏幕宽414、高896。于是整个布局束缚如下:

深入flutter布局约束原理

这儿有个问题,便是Container分明现已设置了固定宽高,为什么无效?

由于父级向下传递的束缚,子组件必须严格遵守。这儿Container容器设置的宽高超出了父级的束缚规模,就会主动被疏忽,采用契合束缚的值。

例一两上Container都被铺满屏幕,而最底下的赤色Container叠到了最上层,所以终究显现赤色。

/// 例二
@override
Widget build(BuildContext context) {
   return Container(
     width: 300,
     height: 300,
     color: Colors.amber,
     alignment: Alignment.center,
     child: Container(width: 50, height: 50, color: Colors.red,),
  );
}

例二也同样能够依据布局束缚求证,如下图:

深入flutter布局约束原理

这儿Container为什么是ConstrainedBoxAlign组件?前面说过Container是一个组合组件,它是由多个原子组件组成的。依据例二,它是由ConstrainedBox和Align嵌套而成。

Align供给给子组件的是一个松束缚,所以容器本身设置50宽高值是在合理规模的,因此生效,屏幕上显现的便是50像素的赤色方块。ConstrainedBox遭到的是紧束缚,所以本身的300宽高被疏忽,显现的是铺满屏幕的黄色块。

/// 例三
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      width: 300,
      height: 300,
      color: Colors.amber,
      alignment: Alignment.center,
      child: Container(width: 50, height: 50, color: Colors.red,),
    ),
  );
}

例三中Scaffold向下传递的是一个松束缚,所以黄色Container的宽高依据本身设置的300,在合理的规模内,有效。Container再向下传递的也是松束缚,终究赤色Container宽高为50。

深入flutter布局约束原理

这儿还有个问题,怎样确认组件向下传递的是紧束缚仍是松束缚?

这就涉及到组件的内部完成了,这儿经过Align举个例。

Align是一个制作组件,它能够进行界面的布局和制作,这是由于Align的承继链为:

Align -> SingleChildRenderObjectWidget -> RenderObjectWidget

Align需求重写createRenderObject办法,回来RenderObject的完成,这儿Align回来的是RenderPositionedBox,所以核心内容就在这个类中

class Align extends SingleChildRenderObjectWidget {
    /// ...
    @override
    RenderPositionedBox createRenderObject(BuildContext context) {
      return RenderPositionedBox(
        alignment: alignment,
        widthFactor: widthFactor,
        heightFactor: heightFactor,
        textDirection: Directionality.maybeOf(context),
      );
    }
    /// ...
}

而RenderPositionedBox类中,重写performLayout办法,该办法用于依据本身束缚条件,计算出子组件的布局,再依据子组件的尺度设置本身的尺度,构成一个至下而上,由上到下的闭环,终究完成界面的整个制作。

RenderPositionedBox -> RenderAligningShiftedBox -> RenderShiftedBox -> RenderBox

class RenderPositionedBox extends RenderAligningShiftedBox {
    /// ...
    @override
    void performLayout() {
      final BoxConstraints constraints = this.constraints; // 本身的束缚巨细
      final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
      final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
      /// 存在子组件
      if (child != null) {
        /// 开端布局子组件
        child!.layout(constraints.loosen(), parentUsesSize: true);
        /// 依据子组件的尺度设置本身尺度
        size = constraints.constrain(Size(
          shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
          shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
        ));
        /// 计算子组件的位置
        alignChild();
      } else {
        /// 不存在子组件
        size = constraints.constrain(Size(
          shrinkWrapWidth ? 0.0 : double.infinity,
          shrinkWrapHeight ? 0.0 : double.infinity,
        ));
      }
    }
    /// ...
}

依据Align中performLayout办法的完成,能够确认该组件终究会给子组件传递一个怎样样的束缚。

/// constraints.loosen供给的是一个松束缚
child!.layout(constraints.loosen(), parentUsesSize: true);
/// loosen办法
BoxConstraints loosen() {
  assert(debugAssertIsValid());
  /// BoxConstraints({double minWidth = 0.0, double maxWidth = double.infinity, double minHeight = 0.0, double maxHeight = double.infinity})
  return BoxConstraints(
    maxWidth: maxWidth,
    maxHeight: maxHeight,
  );
}

其它制作类的组件根本跟Align大同小异,只需重点看performLayout办法的完成,即可判别出组件供给的束缚条件。

总结

1、flutter的widget分为,组合类、署理类和制作类。

2、Container是一个组合类,由DecoratedBox、ConstrainedBox、Transform、Padding、Align等制作组件组合而成。

3、flutter中有两种布局束缚BoxConstraints盒束缚和SliverConstraints线性束缚。

4、BoxConstraints的束缚原理是:「向下传递束缚,向上传递尺度」

5、BoxConstraints的束缚类型为:紧束缚、松束缚、无界束缚、有界束缚。

6、判别一个制作组件的束缚行为能够经过检查performLayout办法中layout传入的束缚值。

最后,既然都看到这儿了,就请您高抬贵手点个赞呗~。您的鼓舞,是我创造的最大动力!