【- Flutter 元件篇 285 -】 CustomSingleChildLayout 通用單子佈局

張風捷特烈發表於2020-05-26

一、認識元件

1. CustomSingleChildLayout元件介紹

可容納一個子元件,並指定代理類對子元件進行排布。代理類可獲取父容器區域和子元件的區域大小,及區域約束情況。

名稱:       CustomSingleChildLayout  通用單子排布
型別:       佈局型
重要性:     ☆☆☆
相關元件:   【Align】、【FractionallySizedBox】、【CustomMultiChildLayout】  
家族:       RenderObjectWidget
                |--- SingleChildRenderObjectWidget
                    |--- CustomSingleChildLayout
複製程式碼

二、元件測試

1. 測試環境:

這裡父容器使用11灰的300*200的盒子,子元件為不設寬高的橙色Container
如下: 預設的約束條件,會使橙色Container伸展佔滿父容器。

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

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可以根據原約束區域返回新的約束區域。如下:

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

@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) 可獲取父、子元件的區域,返回子元件偏移量
    如下,在剛才的基礎上,可以通過偏移讓子元件到容器的右上角。

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

@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?還花裡胡哨的。"
兄臺莫急,且往下看。

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

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支援負偏移

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

child: OffSetWidget(
   offset: Offset(-20, 20),
   child: Icon(Icons.android, size: 30,color: Colors.green,),
));
複製程式碼

4. 方向版

通過動態計算,可以鎖定訪問進行偏移,如下:
這樣就像Position的能力,但Position有必須用於Stack的現在。
OffSetWidget隨意 並且支援負偏移

【- Flutter 元件篇  285 -】 CustomSingleChildLayout 通用單子佈局

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 ~

相關文章