Flutter LayoutBuilder、AfterLayout

365 阅读2分钟

1.1 LayoutBuilder

通过 LayoutBuilder,可以在布局过程中拿到父组件传递的约束信息,然后可以根据约束信息动态的构建不同的布局。

比如实现一个响应式的 Column 组件 ResponsiveColumn,它的功能是当当前可用的宽度小于 200 时,将子组件显示为一列,否则显示为两列。简单来实现一下:

class ResponsiveColumn extends StatelessWidget {
  const ResponsiveColumn({Key? key, required this.children}) : super(key: key);

  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    // 通过 LayoutBuilder 拿到父组件传递的约束,然后判断 maxWidth 是否小于200
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        if (constraints.maxWidth < 200) {
          // 最大宽度小于200,显示单列
          return Column(children: children, mainAxisSize: MainAxisSize.min);
        } else {
          // 大于200,显示双列
          var _children = <Widget>[];
          for (var i = 0; i < children.length; i += 2) {
            if (i + 1 < children.length) {
              _children.add(Row(
                children: [children[i], children[i + 1]],
                mainAxisSize: MainAxisSize.min,
              ));
            } else {
              _children.add(children[i]);
            }
          }
          return Column(children: _children, mainAxisSize: MainAxisSize.min);
        }
      },
    );
  }
}


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

  @override
  Widget build(BuildContext context) {
    var _children = List.filled(6, Text("A"));
    // Column在本示例中在水平方向的最大宽度为屏幕的宽度
    return Column(
      children: [
        // 限制宽度为190,小于 200
        SizedBox(width: 190, child: ResponsiveColumn(children: _children)),
        ResponsiveColumn(children: _children),
        LayoutLogPrint(child:Text("xx")) // 下面介绍
      ],
    );
  }
}

可以发现 LayoutBuilder 的使用很简单,但是不要小看它,因为它非常实用且重要,它主要有两个使用场景:

  1. 可以使用 LayoutBuilder 来根据设备的尺寸来实现响应式布局。
  2. LayoutBuilder 可以帮我们高效排查问题。比如我们在遇到布局问题或者想调试组件树中某一个节点布局的约束时 LayoutBuilder 就很有用。

1.2 AfterLayout

1.1 获取组件大小和相对于屏幕的坐标

它可以在子组件布局完成后执行一个回调,并同时将 RenderObject 对象作为参数传递。

AfterLayout(
  callback: (RenderAfterLayout ral) {
    print(ral.size); //子组件的大小
    print(ral.offset);// 子组件在屏幕中坐标
  },
  child: Text('flutter@wendux'),
),

1.2 获取组件相对于某个父组件的坐标

RenderAfterLayout 类继承自 RenderBox,RenderBox 有一个 localToGlobal 方法,它可以将坐标转化为相对与指定的祖先节点的坐标,比如下面代码可以打印出 Text('A') 在 父 Container 中的坐标

Builder(builder: (context) {
  return Container(
    color: Colors.grey.shade200,
    alignment: Alignment.center,
    width: 100,
    height: 100,
    child: AfterLayout(
      callback: (RenderAfterLayout ral) {
        Offset offset = ral.localToGlobal(
          Offset.zero,
          // 传一个父级元素
          ancestor: context.findRenderObject(),
        );
        print('A 在 Container 中占用的空间范围为:${offset & ral.size}');
      },
      child: Text('A'),
    ),
  );
}),