Flutter Go 源碼分析(二)

(5) AppPage()基礎(chǔ)頁(yè)面

AppPage是整個(gè)App的入口,在這里實(shí)現(xiàn)Tabbar、SearchBar等基礎(chǔ)控件。
在分析AppPage頁(yè)面之前先說(shuō)一下Scaffold這個(gè)widget,這里我們可以把它理解為頁(yè)面,類似OC里面的UIViewController:

    this.appBar, //橫向水平布局,通常顯示在頂部(*)
    this.body, // 內(nèi)容(*)
    this.floatingActionButton, //懸浮按鈕,就是上圖右下角按鈕(*)
    this.floatingActionButtonLocation, //懸浮按鈕位置
    //懸浮按鈕在[floatingActionButtonLocation]出現(xiàn)/消失動(dòng)畫
    this.floatingActionButtonAnimator, 
    //在底部呈現(xiàn)一組button,顯示于[bottomNavigationBar]之上,[body]之下
    this.persistentFooterButtons,
    //一個(gè)垂直面板,顯示于左側(cè),初始處于隱藏狀態(tài)(*)
    this.drawer,
    this.endDrawer,
    //出現(xiàn)于底部的一系列水平按鈕(*)
    this.bottomNavigationBar,
    //底部持久化提示框
    this.bottomSheet,
    //內(nèi)容背景顏色
    this.backgroundColor,
    //棄用,使用[resizeToAvoidBottomInset]
    this.resizeToAvoidBottomPadding,
    //重新計(jì)算布局空間大小
    this.resizeToAvoidBottomInset,
    //是否顯示到底部,默認(rèn)為true將顯示到頂部狀態(tài)欄
    this.primary = true,
    //
    this.drawerDragStartBehavior = DragStartBehavior.down,

AppPage:

Widget build(BuildContext context) {
    var db = Provider.db;

    return new Scaffold(
      appBar: new AppBar(title: buildSearchInput(context)),
      body: new TabBarView(controller: controller, children: <Widget>[
        new FirstPage(),
        new WidgetPage(db),
        new CollectionPage(),
        FourthPage()
      ]),
      bottomNavigationBar: Material(
        color: const Color(0xFFF0EEEF), //底部導(dǎo)航欄主題顏色
        child: SafeArea(
          child: Container(
            height: 65.0,
            decoration: BoxDecoration(
              color: const Color(0xFFF0F0F0),
              boxShadow: <BoxShadow>[
                BoxShadow(
                  color: const Color(0xFFd0d0d0),
                  blurRadius: 3.0,
                  spreadRadius: 2.0,
                  offset: Offset(-1.0, -1.0),
                ),
              ],
            ),
            child: TabBar(
                controller: controller,
                //tab標(biāo)簽的下劃線顏色
                indicatorColor: Theme.of(context).primaryColor,
                
                // labelColor: const Color(0xFF000000),
                indicatorWeight: 3.0,
                //labelcolor 選中的
                labelColor: Theme.of(context).primaryColor,
                //labelColor: Colors.green,
                unselectedLabelColor: const Color(0xFF8E8E8E),
                tabs: myTabs),
          ),
        ),
      ),
    );

主要是通過(guò)AppBar-->buildSearchInput()(搜索框)、body-->TabBarView()(頁(yè)面)、bottomNavigationBar-->TabBar()(tabbar)三部分組成,下面我們來(lái)依次拆解。

  1. buildSearchInput
    buildSearchInput函數(shù)里是創(chuàng)建的SearchInput對(duì)象:
    構(gòu)造函數(shù):
  final getResults;//獲取搜索內(nèi)容函數(shù)

  final ValueChanged<String> onSubmitted;//沒有用到

  final VoidCallback onSubmitPressed;//沒有用到

  SearchInput(this.getResults, this.onSubmitted, this.onSubmitPressed);//構(gòu)造函數(shù)

build函數(shù)除了MaterialSearchInput之外都是一些基礎(chǔ)wiget布局,其他不做闡述,我們來(lái)看一下MaterialSearchInput
build函數(shù):

Widget build(BuildContext context) {
    final TextStyle valueStyle = Theme.of(context).textTheme.subhead;

    return new InkWell(
      onTap: () => _showMaterialSearch(context),
      child: new FormField<T>(
        key: _formFieldKey,
        validator: widget.validator,
        onSaved: widget.onSaved,
        autovalidate: autovalidate,
        builder: (FormFieldState<T> field) {
          return new InputDecorator(
            isEmpty: _isEmpty(field),
            decoration: new InputDecoration(
              labelText: widget.placeholder,
              border: InputBorder.none,
              errorText: field.errorText,
            ),
            child: _isEmpty(field)
                ? null
                : new Text(
                    widget.formatter != null
                        ? widget.formatter(field.value)
                        : field.value.toString(),
                    style: valueStyle),
          );
        },
      ),
    );
  }

可以看到這搜索框是有一個(gè)FormField
來(lái)實(shí)現(xiàn)的,這里實(shí)例化對(duì)象的時(shí)候只用到了getResultsplaceholder我暫時(shí)只對(duì)這兩個(gè)做說(shuō)明,其他屬性如果有感興趣的同學(xué)可以自行去了解。
接下來(lái)我們主要研究_showMaterialSearch,也就是點(diǎn)擊之后跳轉(zhuǎn)的搜索頁(yè)面。

_showMaterialSearch(BuildContext context) {
    Navigator.of(context)
        .push(_buildMaterialSearchPage(context))
        .then((dynamic value) {
      if (value != null) {
        _formFieldKey.currentState.didChange(value);
        widget.onSelect(value);
      }
    });
  }

_showMaterialSearch-->_MaterialSearchPageRoute-->MaterialSearchbuild函數(shù):

Widget build(BuildContext context) {
    var results =
        (widget.results ?? _results).where((MaterialSearchResult result) {
      if (widget.filter != null) {
        return widget.filter(result.value, _criteria);
      }
      //only apply default filter if used the `results` option
      //because getResults may already have applied some filter if `filter` option was omited.
      else if (widget.results != null) {
        return _filter(result.value, _criteria);
      }

      return true;
    }).toList();

    if (widget.sort != null) {
      results.sort((a, b) => widget.sort(a.value, b.value, _criteria));
    }

    results = results.take(widget.limit).toList();

    IconThemeData iconTheme =
        Theme.of(context).iconTheme.copyWith(color: widget.iconColor);

    return new Scaffold(
      appBar: new AppBar(
        leading: widget.leading,
        backgroundColor: widget.barBackgroundColor,
        iconTheme: iconTheme,
        title: new TextField(
          controller: _controller,
          autofocus: true,
          decoration:
              new InputDecoration.collapsed(hintText: widget.placeholder),
          style: Theme.of(context).textTheme.title,
          onSubmitted: (String value) {
            if (widget.onSubmit != null) {
              widget.onSubmit(value);
            }
          },
        ),
        actions: _criteria.length == 0
            ? []
            : [
                new IconButton(
                    icon: new Icon(Icons.clear),
                    onPressed: () {
                      setState(() {
                        _controller.text = _criteria = '';
                      });
                    }),
              ],
      ),
      body: buildBody(results),
      );
  }

組成部分有兩部分

  • 1)AppBar(主要是textfield)
    用一個(gè)TextEditingController來(lái)配合監(jiān)聽輸入框的文字變化
