美文网首页Android技术知识Android开发Android开发经验谈
Flutter嵌套地狱!看完此文你就掌握了解决方案

Flutter嵌套地狱!看完此文你就掌握了解决方案

作者: 像程序一样思考 | 来源:发表于2020-02-04 21:41 被阅读0次

    背景

    嵌套层级深的问题让众多刚接触Flutter的同学感到困扰,它不仅是看起来让人感到不适,还非常影响编码体验。

    大佬们会告诉你应该拆分自己的嵌套代码(自定义widget或者抽取build方法)来减少嵌套层级。这确实是个行之有效的方法,除此之外,还有没有别的方法呢,本文将向您介绍另一种减少嵌套层级的方法。

    嵌套过深影响代码的视觉观感

    这段代码演示了什么叫做:

    嵌套地狱

    class Test extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Demo'),),
          body: Container(
            child: Offstage(
              offstage: false,
              child: ListView(
                children: <Widget>[
                  Container(
                    color: Colors.white,
                    padding: EdgeInsets.all(20),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: <Widget>[
                        Icon(Icons.phone),
                        Text("amy"),
                      ],
                    ),
                  ),
                  Container(
                    color: Colors.white,
                    padding: EdgeInsets.all(20),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: <Widget>[
                        Icon(Icons.phone),
                        Text("billy"),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            Toast.makeText(MainActivity.this,"子线程弹出Toast",Toast.LENGTH_SHORT).show();
            Looper.loop();
        }
    });
    thread.start();
    

    提取build方法后,嵌套层级得到了明显的改善:

    class Test extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Demo'),),
          body: Container(
            child: Offstage(
              offstage: false,
              child: ListView(
                children: <Widget>[
                  buildItem("amy"),
                  buildItem("billy"),
                ],
              ),
            ),
          ),
        );
      }
    
      Container buildItem(String name) {
        return Container(
          color: Colors.white,
          padding: EdgeInsets.all(20),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Icon(Icons.phone),
              Text(name),
            ],
          ),
        );
      }
    }
    

    还能不能继续优化呢?

    自定义扩展函数

    举个例子:

    想要给下面这段代码中的第2个Textwidget加上marginTop:10属性

      @override
      Widget build(BuildContext context) {
        return Container(
          padding: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              Text('billy'),
              Text('say hello'), //add margin top??
            ],
          ),
        );
      }
    

    此时,我内心希望可以这样写:

    显然,flutter不支持这么写,幸运的是:dart2.7发布时正式宣布支持扩展函数(Extension Methods)

    https://medium.com/dartlang/dart-2-7-a3710ec54e97

    实际上从dart 2.6.0就开始支持扩展函数了
    如果pubspec.yaml中设置的dart版本低于2.6.0则会出现警告提示
    
    如:
    environment:
      sdk: ">=2.1.0 <3.0.0"
    
    警告提示:
    Extension methods weren’t supported until version 2.6.0
    

    先来定义一个扩展函数

    extension WidgetExt on Widget {
    
      Container intoContainer({
          //复制Container构造函数的所有参数(除了child字段)
        Key key,
        AlignmentGeometry alignment,
        EdgeInsetsGeometry padding,
        Color color,
        Decoration decoration,
        Decoration foregroundDecoration,
        double width,
        double height,
        BoxConstraints constraints,
        EdgeInsetsGeometry margin,
        Matrix4 transform,
      }) {
          //调用Container的构造函数,并将当前widget对象作为child参数
        return Container(
          key: key,
          alignment: alignment,
          padding: padding,
          color: color,
          decoration: decoration,
          foregroundDecoration: foregroundDecoration,
          width: width,
          height: height,
          constraints: constraints,
          margin: margin,
          transform: transform,
          child: this,
        );
      }
    }
    

    现在,所有widget对象都多了一个intoContainer(...)扩展函数,而且参数与Container构造方法一致,于是,我们就可以这样写了:

    除了Container,其它容器也可以通过同样的方式来扩展。于是,编程体验大大提升。

    还可以支持链式调用:

    Text("billy")
      .intoExpanded(flex: 1)
      .intoContainer(color: Colors.white)
    

    有些widget有多个子widget (children), 可以添加如下的扩展函数:

    extension WidgetExt on Widget {
      //添加一个相邻的widget,返回List<Widget>
      List<Widget> addNeighbor(Widget widget) {
        return <Widget>[this, widget];
      }
    
      //添加各种单child的widget容器
      //如:Container、Padding等...
    }
    
    extension WidgetListExt<T extends Widget> on List<T> {
      //子List<Widget>列表中再添加一个相邻的widget,并返回当前列表
      List<Widget> addNeighbor(Widget widget) {
        return this..add(widget);
      }
    
      Row intoRow({
        Key key,
        MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
        MainAxisSize mainAxisSize = MainAxisSize.max,
        CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
        TextDirection textDirection,
        VerticalDirection verticalDirection = VerticalDirection.down,
        TextBaseline textBaseline,
      }) {
        return Row(
          key: key,
          mainAxisAlignment: mainAxisAlignment,
          mainAxisSize: mainAxisSize,
          crossAxisAlignment: crossAxisAlignment,
          textDirection: textDirection,
          verticalDirection: verticalDirection,
          textBaseline: textBaseline,
          children: this,
        );
      }
      //添加其它多child的widget容器
      //如:Column、ListView等...
    }
    

    使用扩展函数解决嵌套过深的问题

    回到本文最初的嵌套地狱,现在我们的代码可以写成这样

    class Test extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text('Demo'),),
            body: buildItem("amy")
                  .addNeighbor(buildItem("billy"),)
                  .intoListView()
                  .intoOffstage(offstage: false)
                  .intoContainer()
        );
      }
    
      Container buildItem(String name) {
        return Icon(Icons.phone)
            .addNeighbor(Text(name))
            .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
            .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),);
      }
    }
    

    为了让我们的代码更加符合链式编程风格,再定义一个静态方法吧

    class WidgetChain {
      static Widget addNeighbor(Widget widget) {
        return widget;
      }
    }
    

    另外,再定义一个从数据到widget的映射扩展方法

    extension ListExt<T> on List<T> {
    
      List<Widget> buildAllAsWidget(Widget Function(T) builder) {
        return this.map<Widget>((item) {
          return builder(item);
        }).toList();
      }
    
    }
    class WidgetChain {
      static Widget addNeighbor(Widget widget) {
        return widget;
      }
    }
    

    现在,代码是这样的:

    class Test extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text('Demo'),),
            body: ["amy", "billy"]
                .buildAllAsWidget((name) =>
                  WidgetChain
                  .addNeighbor(Icon(Icons.phone))
                  .addNeighbor(Text(name))
                  .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
                  .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
                )
                .intoListView()
                .intoOffstage(offstage: false)
                .intoContainer()
        );
      }
    }
    

    值得指出的是,扩展函数(无嵌套)跟构造函数(有嵌套)是可以混用的。

    上面的代码也可以写成这样(Container和Offstage这2层改成了构造函数):

    class Test extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Demo'),),
          body: Container(
            child: Offstage(
              offstage: false,
              child: ["amy", "billy"]
                .buildAllAsWidget((name) =>
                  WidgetChain
                  .addNeighbor(Icon(Icons.phone))
                  .addNeighbor(Text(name))
                  .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
                  .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
                )
                .intoListView()
            ),
          ),
        );
      }
    }
    

    这样的扩展函数你想不想试试呢?

    我已经替大家封装好了常用Widget对应的into扩展函数,可以直接食用:

    dependencies:
      widget_chain: ^0.1.0
    

    导入:

    import 'package:widget_chain/widget_chain.dart';
    

    然后就可以起飞了!

    Github源码地址:

    https://github.com/luckybilly/widget_chain

    就算你现在用不上,也可以先star收藏之,今后如果有人向你吐槽flutter嵌套深,除了叫他拆分封装之外,还可以把widget_chain甩给他 :D

    总结

    本文介绍了Flutter中的嵌套地狱,并使用扩展函数的方式来解决flutter的嵌套地狱问题。

    由于大篇幅的扩展函数调用会影响代码的阅读体验(因为intoXxx函数的调用顺序与widget层级是相反的),需要保留关键嵌套层级结构以使得布局的层级结构保持清晰,文中的扩展函数支持与构造函数混用,具体使用到什么程度,就看大家自己的选择了。

    补充说明

    本文初次发布时包含了使用扩展函数提升编码体验的相关内容。

    因为我之前编写flutter代码时添加父容器的方式为:

    • 先剪切要添加父容器的widget代码
    • 在剪切处编写父容器及其属性
    • 在父容器节点下添加child,粘贴刚才剪切的内容作为child的值

    后来经大佬告知我:IDE中其实提供了添加父容器的快捷键(光标定位在Widget上,然后Alt + Enter,选择需要添加的父容器即可),非常方便。

    之前我确实不知道,特别感谢大佬!

    于是,重新编辑了本文,重点介绍如何使用扩展函数以不一样的编码风格来解决flutter嵌套层级过深的问题。

    希望大家喜欢!

    在这我也分享一份自己收录整理的 Android学习PDF+架构视频+面试文档+源码笔记 ,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料

    总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

    如果你有需要的话,可以点赞+评论关注我,然后私信我【进阶资料】我发给你

    相关文章

      网友评论

        本文标题:Flutter嵌套地狱!看完此文你就掌握了解决方案

        本文链接:https://www.haomeiwen.com/subject/yswdxhtx.html