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关联起来进行联动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345