flutter 布局

本文最后更新于:2022年4月22日 上午

flutter 布局

布局

flutter 根部的容器大小为手机屏幕的大小,最外层无论怎么设置大小都无效,

runAPP(
  Container(
    width: 0.04
    height: 0.04,
    child: Center(
      child: Container(
        width: 200,
        height: 200,
        child: FlutterLogo(size: 9000)
      )
    ),
  );
)

如上

root 会先向下传递约束,Container 的约束为手机屏幕大小,假如为 414 * 896,那么宽高设置为 0.04就是无效的,会被flutter忽视

接着Container 会向下的 Center传递约束,Center的大小和Container 的大小是一致的

然后 Center 再向下的 Container 传递约束,这时候就有些不太一样了,向下的约束为 0 ≤ 宽 ≤ 414,0 ≤ 高 ≤ 896,表示只需要子容器可以在这个范围大小内

接着 Container 接收到 Center的约束后,告诉父级它的大小为200*200,则表示Center最大值和最小值都为200*200

最后 Container向下FlutterLogo传递约束,要求大小和Container是一致的200 * 200,所以FlutterLogo将大小设置为了9000是无效的,此时父级是不会接受这个大小的,会将FlutterLogo的大小设置为 200 * 200

Align

可以控制子元素的位置,值为 负1 - 正1,默认值为 0,0表示子元素居中

Align(
  alignment: Alignment(-1, 0);
)

查看布局约束 LayoutBuilder

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints){
    print(constraints); // 布局约束
    return FlutterLogo(size: 80);
  }
)

占满父级约束 SizeBox.expand

SizeBox.expand()

父级百分比约束 FractionallySizeBox

FractionallySizeBox(
  widthFactor: .5,
  heightFactor: .5,
  child: Container()
)

设置约束ConstrainedBox

ConstrainedBox可以重新设置子元素的最大和最小约束,但是这个范围也是在ConstrainedBox父级约束的区间内,不允许超过父级对ConstrainedBox的约束

return ConstrainedBox(
  constraints: BoxConstraints(
    minHeight: 60,
    minWidth: 60,
    maxHeight: double.infinity,
    maxWidth: double.infinity,
  ),
  child: FlutterLogo(size: 80),
);

松约束

.loosen()会将最小值60变成松约束,将其变成0CenterAlign其实也是松约束,Container则是紧约束

return ConstrainedBox(
  constraints: BoxConstraints(
    minHeight: 60,
    minWidth: 60,
    maxHeight: double.infinity,
    maxWidth: double.infinity,
  ).loosen(),
  child: FlutterLogo(size: 80),
);

紧约束

简单设置可以通过 SizebBox()直接设置宽高

BoxConstraints.tightFor()会创建一个最大值和最小值都相等的紧约束

return ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 120, height: 120),
  child: FlutterLogo(size: 80),
);

什么是松约束和紧约束

松约束是最小值为0的情况

紧约束是最小值和最大值都相等的情况

如果最大值和最小值都为0,则这个约束既是松约束,又是紧约束

阅读方向 Directionality

如果没有设置过的话,默认会从设备中读取阅读方向,一般是从左往右

Directionality(
  textDirection: TextDirection.rtl
)

层叠布局 Stack

Stack的子元素分为两类,一类是有 Positioned的,一类是没有的

如果Stack的子元素全部都是 Positioned,有定位的,那么 Stack的大小就是越大越好,会占满父元素的大小

如果Stack中全是没有Positioned,或者没有定位的和有定位的都有,那么Stack的大小就是 没有设置定位的最大元素的大小

Stack 约束

StackFit.passthrough直接将上级的约束传递给所有子元素

StackFit.loose会变成松约束,例如上级 minWidth: 20会将其最小约束变成0,使子元素最小宽度可以设置小于20的值

StackFit.expanded 设置为父级的最大约束,例如上级maxWidth: 300,那么会 将宽度300作为紧约束传递给子元素,让子元素必须为300

Stack 裁剪

如果有子元素超出了Stack的尺寸,那么默认会被裁切掉,如果不想裁切掉,可以设置 clipBehavior: Clip.none,也可以通过设置overflow来实现,只不过这个属性是旧版的属性

如果子元素超出了Stack,那么点击事件是没办法响应的

return ConstrainedBox(
  constraints: BoxConstraints(
    minHeight: 20,
    minWidth: 20,
    maxHeight: 300,
    maxWidth: 300,
  ),
  child: Stack(
    fit: StackFit.expanded, // 占满父级的约束 300
    children: [
      Container(width: 0, height: 80, color: Colors.blue),
    ]
  ),
);

Placeholder 占位元素

Placeholder(
  fallbackHeight: 200, // 占位高度
)

LimitedBox

当父级是宽高无限大的时候,这个元素才有效

CustomMultichildLayout 自定义布局

  • LayoutId() 给每个子元素设置id
  • layoutChild() 找到指定的子元素,并设置其约束
  • positionChild() 将指定的子元素设置位置
import 'package:flutter/material.dart';

class CustomLayoutTest extends StatelessWidget {
  const CustomLayoutTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('自定义布局')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: CustomMultiChildLayout(
          delegate: MyDalegate(),
          children: [
            LayoutId(id: '1', child: Container(color: Colors.blue[500], child: Text('1'))),
            LayoutId(id: '2', child: Container(color: Colors.blue[300], child: Text('2'))),
            LayoutId(id: '3', child: Container(color: Colors.blue[300], child: Text('3'))),
            LayoutId(id: '4', child: Container(color: Colors.blue[300], child: Text('4'))),
            LayoutId(id: '5', child: Container(color: Colors.blue[300], child: Text('5'))),
            LayoutId(id: '6', child: Container(color: Colors.blue[300], child: Text('6'))),
          ],
        ),
      ),
    );
  }
}

class MyDalegate extends MultiChildLayoutDelegate {
  // 布局
  @override
  void performLayout(Size size) {
    // 这个 size 是父元素的尺寸
    Size? size1, size2, size3, size4, size5;

    if (hasChild('1')) {
      size1 = layoutChild('1', BoxConstraints.tight(Size(70, 70)));
      positionChild('1', Offset(0, 0));
    }

    if (hasChild('2') && hasChild('3')) {
      size2 = layoutChild('2', BoxConstraints.tight(Size(30, 30)));
      layoutChild('3', BoxConstraints.tight(Size(30, 30)));
      positionChild('2', Offset(size1!.width + 10, 0));
      positionChild('3', Offset(size1.width + 10, size2.height + 10));
    }

    size4 = layoutChild('4', BoxConstraints.tight(Size(30, 30)));
    size5 = layoutChild('5', BoxConstraints.tight(Size(30, 30)));
    layoutChild('6', BoxConstraints.tight(Size(30, 30)));

    positionChild('4', Offset(0, size1!.height + 10));
    positionChild('5', Offset(size4.width + 10, size1.height + 10));
    positionChild('6', Offset(size4.width + size5.width + 20, size1.height + 10));
  }

  @override
  bool shouldRelayout(_) => true;
}

局限性

  1. 如果想让CustomMultichildLayout的大小紧紧包裹住子元素大小,是比较不好做的,需要手写一个 RenderObject
  2. 没有办法无中生有,例如想要加一个下划线,则必须需要传入一个下划线

总结

向下传递约束,向上传递尺寸,最终flutter知道每一个元素的尺寸后,再决定如何进行布局

flutter布局的时候一定要满足上级的约束,只有满足上级约束,尺寸才会生效,否则会被自耦东修正。

可以理解为给子元素设置大小只是一个建议大小,只有满足上级约束,这个建议才会被采纳


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。