Flutter Go 源碼分析(二)

(5) AppPage()基礎頁面

AppPage是整個App的入口,在這里實現Tabbar、SearchBar等基礎控件。
在分析AppPage頁面之前先說一下Scaffold這個widget,這里我們可以把它理解為頁面,類似OC里面的UIViewController:

    this.appBar, //橫向水平布局,通常顯示在頂部(*)
    this.body, // 內容(*)
    this.floatingActionButton, //懸浮按鈕,就是上圖右下角按鈕(*)
    this.floatingActionButtonLocation, //懸浮按鈕位置
    //懸浮按鈕在[floatingActionButtonLocation]出現/消失動畫
    this.floatingActionButtonAnimator, 
    //在底部呈現一組button,顯示于[bottomNavigationBar]之上,[body]之下
    this.persistentFooterButtons,
    //一個垂直面板,顯示于左側,初始處于隱藏狀態(*)
    this.drawer,
    this.endDrawer,
    //出現于底部的一系列水平按鈕(*)
    this.bottomNavigationBar,
    //底部持久化提示框
    this.bottomSheet,
    //內容背景顏色
    this.backgroundColor,
    //棄用,使用[resizeToAvoidBottomInset]
    this.resizeToAvoidBottomPadding,
    //重新計算布局空間大小
    this.resizeToAvoidBottomInset,
    //是否顯示到底部,默認為true將顯示到頂部狀態欄
    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), //底部導航欄主題顏色
        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標簽的下劃線顏色
                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),
          ),
        ),
      ),
    );

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

  1. buildSearchInput
    buildSearchInput函數里是創建的SearchInput對象:
    構造函數:
  final getResults;//獲取搜索內容函數

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

  final VoidCallback onSubmitPressed;//沒有用到

  SearchInput(this.getResults, this.onSubmitted, this.onSubmitPressed);//構造函數

build函數除了MaterialSearchInput之外都是一些基礎wiget布局,其他不做闡述,我們來看一下MaterialSearchInput
build函數:

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

可以看到這搜索框是有一個FormField
來實現的,這里實例化對象的時候只用到了getResultsplaceholder我暫時只對這兩個做說明,其他屬性如果有感興趣的同學可以自行去了解。
接下來我們主要研究_showMaterialSearch,也就是點擊之后跳轉的搜索頁面。

_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函數:

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)
    用一個TextEditingController來配合監聽輸入框的文字變化
_controller.addListener(() {
      setState(() {
        _criteria = _controller.value.text;
        if (widget.getResults != null) {
          _getResultsDebounced();
        }
      });
    });

通過構造函數傳進來的getResults來去數據庫獲取搜索結果。

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

    if (_resultsTimer != null && _resultsTimer.isActive) {
      _resultsTimer.cancel();
    }
    //延遲400毫秒再執行
    _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) {//如果沒有搜索關鍵字則顯示歷史記錄
      return History();
    } else if (_loading) {//正在搜索顯示加載框
      return new Center(
          child: new Padding(
              padding: const EdgeInsets.only(top: 50.0),
              child: new CircularProgressIndicator()
          )
      );
    }
    if (results.isNotEmpty) {//如果有搜索結果就顯示搜索列表
      var content = new SingleChildScrollView(
          child: new Column(
            children: results
          )
      );
      return content;
    }
    return Center(child: Text("暫無數據"));//這個是有搜索關鍵字而沒有搜索結果的時候顯示暫無數據
  }

看一下搜索歷史記錄頁面:

  • History()
    build函數:
Widget build(BuildContext context) {
    //獲取歷史記錄的widget list
    List<Widget> childList = buildChips(context);
    if (childList.length == 0) {//如果沒有歷史記錄 
      return Center(
        child: Text("當前歷史面板為空"),
      );
    }
    return Column(//有歷史記錄
      children: <Widget>[
        Container(//頭部 歷史搜索文字
          alignment: Alignment.centerLeft,
          padding: EdgeInsets.fromLTRB(12.0, 12, 12, 0),
          child: InkWell(
            onLongPress: () {//長按情況搜索歷史記錄
              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 = [];//存儲搜索列表widget
    List<SearchHistory> historyList = searchHistoryList.getList();//獲取搜索記錄數據(SearchHistory)
    print("historyList> $historyList");
    Color bgColor = Theme.of(context).primaryColor;
    historyList.forEach((SearchHistory value) {//遍歷歷史記錄數據 轉成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: () {//跳轉
            Application.router.navigateTo(context, "${value.targetRouter}", transition: TransitionType.inFromRight);
          },
          child: Chip(
            avatar: icon,
            label: Text("${value.name}"),
          ),
        )
      );
    });
    return list;
  }

代碼都已做了詳細注釋,不做過多解釋了。

  • 搜索結果列表
    首先我們看results數據:
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的實例對象,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)
          ],
        ),
      ),
    );
  }

跳轉代碼:

oid onWidgetTap(WidgetPoint widgetPoint, BuildContext context) {
    List widgetDemosList = new WidgetDemoList().getDemos();//獲取所有注冊過的demo頁面
    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");
  }

這也搜索結果列表的邏輯也就出來了。

  1. TabBarView
    這個在稍后我們進行詳細的分開拆解。
  2. bottomNavigationBar
bottomNavigationBar: Material(
        color: const Color(0xFFF0EEEF), //底部導航欄主題顏色
        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標簽的下劃線顏色
                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),
          ),
        ),
      ),

這里通過controllerTabBarViewTabBar關聯起來進行聯動。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。