阅读 1323

【- Flutter 组件篇 285 -】 CustomSingleChildLayout 通用单子布局

一、认识组件

1. CustomSingleChildLayout组件介绍

可容纳一个子组件,并指定代理类对子组件进行排布。代理类可获取父容器区域和子组件的区域大小,及区域约束情况。

名称:       CustomSingleChildLayout  通用单子排布
类型:       布局型
重要性:     ☆☆☆
相关组件:   【Align】、【FractionallySizedBox】、【CustomMultiChildLayout】  
家族:       RenderObjectWidget
                |--- SingleChildRenderObjectWidget
                    |--- CustomSingleChildLayout
复制代码

二、组件测试

1. 测试环境:

这里父容器使用11灰的300*200的盒子,子组件为不设宽高的橙色Container

如下: 默认的约束条件,会使橙色Container伸展占满父容器。

class CustomSingleChildLayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 300,
      height: 200,
      color: Colors.grey.withAlpha(11),
      child:nContainer(
          color: Colors.orange,
      ),
    );
  }
}
复制代码

2. 认识CustomSingleChildLayout

CustomSingleChildLayout容纳一个child,且需要一个抽象代理类SingleChildLayoutDelegate

Flutter并没有提供可用的实现类,所以只能自定义_TolySingleChildLayoutDelegate

class CustomSingleChildLayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 300,
      height: 200,
      color: Colors.grey.withAlpha(11),
      child: CustomSingleChildLayout(
        delegate: _TolySingleChildLayoutDelegate(),
        child: Container(
          color: Colors.orange,
        ),
      ),
    );
  }
}
复制代码

  • SingleChildLayoutDelegate必须实现shouldRelayout方法,可重写:
  • Size getSize(BoxConstraints): 可获取父容器约束条件
  • Offset getPositionForChild(Size, Size) 可获取父子组件的区域,返回子组件偏移量
  • BoxConstraints getConstraintsForChild(BoxConstraints)可获取父容器约束条件,并返回新的约束条件
class _TolySingleChildLayoutDelegate extends SingleChildLayoutDelegate {
  @override
  bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
    return true;
  }

  @override
  Size getSize(BoxConstraints constraints) {
    print('----getSize:----constraints:$constraints----');
    return super.getSize(constraints);
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    print('----size:$size----childSize:$childSize----');
    return super.getPositionForChild(size, childSize);
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    print('----getConstraintsForChild:----constraints:$constraints----');
    return super.getConstraintsForChild(constraints);
  }
}
复制代码

看一下运行打印结果:

I/flutter (28366): ----getSize:----constraints:BoxConstraints(w=300.0, h=200.0)----
I/flutter (28366): ----getConstraintsForChild:----constraints:BoxConstraints(w=300.0, h=200.0)----
I/flutter (28366): ----size:Size(300.0, 200.0)----childSize:Size(300.0, 200.0)----
复制代码
3.使用新的区域约束

getConstraintsForChild可以根据原约束区域返回新的约束区域。如下:

@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
  print('----getConstraintsForChild:----constraints:$constraints----');
  return BoxConstraints(
    maxHeight: constraints.maxHeight/2,
    maxWidth: constraints.maxWidth/2,
    minHeight: constraints.maxHeight/4,
    minWidth: constraints.maxWidth/4,
  );
}
复制代码

4. 子组件的偏移
  • Offset getPositionForChild(Size, Size) 可获取父、子组件的区域,返回子组件偏移量

如下,在刚才的基础上,可以通过偏移让子组件到容器的右上角。

@override
Offset getPositionForChild(Size size, Size childSize) {
  print('----size:$size----childSize:$childSize----');
  return Offset(size.width/2,0 );
}
复制代码

三、CustomSingleChildLayout能干嘛?

从上面可以看出,使用CustomSingleChildLayout可以获取父组件和子组件的布局区域。并可以对子组件进行盒约束偏移定位。一句话来说用于排布一个组件。

