Flutter嵌套深闹丐?動(dòng)不動(dòng)就要拆分横殴?現(xiàn)在有了這一招

背景

嵌套層級深的問題讓眾多剛接觸Flutter的同學(xué)感到困擾,它不僅是看起來讓人感到不適卿拴,還非常影響編碼體驗(yàn)衫仑。

老手們會(huì)告訴你應(yīng)該拆分自己的嵌套代碼(自定義widget或者抽取build方法)來減少嵌套層級。這確實(shí)是個(gè)行之有效的方法堕花,但這只是解決了最終代碼的視覺觀感問題文狱,編碼體驗(yàn)還是不太理想。

嵌套過深影響代碼的視覺觀感

這段代碼演示了什么叫做:嵌套地獄

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"),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

提取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),
        ],
      ),
    );
  }
}

但瞄崇,F(xiàn)lutter嵌套層級的問題不僅體現(xiàn)在代碼的視覺觀感上呻粹,還體現(xiàn)在編碼過程中的體驗(yàn)

影響編碼體驗(yàn)

如果想要給某個(gè)widget加一個(gè)父容器灭美,需要三步走:

  • 剪切當(dāng)前widget的及其子widget的所有代碼到粘貼板中
  • 在剪切處編寫新增的父容器代碼
  • 在父容器下添加child贵扰,并將剛才剪切的代碼粘貼賦值給child

舉個(gè)例子:想要給下面這段代碼中的第2個(gè)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??
        ],
      ),
    );
  }

當(dāng)時(shí),我是這樣寫的:

  • 先把Text('say hello')剪切
  • 在剪切處編寫Container并設(shè)置margin屬性
  • 在Container節(jié)點(diǎn)下添加child抄谐,粘貼剛才剪切的內(nèi)容作為child的值
添加marginTop

實(shí)際編碼過程中摹蘑,這種情況頻繁出現(xiàn)筹燕,特別是代碼行數(shù)比較多的時(shí)候,這是非常糟糕的體驗(yàn)衅鹿。

此時(shí)撒踪,我內(nèi)心希望可以這樣寫:

如果能這樣添加marginTop就好了

顯然,flutter不支持這么寫大渤,幸運(yùn)的是:dart2.7發(fā)布時(shí)正式宣布支持?jǐn)U展函數(shù)(Extension Methods)

實(shí)際上從dart 2.6.0就開始支持?jǐn)U展函數(shù)了
如果pubspec.yaml中設(shè)置的dart版本低于2.6.0則會(huì)出現(xiàn)警告提示

如:
environment:
  sdk: ">=2.1.0 <3.0.0"

警告提示:
Extension methods weren’t supported until version 2.6.0

用擴(kuò)展函數(shù)提升編碼體驗(yàn)

先來定義一個(gè)擴(kuò)展函數(shù)

extension WidgetExt on Widget {

  Container intoContainer({
    //復(fù)制Container構(gòu)造函數(shù)的所有參數(shù)(除了child字段)
    Key key,
    AlignmentGeometry alignment,
    EdgeInsetsGeometry padding,
    Color color,
    Decoration decoration,
    Decoration foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    EdgeInsetsGeometry margin,
    Matrix4 transform,
  }) {
    //調(diào)用Container的構(gòu)造函數(shù)制妄,并將當(dāng)前widget對象作為child參數(shù)
    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,
    );
  }
}

現(xiàn)在,所有widget對象都多了一個(gè)intoContainer(...)擴(kuò)展函數(shù)兼犯,而且參數(shù)與Container構(gòu)造方法一致忍捡,于是集漾,我們就可以這樣寫了:

用intoContainer擴(kuò)展函數(shù)添加marginTop

除了Container切黔,其它容器也可以通過同樣的方式來擴(kuò)展。于是具篇,編程體驗(yàn)大大提升纬霞,再也不用動(dòng)不動(dòng)就大段選擇代碼剪切粘貼了。

