Flutter 组件 | CustomSingleChildLayout 通用单子布局
一、认识 CustomSingleChildLayout 组件
1. CustomSingleChildLayout 组件介绍
它可容纳一个子组件,并指定代理类对子组件进行 排布
。可以说它是最灵活的单子布局,我们通过代理类可以获取 父区域的信息
和 限制子组件的区域
。一些单子布局的 疑难杂症
都可以通过 CustomSingleChildLayout
药到病除。

2. 测试初始代码
如下,这里父容器使用 300*200的灰色盒子
,我们将在这个区域对 子组件进行排布
。

1import 'package:flutter/material.dart';
2
3void main() => runApp(MyApp());
4
5class MyApp extends StatelessWidget {
6 @override
7 Widget build(BuildContext context) {
8 return MaterialApp(
9 title: 'Flutter Demo',
10 theme: ThemeData(
11 primarySwatch: Colors.blue,
12 ),
13 home: Scaffold(
14 appBar: AppBar(),
15 body: Center(child: CustomSingleChildLayoutDemo()),
16 ));
17 }
18}
19
20class CustomSingleChildLayoutDemo extends StatelessWidget {
21 @override
22 Widget build(BuildContext context) {
23 return Container(
24 width: 300,
25 height: 200,
26 alignment: Alignment.center,
27 color: Colors.grey.withAlpha(11),
28 );
29 }
30}
3. CustomSingleChildLayout 简单使用
CustomSingleChildLayout
组件可以说是 上有老,下有小
,一方面可以获取父区域的限制,另一方面需要排布子组件的位置。它可以设置一个 child,还需要设置一个抽象代理类 SingleChildLayoutDelegate
,
Flutter 框架中并 没有提供
可用的实现类,所以只能自定义 _TolySingleChildLayoutDelegate
。
1class CustomSingleChildLayoutDemo extends StatelessWidget {
2 @override
3 Widget build(BuildContext context) {
4 return Container(
5 width: 300,
6 height: 200,
7 alignment: Alignment.center,
8 color: Colors.grey.withAlpha(11),
9 child: CustomSingleChildLayout(
10 delegate: _TolySingleChildLayoutDelegate(),
11 child: Icon(Icons.android_rounded),
12 ),
13 );
14 }
15}
SingleChildLayoutDelegate
抽象类中需要实现 shouldRelayout
决定是否需要重新布局。这和画板中的 shouldRePaint
方法是类似的作用。效果如下,子组件默认摆放在区域左上角。

1class _TolySingleChildLayoutDelegate extends SingleChildLayoutDelegate {
2 @override
3 bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
4 return false;
5 }
6}
二、SingleChildLayoutDelegate 获取布局信息
除了抽象方法外,还有三个可以复写的方法,通过这些方法我们可以获取一些重要的布局信息,来帮助我们对子组件进行排布。

1.获取父级约束区域
可以重写 getConstraintsForChild
方法,获取父区域的约束情况。如果需要对子组件区域进行重新约束,也可以返回新的约束。

1---->[_TolySingleChildLayoutDelegate]----
2@override
3BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
4 print('----getConstraintsForChild:----constraints:$constraints----');
5 return constraints;
6}
2.设置 CustomSingleChildLayout 尺寸
可以重写 getSize
方法设置 CustomSingleChildLayout 的尺寸,从源码中可以看出,默认 super.getSize(constraints)
是父区域约束的最大区域。如下,将 Size
设置为 Size(150,100)
,可以看出 CustomSingleChildLayout 本身的区域尺寸发生了变化。

1---->[_TolySingleChildLayoutDelegate]----
2@override
3Size getSize(BoxConstraints constraints) {
4 Size size = super.getSize(constraints);
5 print('----getSize:----constraints:$constraints---$size-');
6 return Size(150,100);
7}
3.设置子组件的偏移
getPositionForChild
是使用时最为重要的一个方法,我们可以在这个方法中对子组件的位置进行偏移。这个方法中,可以获取 CustomSingleChildLayout
的尺寸和 子组件的尺寸,默认处理是零偏移。

1---->[_TolySingleChildLayoutDelegate]----
2@override
3Offset getPositionForChild(Size size, Size childSize) {
4 print('----size:$size----childSize:$childSize----');
5 return super.getPositionForChild(size, childSize);
6}
如下,可以很简单地通过尺寸计算,让子组件的中心位于 CustomSingleChildLayout
区域的左上角。