_controller.addListener(() {
      setState(() {
        _criteria = _controller.value.text;
        if (widget.getResults != null) {
          _getResultsDebounced();
        }
      });
    });

通過(guò)構(gòu)造函數(shù)傳進(jìn)來(lái)的getResults來(lái)去數(shù)據(jù)庫(kù)獲取搜索結(jié)果。

Timer _resultsTimer;
  Future _getResultsDebounced() async {
    if (_results.length == 0) {
      setState(() {
        _loading = true;
      });
    }

    if (_resultsTimer != null && _resultsTimer.isActive) {
      _resultsTimer.cancel();
    }
    //延遲400毫秒再執(zhí)行
    _resultsTimer = new Timer(new Duration(milliseconds: 400), () async {
      if (!mounted) {
        return;
      }

      setState(() {
        _loading = true;
      });

      var results = await widget.getResults(_criteria);

      if (!mounted) {
        return;
      }

      if (results != null) {
        setState(() {
          _loading = false;
          _results = results;
        });
      }
    });
  }
  • 2)body(buildBody)
    最外層代碼不講解了,我都注釋好了:
Widget buildBody(List results) {
    if (_criteria.isEmpty) {//如果沒有搜索關(guān)鍵字則顯示歷史記錄
      return History();
    } else if (_loading) {//正在搜索顯示加載框
      return new Center(
          child: new Padding(
              padding: const EdgeInsets.only(top: 50.0),
              child: new CircularProgressIndicator()
          )
      );
    }
    if (results.isNotEmpty) {//如果有搜索結(jié)果就顯示搜索列表
      var content = new SingleChildScrollView(
          child: new Column(
            children: results
          )
      );
      return content;
    }
    return Center(child: Text("暫無(wú)數(shù)據(jù)"));//這個(gè)是有搜索關(guān)鍵字而沒有搜索結(jié)果的時(shí)候顯示暫無(wú)數(shù)據(jù)
  }

看一下搜索歷史記錄頁(yè)面:

  • History()
    build函數(shù):
Widget build(BuildContext context) {
    //獲取歷史記錄的widget list
    List<Widget> childList = buildChips(context);
    if (childList.length == 0) {//如果沒有歷史記錄 
      return Center(
        child: Text("當(dāng)前歷史面板為空"),
      );
    }
    return Column(//有歷史記錄
      children: <Widget>[
        Container(//頭部 歷史搜索文字
          alignment: Alignment.centerLeft,
          padding: EdgeInsets.fromLTRB(12.0, 12, 12, 0),
          child: InkWell(
            onLongPress: () {//長(zhǎng)按情況搜索歷史記錄
              searchHistoryList.clear();
            },
            child: Text('歷史搜索'),
          ),
        ),
        Container(//搜索歷史列表
          padding: EdgeInsets.only(left: 10),
          alignment: Alignment.topLeft,
          child: Wrap(
            spacing: 6.0, // gap between adjacent chips
            runSpacing: 0.0, // gap between lines
            children: childList
          ),
        )
      ],
    );
  }

