Flutter筆記-自定義布局

自定義布局

布局?jǐn)[放

我們來(lái)實(shí)現(xiàn)這樣一個(gè)布局,每個(gè)正方形都是一個(gè)子控件,先實(shí)現(xiàn)基礎(chǔ)的部分

class MyLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      child: LayoutDemo(
        children: <Widget>[
          Container(
            color: Colors.blue,
            width: 100,
            height: 100,
          ),
          Container(
            color: Colors.red,
            width: 100,
            height: 100,
          ),
          Container(
            color: Colors.green,
            width: 100,
            height: 100,
          ),
          Container(
            color: Colors.amber,
            width: 100,
            height: 100,
          )
        ],
      ),
    );
  }
}

然后我們通過(guò)LayoutDemo來(lái)對(duì)4個(gè)子控件進(jìn)行位置擺放,和之前自定義控件一樣,繼承一個(gè)RenderObjectWidget,不過(guò)這次我們使用MultiChildRenderObjectWidget,顧名思義,多個(gè)子控件

class LayoutDemo extends MultiChildRenderObjectWidget{
  LayoutDemo({
    Key key,
    List<Widget> children
  }): super(key: key , children: children);

  @override
  RenderLayout createRenderObject(BuildContext context) {
    return RenderLayout();
  }
}

class PageParentData extends ContainerBoxParentData<RenderBox> {}

class RenderLayout extends RenderBox with ContainerRenderObjectMixin<RenderBox, PageParentData>,RenderBoxContainerDefaultsMixin<RenderBox, PageParentData>{

  //必須使用,作用是初始化data對(duì)象
  @override
  void setupParentData(RenderBox child) {
    if (child.parentData is! PageParentData)
      child.parentData = PageParentData();
  }

  @override
  void performLayout() {}

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }
}

為什么混合ContainerRenderObjectMixin類(lèi),和之前一樣,類(lèi)里包含了子控件及偏移的數(shù)據(jù),也可以不用自己寫(xiě)?;旌?code>RenderBoxContainerDefaultsMixin類(lèi)是用其defaultPaint(context, offset);,當(dāng)然也可以單獨(dú)提取出來(lái)
直接回到layout過(guò)程

@override
  void performLayout() {
    var index = 0;
    double widthOffset = 0.0;
    double heightOffset = 0.0;
    //size = constraints.constrain(Size(200, 200));
    size = constraints.constrain(Size(double.infinity,double.infinity));
    RenderBox child = firstChild;
    while(child != null){
      if(index != 0){
        if(index%2 == 0){
          widthOffset = 0.0;
          heightOffset += 100.0;
        }else{
          widthOffset += 100.0;
        }
      }
      final PageParentData childParentData = child.parentData;
      //給一個(gè)約束布局,讓子控件自己擺放
      child.layout(constraints.heightConstraints(), parentUsesSize: true);
      //保存偏移值
      childParentData.offset = Offset(widthOffset, heightOffset);
      index++;
      //找到下一個(gè)子控件
      child = childParentData.nextSibling;
    }
  }

layout過(guò)程中,必須給size賦值,size = constraints.constrain(Size(double.infinity,double.infinity));,這個(gè)就是match_parent效果,內(nèi)部占據(jù)了最大;
size = constraints.constrain(Size(300, 300));給定一個(gè)精確的值;size = constraints.constrain(Size.zero);,這個(gè)就是warp_content效果,布局的大小有全部子控件的大小決定
真正的繪制決定了子控件的位置

void defaultPaint(PaintingContext context, Offset offset) {
    ChildType child = firstChild;
    while (child != null) {
      final ParentDataType childParentData = child.parentData;
      //使用之前保存的偏移值
      context.paintChild(child, childParentData.offset + offset);
      child = childParentData.nextSibling;
    }
  }

同樣的,也有官方的封裝控件CustomMultiChildLayout,需要給一個(gè)delegate(作用與之前的CustomPainter一樣)

class LayoutWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      child: CustomMultiChildLayout(
        delegate: _MyDelegate(),
        children: <Widget>[
          LayoutId(
              id: 0,
              child: Container(
                color: Colors.blue,
                width: 100,
                height: 100,
              )
          ),
          LayoutId(
            id: 1,
            child: Container(
              color: Colors.red,
              width: 100,
              height: 100,
            ),
          ),
          LayoutId(
            id: 2,
            child:  Container(
              color: Colors.green,
              width: 100,
              height: 100,
            ),
          ),
          LayoutId(
              id: 3,
              child: Container(
                color: Colors.amber,
                width: 100,
                height: 100,
              )
          ),
        ],
      ),
    );
  }
}

LayoutId給每個(gè)子控件一個(gè)id編號(hào),供下面判斷

class _MyDelegate extends MultiChildLayoutDelegate{
  @override
  void performLayout(Size size) {
     //這里傳遞過(guò)來(lái)的size是父控件的size,因?yàn)楦割?lèi)并沒(méi)有對(duì)size進(jìn)行約束,所以是最大的
    double widthOffset = 0.0;
    double heightOffset = 0.0;
    for(int i = 0; i < 4; i++){
      if(hasChild(0)){
        if(i != 0){
          if(i%2 == 0){
            widthOffset = 0.0;
            heightOffset += 100.0;
          }else{
            widthOffset += 100.0;
          }
        }
        //其實(shí)內(nèi)部就是用child.layout(constraints.heightConstraints(), parentUsesSize: true);
        layoutChild(i, BoxConstraints.loose(size));
        //其實(shí)內(nèi)部就是用childParentData.offset = Offset(widthOffset, heightOffset);
        positionChild(i, Offset(widthOffset, heightOffset));
      }
    }
  }

  //是否需要重新擺放
  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
    return false;
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容