還可以支持鏈?zhǔn)秸{(diào)用:

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

有些widget有多個(gè)子widget (children)驱显, 可以添加如下的擴(kuò)展函數(shù):

extension WidgetExt on Widget {
  //添加一個(gè)相鄰的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>列表中再添加一個(gè)相鄰的widget埃疫,并返回當(dāng)前列表
  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等...
}

到現(xiàn)在,不知大家有沒有發(fā)現(xiàn)栓霜,這個(gè)擴(kuò)展函數(shù)不僅僅是能在局部優(yōu)化我們的編碼體驗(yàn)?zāi)敲春唵瘟舜滂耄€能解決代碼嵌套層級過深的問題。

使用擴(kuò)展函數(shù)解決嵌套過深的問題

回到本文最初的嵌套地獄胳蛮,現(xiàn)在我們的代碼可以寫成這樣

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),);
  }
}

為了讓我們的代碼更加符合鏈?zhǔn)骄幊田L(fēng)格销凑,再定義一個(gè)靜態(tài)方法吧

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

另外,再定義一個(gè)從數(shù)據(jù)到widget的映射擴(kuò)展方法

extension ListExt<T> on List<T> {

  List<Widget> buildAllAsWidget(Widget Function(T) builder) {
    return this.map<Widget>((item) {
      return builder(item);
    }).toList();
  }

}

現(xiàn)在仅炊,代碼是這樣的:

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()
    );
  }
}

值得指出的是斗幼,擴(kuò)展函數(shù)(無嵌套)跟構(gòu)造方法(有嵌套)是可以混用的。上面的代碼也可以寫成這樣:

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()
        ),
      ),
    );
  }
}

這樣的擴(kuò)展函數(shù)你想不想要試試呢抚垄?大量常用的Widget的into擴(kuò)展函數(shù)我已經(jīng)替大家封裝好了蜕窿,可以直接食用:

dependencies:
  widget_chain: ^0.1.0

導(dǎo)入:

import 'package:widget_chain/widget_chain.dart';

然后就可以起飛了谋逻!

Github源碼地址: widget_chain 敬請star收藏

總結(jié)

本文介紹了Flutter中的嵌套地獄,以及由于嵌套導(dǎo)致編碼體驗(yàn)不佳的問題桐经,并從解決編碼體驗(yàn)出發(fā)斤贰,使用擴(kuò)展函數(shù)解決了flutter的嵌套地獄。

由于大篇幅的擴(kuò)展函數(shù)調(diào)用會(huì)影響代碼閱讀體驗(yàn)次询,還是需要保留部分關(guān)鍵嵌套層級結(jié)構(gòu)以使得布局的層級結(jié)構(gòu)保持清晰荧恍,文中的擴(kuò)展函數(shù)支持與構(gòu)造函數(shù)混用,具體使用到什么程度屯吊,就看大家自己的選擇了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末送巡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盒卸,更是在濱河造成了極大的恐慌骗爆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔽介,死亡現(xiàn)場離奇詭異摘投,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)虹蓄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門犀呼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薇组,你說我怎么就攤上這事外臂。” “怎么了律胀?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵宋光,是天一觀的道長。 經(jīng)常有香客問我炭菌,道長罪佳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任黑低,我火速辦了婚禮赘艳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘投储。我一直安慰自己第练,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布玛荞。 她就那樣靜靜地躺著娇掏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勋眯。 梳的紋絲不亂的頭發(fā)上婴梧,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天下梢,我揣著相機(jī)與錄音,去河邊找鬼塞蹭。 笑死孽江,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的番电。 我是一名探鬼主播岗屏,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漱办!你這毒婦竟也來了这刷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤娩井,失蹤者是張志新(化名)和其女友劉穎暇屋,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洞辣,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咐刨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扬霜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片定鸟。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖畜挥,靈堂內(nèi)的尸體忽然破棺而出仔粥,到底是詐尸還是另有隱情,我是刑警寧澤蟹但,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站谭羔,受9級特大地震影響华糖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘟裸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一客叉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧话告,春花似錦兼搏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至病线,卻和暖如春吓著,著一層夾襖步出監(jiān)牢的瞬間鲤嫡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工绑莺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暖眼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓纺裁,卻偏偏與公主長得像诫肠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子欺缘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容