1---->[_TolySingleChildLayoutDelegate]----
2@override
3Offset getPositionForChild(Size size, Size childSize) {
4 return Offset(-childSize.width/2, -childSize.height/2);
5}
三、CustomSingleChildLayout能干嘛?
从上面可以看出,使用 CustomSingleChildLayout
可以获取布局信息,并可以对子组件进行布局。一句话来说 用于排布一个组件。
这样我们可以简单封装一个用于偏移的组件。
1. 控制偏移的代理类
传入一个 Offset对象
控制子组件的偏移。
1class _OffSetDelegate extends SingleChildLayoutDelegate {
2 final Offset offset;
3
4 _OffSetDelegate({this.offset = Offset.zero});
5
6 @override
7 bool shouldRelayout(_OffSetDelegate oldDelegate) =>
8 offset != oldDelegate.offset;
9
10 @override
11 Offset getPositionForChild(Size size, Size childSize) {
12 return offset;
13 }
14}
2. 封装类 OffSetWidget
为了方便使用,封装 OffSetWidget组件
来实现子组件相对于父组件的偏移。
1class OffSetWidget extends StatelessWidget {
2 final Offset offset;
3 final Widget child;
4
5 OffSetWidget({this.offset = Offset.zero, this.child});
6
7 @override
8 Widget build(BuildContext context) {
9 return CustomSingleChildLayout(
10 delegate: _OffSetDelegate(offset: offset),
11 child: child,
12 );
13 }
14}
3. OffSetWidget 使用
这样就可以让子组件在父组件中发生相对偏移。
简约派代表: "等等...老子看了半天,你给我个简易版的Padding?还花里胡哨的。"
兄台莫急,且往下看。

1class OffSetWidgetDemo extends StatelessWidget {
2 @override
3 Widget build(BuildContext context) {
4 return Container(
5 width: 300,
6 height: 100,
7 alignment: Alignment.topRight,
8 color: Colors.grey.withAlpha(11),
9 child: OffSetWidget(
10 offset: Offset(20, 20),
11 child: Icon(Icons.android, size: 30,color: Colors.green,),
12 ));
13 }
14}
这里最大的优势是: Offset支持负偏移

1OffSetWidget(
2 offset: Offset(-20, 20),
3 child: Icon(Icons.android, size: 30,color: Colors.green,),
4));
4. 方向版
通过动态计算,可以设置方向进行偏移,如下:
这就像 Position 的能力,但 Position 有必须和 Stack 连用。而 OffSetWidget
随意使用

1class OffSetWidget extends StatelessWidget {
2 final Offset offset;
3 final Widget child;
4 final Direction direction;
5
6 OffSetWidget({this.offset = Offset.zero,
7 this.child,
8 this.direction = Direction.topLeft});
9
10 @override
11 Widget build(BuildContext context) {
12 return CustomSingleChildLayout(
13 delegate: _OffSetDelegate(offset: offset, direction: direction),
14 child: child,
15 );
16 }
17}
18
19enum Direction { topLeft, topRight, bottomLeft, bottomRight }
20
21class _OffSetDelegate extends SingleChildLayoutDelegate {
22 final Offset offset;
23 final Direction direction;
24
25 _OffSetDelegate(
26 {this.offset = Offset.zero, this.direction = Direction.topLeft});
27
28 @override
29 bool shouldRelayout(_OffSetDelegate oldDelegate) =>
30 offset != oldDelegate.offset|| direction!=oldDelegate.direction;
31
32 @override
33 Offset getPositionForChild(Size size, Size childSize) {
34 var w = size.width;
35 var h = size.height;
36 var wc = childSize.width;
37 var hc = childSize.height;
38
39 switch (direction) {
40 case Direction.topLeft:
41 return offset;
42 case Direction.topRight:
43 return offset.translate(w - wc - offset.dx * 2, 0);
44 case Direction.bottomLeft:
45 return offset.translate(0, h - hc - offset.dy * 2);
46 case Direction.bottomRight:
47 return offset.translate(w - wc - offset.dx * 2, h - hc - offset.dy * 2);
48 }
49 return offset;
50 }
51}
CustomSingleChildLayout
组件的用法还是比较简单的。上面代码只是简单演示一下使用方式,也许并不是太实用。 Positioned组件
可以实现定位, SizedOverflowBox组件
可以实现溢出。如下,想在色块的右侧引出一条线,可以使用 CustomSingleChildLayout
让子组件线进行偏移,虽然通过 Row
也可以实现,但是通过 CustomSingleChildLayout
可以得到更多尺寸信息,处理起来更加灵活。

当你遇到对某一个组件约束或定位困难时,或想要获取相关尺寸信息才能进行布局,那么 CustomSingleChildLayout
也许可以帮到你。本篇就这样,谢谢观看 ~