Flutter的盒子约束

  • 2020 年 3 月 11 日
  • 筆記

由Expanded widget引发的思考

设计稿如下

flutter效果图

布局widget分解

flutter效果图

很常见的一种布局方式:Column的子widget中包含ListView

  @override    Widget build(BuildContext context) {      return Column(        children: <Widget>[          ListView.builder(            itemCount: 30,            itemBuilder: (BuildContext context, int index) {              return ListTile(                title: Text("标题$index"),              );            },          ),        ],      );    }

当然这样仅仅这样写,在手机屏幕上会看到一片空白,如果是debug模式,则会在控制台中看到如下报错

════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: RenderRepaintBoundary#e7255 relayoutBoundary=up2 NEEDS-PAINT
‘package:flutter/src/rendering/box.dart’:
Failed assertion: line 1687 pos 12: ‘hasSize’
The relevant error-causing widget was
Column

从报错信息可以得知是Column widget出错了,RenderBox未正确布局等等。

正确的写法

@override    Widget build(BuildContext context) {      return Column(        children: <Widget>[          Expanded(            child: ListView.builder(              itemCount: 30,              itemBuilder: (BuildContext context, int index) {                return ListTile(                  title: Text("标题$index"),                );              },            ),          ),        ],      );    }

在ListView外层嵌套一个Expanded widget

Expanded widget作用是给子 widget 分配剩余的空间,也就是只要给ListView指定高度即可。当然也就可以在ListView外层嵌套一个限定高度的Container widget。

Flutter盒子

In Flutter, widgets are rendered by their underlying RenderBox objects. Render boxes are given constraints by their parent, and size themselves within those constraints. Constraints consist of minimum and maximum widths and heights; sizes consist of a specific width and height.
Generally, there are three kinds of boxes, in terms of how they handle their constraints:

  • Those that try to be as big as possible. For example, the boxes used by Center and ListView.
  • Those that try to be the same size as their children. For example, the boxes used by Transform and Opacity
  • Those that try to be a particular size. For example, the boxes used by Image and Text.

这是Flutter官网关于flutter 盒子约束的一段话。在Flutter中,widget由其底层的RenderBox对象渲染。 渲染框由它们的父级给出约束,并且在这些约束下调整自身大小。约束由最小宽高、最大宽高组成(这里是我个人看法,Flutter中文网翻译是,约束由最小宽度、最大宽度和高度组成 ); 尺寸由特定的宽度和高度组成。

按照宽高约束条件来划分,flutter 盒子有三类:

  • 无限制边界(在某一个方向,比如纵向滚动那么宽度就是受限制的而高度无边界,并不是宽高都任意大),例如Center和ListView widget
  • 由子widget的宽高决定了自己的边界,如 Transform 和 Opacity
  • 有确定的宽高大小,如 Image和Text widget

而下面这句话就刚好解释了上面那个报错

A box that tries to be as big as possible won’t function usefully when given an unbounded constraint and, in debug mode, such a combination throws an exception that points to this file.

The most common cases where a render box finds itself with unbounded constraints are within flex boxes (Row and Column), and within scrollable regions (ListView and other ScrollView subclasses).

上面那个例子中,Column在纵向上面是无限制的,而ListView在其滚动方向上也是无限制。而上面这句话也解释了,一个本身试图占用尽可能大的渲染盒在给定无边界约束时不会有用,在debug模式下,会抛出异常

所以当我们给ListView外层嵌套了Expanded或者限高的Container,避免了一个不限制高度的widget直接被嵌套于另外一个不限高的widget中,在VSCode终端直接运行flutter run 控制台是没有任何打印信息,只有在debug模式下,控制台会打印异常。

总结

开发中使用debug模式,不仅可以断点调试,更能在控制台看到异常信息。技术都有一定的共通性,虽然flutter一切基于widget,但是在盒子的宽高方面也如同CSS ,可以继承自父类,也可以自定义宽高,或者由子类宽高撑起来。

参考文档

https://flutter.dev/docs/development/ui/layout/box-constraints
https://flutterchina.club/layout/