美文网首页Flutter圈子Flutter中文社区
Flutter入门-03-封装简单复用组件

Flutter入门-03-封装简单复用组件

作者: HelloMrLi | 来源:发表于2019-12-18 16:40 被阅读0次

    前言

      首先我们知道Flutter的语法是嵌套型的,这种语法的最直观的感受就是代码多,嵌套层次深的话可能导致代码阅读困难,其实,这也是Flutter的一项不完美的地方吧。

    正文

      面对这个问题,我们可以采用代码抽取,复用的方式来减少在一个文件中堆叠大量代码;采用这种方式的好处如下:

    • 减少了使用处代码的代码量和层级,代码精简直观
    • 抽取出的代码可以多处复用,不限于当前页面,方便灵活拓展
    • 减少了代码量的同时,也方便了后续维护者的阅读与编写,便于维护

    一:代码抽取

      在日常的开发中,我们首先会拿到UI的设计稿,在脑子里已经排版布局,然后就疯狂的码代码。例如UI小姐姐拿出了下面常用设计稿


    我的
    1. 经过分析,这是一个除了图标和文字,其他都一样的组件
    2. 开始编写其中一条的代码,比如我的订单
    3. 写完一条后后粘贴复制,修改成其他剩余的icon和文字就搞定了

    那我就直接拿出某一条代码,这是从Android studio里直接copy的,行数我看了下是41行,具体原始代码如下:

    GestureDetector(
                  child: Container(
                      color: Color(0xffffffff),
                      padding: EdgeInsets.only(left: 20, right: 16),
                      child: SizedBox(
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: <Widget>[
                            Wrap(
                              runAlignment: WrapAlignment.center,
                              crossAxisAlignment: WrapCrossAlignment.center,
                              children: <Widget>[
                                Image.asset(
                                  "resource/imgs/icon_my_card.png",
                                  width: 18,
                                  height: 18,
                                  fit: BoxFit.contain,
                                ),
                                Padding(
                                    padding: EdgeInsetsDirectional.only(start: 22)),
                                Text("银行卡01"),
                              ],
                            ),
                            Wrap(
                              runAlignment: WrapAlignment.center,
                              crossAxisAlignment: WrapCrossAlignment.center,
                              children: <Widget>[
                                Text(""),
                                Padding(
                                  padding: EdgeInsetsDirectional.only(start: 10),
                                  child:
                                      Image.asset("resource/imgs/arrow_right.png"),
                                )
                              ],
                            ),
                          ],
                        ),
                        height: 48,
                      )),
                  onTap: () {},
                )
    

      按照我们原来的偷懒计划,直接粘贴复制,一顿操作后发现近300行代码,心中窃喜(这下统计代码量,老子吊打一切,- _-)。当然开玩笑,我们写代码不是为了多,而是完成一个功能,使用的代码越少越好。

    二:改造代码,多处复用

      我们上面发现,只是实现了7个相同的item,没有优化的情况下就编写了200多行代码,这实在是太多了,看起来也是眼花缭乱的。不行,我们作为有责任感,有逼格的程序员怎么能看的下去呢,我们需要对这坨代码进行优化,大脑在思考:

    1. 其实大多数代码都一样,只是图标和文字不同
    2. 是否能够抽取公共部分,把变量部分暴露出来给用户自己动态传递呢
    

    答案当然是可以的,且思路也是正确的。那么我们用什么来存储这些公共部分呢,有两种方式:

    1. 继承statelessWidget或者statefulWidget,重新渲染公共部分
    2. 直接写个公共方法,在return里直接返回公共部分组件
    

    两种方式都是可以的,看需求而定;如果你的公共部分不需要独立的动态渲染,只是提供静态的公共组件,那直接选用第二种将更方便;这里我们看,我们的公共部分,只是静态的显示,不涉及动态变化的东西,我们就直接用第二种,抽取的过程我就不多说了,下面看抽取出来的代码:

    ///通用item左右都带有有个icon和text的通用item,常用于我的页面等item条目
    ///
    ///[iconLeft] 左边icon的widget
    ///
    ///[textLeft] 左边文本的Widget
    ///
    ///[iconRight] 右边icon的widget
    ///
    ///[textRight] 右边文本的Widget
    ///
    ///[heightItem] item高度
    ///
    ///[paddingLeftRight] 左边Image距离右边文本的间距
    ///
    ///[paddingRightLeft] 右边Image距离左边文本的间距
    ///
    ///[importance] 左侧文本是否显示必填*号
    ///
    ///[onPress] item点击事件函数回调
    iconTextItem(
        {Widget iconLeft,
        Widget iconRight,
        double heightItem = 50,
        double paddingLeftRight = 8,
        double paddingRightLeft = 15,
        importance = false,
        Color backgroundColor,
        double leftPadding,
        double rightPadding,
        @required Widget textLeft,
        Widget textRight,
        VoidCallback onPress,
        Key key}) {
      return GestureDetector(
        child: Container(
            key: key,
            color: backgroundColor ?? Color(0xffffffff),
            padding:
                EdgeInsets.only(left: leftPadding ?? 0, right: rightPadding ?? 0),
            child: SizedBox(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Wrap(
                    runAlignment: WrapAlignment.center,
                    crossAxisAlignment: WrapCrossAlignment.center,
                    children: <Widget>[
                      iconLeft ?? Text(""),
                      Padding(
                          padding: EdgeInsetsDirectional.only(
                              start: iconLeft != null ? paddingLeftRight : 0),
                          child:
                              importance ? _getImportanceText(textLeft) : textLeft),
                    ],
                  ),
                  Wrap(
                    runAlignment: WrapAlignment.center,
                    crossAxisAlignment: WrapCrossAlignment.center,
                    children: <Widget>[
                      textRight ?? Text(""),
                      Padding(
                        padding:
                            EdgeInsetsDirectional.only(start: paddingRightLeft),
                        child: iconRight ??
                            Image.asset("resource/imgs/arrow_right.png"),
                      )
                    ],
                  ),
                ],
              ),
              height: heightItem,
            )),
        onTap: onPress,
      );
    }
    
    /// 返回带有必填*的文本,*号默认红色
    _getImportanceText(Text text, {Color color}) {
      assert(text != null);
      return Wrap(
        crossAxisAlignment: WrapCrossAlignment.center,
        children: <Widget>[
          text,
          Text(
            "*",
            style: TextStyle(color: color ?? colorRed23681731, fontSize: 15),
          ),
        ],
      );
    }
    

    代码行数为59行,为啥反而多了呢,因为这里我又兼容了如下的设计稿,通过传入参数可以控制红色的*是否显示,也就是达到了是否必须的设计效果


    必填项

    那既然我们都封装好了,如何在代码里使用呢,使用方式在目标页面中,直接放入即可,具体引入处代码如下:

    iconTextItem(
                  onPress: () {
                    print("我的银行卡");
                    Navigator.pushNamed(context, "/mybankcard");
                  },
                  leftPadding: 20,
                  rightPadding: 14,
    //            backgroundColor: Colors.greenAccent,
                  paddingLeftRight: 22,
                  iconLeft: Image.asset(
                    "resource/imgs/icon_my_card.png",
                    width: 18,
                    height: 18,
                    fit: BoxFit.contain,
                  ),
                  textLeft: Text(
                    "我的银行卡",
                    style: TextStyle(color: colorBlackFF444C63, fontSize: 15),
                  ),
                )
    

    我们把图标和文字声明在方法参数中,同时提供部分默认值,使用处传参即可。我复制过来是19行,原来41行,当前文件节省了21行,不仅如此还减少了层叠情况,看起来更清晰了。现在完成第一个设计稿只需要19*7=133行,比原来(42*7=294)优化了161行,这种效果还是看得见的

    总结:

      本片文章主要介绍了通过方法来抽取公共部分的代码加以复用,方便日后的维护拓展。这里还有个自己轻度封装简洁的库,是一个左右widget的package,同样可以达到上述效果,给用户留了更大的自定义空间,大家有兴趣的可以了解使用支持下

    上一篇:Flutter入门-02-Dart语言简讲
    下一篇:Android开发之WebView(一)配置&小技巧

    相关文章

      网友评论

        本文标题:Flutter入门-03-封装简单复用组件

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