(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)依次拆解。
-
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í)候只用到了getResults
和placeholder
我暫時(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
-->MaterialSearch
build函數(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ì)象,MaterialSearchResult
build 方法:
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)了。
-
TabBarView
這個(gè)在稍后我們進(jìn)行詳細(xì)的分開拆解。 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ò)controller
將TabBarView
和TabBar
關(guān)聯(lián)起來(lái)進(jìn)行聯(lián)動(dòng)。