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: [
        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(
                  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 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(
        key: _formFieldKey,
        validator: widget.validator,
        onSaved: widget.onSaved,
        autovalidate: autovalidate,
        builder: (FormFieldState 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 childList = buildChips(context);
    if (childList.length == 0) {//如果没有历史记录 
      return Center(
        child: Text("当前历史面板为空"),
      );
    }
    return Column(//有历史记录
      children: [
        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 list = [];//存储搜索列表widget
    List 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 list = await widgetControl.search(value);

        return list
            .map((item) => new MaterialSearchResult(
                  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: [
            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(
                  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关联起来进行联动。

  • 下一篇

你可能感兴趣的:(Flutter Go 源码分析(二))