1. 控制偏移的代理类

传入一个Offset对象控制子组件的偏移。

class _OffSetDelegate extends SingleChildLayoutDelegate {
  final Offset offset;
  
  _OffSetDelegate({this.offset = Offset.zero});

  @override
  bool shouldRelayout(_OffSetDelegate oldDelegate) =>
      offset != oldDelegate.offset;

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return offset;
  }
}
复制代码

2. 封装类OffSetWidget

你可以直接用CustomSingleChildLayout组件,但为了方便使用,一般都会进行封装使用

下面的OffSetWidget组件可以实现子组件相对于父组件的偏移。

class OffSetWidget extends StatelessWidget {
  final Offset offset;
  final Widget child;

  OffSetWidget({this.offset = Offset.zero, this.child});

  @override
  Widget build(BuildContext context) {
    return CustomSingleChildLayout(
      delegate: _OffSetDelegate(offset: offset),
      child: child,
    );
  }
}
复制代码

3. OffSetWidget使用

这样就可以让子组件在父组件中发生相对偏移。

简约派代表:"等等...老子看了半天,你给我个简易版的Padding?还花里胡哨的。"
兄台莫急,且往下看。

class OffSetWidgetDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 300,
        height: 100,
        alignment: Alignment.topRight,
        color: Colors.grey.withAlpha(11),
        child: OffSetWidget(
          offset: Offset(20, 20),
          child: Icon(Icons.android, size: 30,color: Colors.green,),
        ));
  }
}
复制代码

这里最大的优势是: Offset支持负偏移

child: OffSetWidget(
   offset: Offset(-20, 20),
   child: Icon(Icons.android, size: 30,color: Colors.green,),
));
复制代码

4. 方向版

通过动态计算,可以锁定访问进行偏移,如下:

这样就像Position的能力,但Position有必须用于Stack的现在。
OffSetWidget随意 并且支持负偏移

class OffSetWidget extends StatelessWidget {
  final Offset offset;
  final Widget child;
  final Direction direction;

  OffSetWidget({this.offset = Offset.zero,
      this.child,
      this.direction = Direction.topLeft});

  @override
  Widget build(BuildContext context) {
    return CustomSingleChildLayout(
      delegate: _OffSetDelegate(offset: offset, direction: direction),
      child: child,
    );
  }
}

enum Direction { topLeft, topRight, bottomLeft, bottomRight }

class _OffSetDelegate extends SingleChildLayoutDelegate {
  final Offset offset;
  final Direction direction;

  _OffSetDelegate(
      {this.offset = Offset.zero, this.direction = Direction.topLeft});

  @override
  bool shouldRelayout(_OffSetDelegate oldDelegate) =>
      offset != oldDelegate.offset;

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    var w = size.width;
    var h = size.height;
    var wc = childSize.width;
    var hc = childSize.height;

    switch (direction) {
      case Direction.topLeft:
        return offset;
      case Direction.topRight:
        return offset.translate(w - wc - offset.dx * 2, 0);
      case Direction.bottomLeft:
        return offset.translate(0, h - hc - offset.dy * 2);
      case Direction.bottomRight:
        return offset.translate(w - wc - offset.dx * 2, h - hc - offset.dy * 2);
    }
    return offset;
  }
}
复制代码

CustomSingleChildLayout组件的用法还是比较简单的。上面代码只是简单演示一下使用方式,也许并不是太实用。Positioned组件可以实现定位,SizedOverflowBox组件可以实现溢出。 不过当你遇到对某一个组件约束或定位困难时,CustomSingleChildLayout也许可以帮到你。


尾声

欢迎Star和关注FlutterUnit 的发展,让我们一起携手,成为Unit一员。

另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同分享Flutter的知识,期待与你的交流与切磋。

@张风捷特烈 2020.05.26 未允禁转

我的公众号:编程之王
联系我--邮箱:1981462002@qq.com --微信:zdl1994328
~ END ~