獲取歷史記錄的widget list 方法buildChips

buildChips(BuildContext context) {
    List<Widget> list = [];//存儲(chǔ)搜索列表widget
    List<SearchHistory> historyList = searchHistoryList.getList();//獲取搜索記錄數(shù)據(jù)(SearchHistory)
    print("historyList> $historyList");
    Color bgColor = Theme.of(context).primaryColor;
    historyList.forEach((SearchHistory value) {//遍歷歷史記錄數(shù)據(jù) 轉(zhuǎn)成widget裝入list

      Widget icon = CircleAvatar(
        backgroundColor: bgColor,
        child: Text(
          value.name.substring(0, 1),
          style: TextStyle(color: Colors.white),
        ),
      );
      if (WidgetName2Icon.icons[value.name] != null) {
        icon = Icon(WidgetName2Icon.icons[value.name], size: 25);
      }

      list.add(
        InkWell(
          onTap: () {//跳轉(zhuǎn)
            Application.router.navigateTo(context, "${value.targetRouter}", transition: TransitionType.inFromRight);
          },
          child: Chip(
            avatar: icon,
            label: Text("${value.name}"),
          ),
        )
      );
    });
    return list;
  }

代碼都已做了詳細(xì)注釋,不做過(guò)多解釋了。

  • 搜索結(jié)果列表
    首先我們看results數(shù)據(jù):
Widget buildSearchInput(BuildContext context) {
    return new SearchInput((value) async {
      if (value != '') {
        List<WidgetPoint> list = await widgetControl.search(value);

        return list
            .map((item) => new MaterialSearchResult<String>(
                  value: item.name,
                  icon: WidgetName2Icon.icons[item.name] ?? null,
                  text: 'widget',
                  onTap: () {
                    onWidgetTap(item, context);
                  },
                ))
            .toList();
      } else {
        return null;
      }
    }, (value) {}, () {});
  }

不難看出results里面裝的是MaterialSearchResult的實(shí)例對(duì)象,MaterialSearchResultbuild 方法:

Widget build(BuildContext context) {

    return new InkWell(
      onTap: this.onTap,
      child: new Container(
        height: 64.0,
        padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
        child: new Row(
          children: <Widget>[
            new Container(width: 30.0, margin: EdgeInsets.only(right: 10), child: new Icon(icon)) ?? null,
            new Expanded(child: new Text(value, style: Theme.of(context).textTheme.subhead)),
            new Text(text, style: Theme.of(context).textTheme.subhead)
          ],
        ),
      ),
    );
  }

跳轉(zhuǎn)代碼:

oid onWidgetTap(WidgetPoint widgetPoint, BuildContext context) {
    List widgetDemosList = new WidgetDemoList().getDemos();//獲取所有注冊(cè)過(guò)的demo頁(yè)面
    String targetName = widgetPoint.name;
    String targetRouter = '/category/error/404';
    widgetDemosList.forEach((item) {
      if (item.name == targetName) {
        targetRouter = item.routerName;
      }
    });
    //添加歷史記錄到SharedPreferences
    searchHistoryList
        .add(SearchHistory(name: targetName, targetRouter: targetRouter));
    print("searchHistoryList ${searchHistoryList.toString()}");
    Application.router.navigateTo(context, "$targetRouter");
  }

這也搜索結(jié)果列表的邏輯也就出來(lái)了。

  1. TabBarView
    這個(gè)在稍后我們進(jìn)行詳細(xì)的分開拆解。
  2. bottomNavigationBar
bottomNavigationBar: Material(
        color: const Color(0xFFF0EEEF), //底部導(dǎo)航欄主題顏色
        child: SafeArea(//safeArea
          child: Container(
            height: 65.0,
            decoration: BoxDecoration(//陰影
              color: const Color(0xFFF0F0F0),
              boxShadow: <BoxShadow>[
                BoxShadow(
                  color: const Color(0xFFd0d0d0),
                  blurRadius: 3.0,
                  spreadRadius: 2.0,
                  offset: Offset(-1.0, -1.0),
                ),
              ],
            ),
            child: TabBar(//下面的tabbar
                controller: controller,
                //tab標(biāo)簽的下劃線顏色
                indicatorColor: Theme.of(context).primaryColor,
                
                // labelColor: const Color(0xFF000000),
                indicatorWeight: 3.0,
                //labelcolor 選中的
                labelColor: Theme.of(context).primaryColor,
                //labelColor: Colors.green,
                unselectedLabelColor: const Color(0xFF8E8E8E),
                tabs: myTabs),
          ),
        ),
      ),

這里通過(guò)controllerTabBarViewTabBar關(guān)聯(lián)起來(lái)進(jìn)行聯(lián)動(dòng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372