往期
上期回顧
上期主要完成環(huán)境的搭建和部署,最終在jetpack.net.cn地址上呈現(xiàn),這期我們就開始搭建主頁(yè),構(gòu)建一個(gè)可以兼容三端(Android、Ios、Web)的主頁(yè)。
開始
在lib文件夾下創(chuàng)建home.dart文件,如圖

打開文件,輸入st出來(lái)的提示選擇第一個(gè),如圖

這樣就自動(dòng)生成一個(gè)完整的模版代碼,一個(gè)小小的技巧,請(qǐng)笑納。最后命名PageHome,這里簡(jiǎn)單說(shuō)下命名規(guī)則,只是建議,頁(yè)面以Page開頭,非頁(yè)面以Widget開頭,這樣在其他地方引用的時(shí)候容易尋找,提高查找效率。接下來(lái),我們就用一個(gè)兼容三端的組件來(lái)構(gòu)建主頁(yè),不賣關(guān)子,我們使用Material主題,因?yàn)槲沂茿ndroid開發(fā),更喜歡這個(gè)主題,我們?cè)谛略鼋M件的時(shí)候總感覺(jué)很麻煩,其實(shí)也有個(gè)小技巧,如圖

選第一個(gè)就可以生成一個(gè)包裹組件,繼續(xù)看圖

然后將widget改為Material。然后添加Padding,Scaffold組件如圖

Padding的用意很簡(jiǎn)單,加一個(gè)左右邊距,而在web,和手機(jī)上的邊距是不一樣的,不能固定死,我畫個(gè)圖展示展示下,如圖


所以使用ResponsiveWidget判斷是否是小屏幕,如果屏幕變小則使用小邊距來(lái)適配,這里注意是小屏幕,不是針對(duì)的Android或者蘋果手機(jī),要注意不能混淆理解,瀏覽器也可以縮小到手機(jī)屏幕大小對(duì)吧,要理解ResponsiveWidget請(qǐng)看前期博客:一個(gè)Flutter widget自動(dòng)適配不同UI到Web、Android
還有一個(gè)ScreenUtil,這個(gè)你可以理解為相當(dāng)于Android中的dp單位,他負(fù)責(zé)的就是等比適配,不會(huì)讓大小分辨率的屏幕看起來(lái)差距很大。這個(gè)工具有個(gè)初始化的過(guò)程
ScreenUtil.instance = ScreenUtil.getInstance()..init(context);
而且初始化必須在MaterialApp包裹中,為什么呢?因?yàn)槲覀兪褂昧薓ediaQuery動(dòng)態(tài)獲取屏幕的寬高,詳細(xì)請(qǐng)看官方文檔https://api.flutter.dev/flutter/widgets/MediaQuery-class.html里面講的很清楚:
WidgetsApp and MaterialApp, which introduce a MediaQuery and keep it up to date with the current screen metrics as they change.
目前WidgetsApp和MaterialApp實(shí)現(xiàn)了MediaQuery的邏輯,所以你不在MaterialApp中初始化是會(huì)報(bào)錯(cuò)地,知道了吧。接下來(lái)看下實(shí)際運(yùn)行效果,我們將內(nèi)容設(shè)置成紅色來(lái)看,如圖

屏幕縮小后如圖

這就是我們要的效果,其實(shí)已經(jīng)有了兩個(gè)框架封裝的很完整,但前期我們?yōu)槭裁礇](méi)有選擇引用它們呢,而且他們實(shí)現(xiàn)的似乎更合理,請(qǐng)看他們的代碼風(fēng)格如圖

一個(gè)ScreenTypeLayout這個(gè)組件實(shí)現(xiàn)了四個(gè)屏幕的適配,框架引用:
responsive_builder: ^0.1.5
我為什么沒(méi)有選擇直接使用呢?其實(shí)我們?cè)诿靼灼渲性砗蟠_實(shí)可以直接引用,但我還是建議自己寫一遍,通過(guò)自己的實(shí)現(xiàn),更清楚其中的道理不是嗎。前期的學(xué)習(xí)過(guò)程中,我們盡量的不去引用,選擇自己實(shí)現(xiàn),也是加快提高自己的一個(gè)辦法。多多學(xué)習(xí),多多練習(xí)。接下來(lái)我們實(shí)現(xiàn)Scaffold部分,Scaffold這個(gè)組件太好用了,先看下它原本的樣子

,它就像一個(gè)大的容器,幫住我們組織好了各個(gè)組件的位置,是對(duì)一個(gè)基礎(chǔ)UI的高度抽象。
簡(jiǎn)單分析下它的參數(shù)源碼

其實(shí)這里最主要的三個(gè)常用的部分appBar、body、floatingActionButton,正好是我們構(gòu)建一個(gè)UI的常用構(gòu)造,所以這個(gè)組件基本是我們使用頻率很高的一個(gè)組件,請(qǐng)認(rèn)真學(xué)習(xí)它,更詳細(xì)的學(xué)習(xí),請(qǐng)看https://api.flutter.dev/flutter/material/Scaffold-class.html,這里面有幾個(gè)例子,認(rèn)真學(xué)習(xí)哦,我們現(xiàn)在通過(guò)它,來(lái)構(gòu)建我們的主頁(yè)代碼,請(qǐng)看效果圖:

我們先構(gòu)建了AppBar的內(nèi)容,怎么實(shí)現(xiàn)的呢,請(qǐng)看代碼
@override
Widget build(BuildContext context) {
ScreenUtil.instance = ScreenUtil.getInstance()..init(context);
return Material(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: !ResponsiveWidget.isSmallScreen(context)
? (ScreenUtil.getInstance().setWidth(108))
: (ScreenUtil.getInstance().setWidth(6))),
child: Scaffold(
appBar: AppBar(
title: _buildTitle(), /// 左邊標(biāo)題
backgroundColor: Color(0xFFf1f3f4),
actions: !ResponsiveWidget.isSmallScreen(context)
? _buildActions(context)
: null, /// 右邊的menu菜單,這里在小屏幕不顯示。
),
body: Center(child: Text('You have pressed the button $_count times.')),
),
));
}
Widget _buildTitle() {
return RichText(
text: TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: "Jetpack",
style: TextStyles.logo,
),
TextSpan(
text: ".net.cn",
style: TextStyles.logo.copyWith(
color: Color(0xFF50AFC0),
),
),
],
),
);
}
_buildActions(BuildContext context) {
return <Widget>[
MaterialButton(
child: Text(
'Home',
style: TextStyles.menuItem,
),
onPressed: () {
if (ResponsiveWidget.isSmallScreen(context)) Navigator.pop(context);
},
),
MaterialButton(
child: Text(
'About',
style: TextStyles.menuItem,
),
onPressed: () {
if (ResponsiveWidget.isSmallScreen(context)) Navigator.pop(context);
},
),
];
}
分別通過(guò)_buildTitle,_buildActions 實(shí)現(xiàn)左邊的標(biāo)題,右邊的菜單按鈕,里面有個(gè)細(xì)節(jié):

大屏幕的返回_buildActions,小屏幕直接隱藏,這里你可能有疑問(wèn),為什么不用drawer實(shí)現(xiàn)菜單?drawer適合小屏幕實(shí)現(xiàn),而大屏慕我們就要充分利用空間,盡可能的操作簡(jiǎn)單,一目了然。下面我們來(lái)實(shí)現(xiàn),小屏幕的drawer,請(qǐng)看代碼
///省略部分代碼
Scaffold(
///省略部分代碼
drawer: _buildDrawer(context),
),
_buildDrawer(BuildContext context) {
return ResponsiveWidget.isSmallScreen(context)
? Drawer(
child: ListView(
padding: const EdgeInsets.all(20),
children: _buildActions(context),
),
)
: null;
}
給Scaffold添加一個(gè)drawer組件,這里用ListView把剛才的_buildActions組件再放進(jìn)來(lái),原來(lái)是以橫向展示,這次因?yàn)長(zhǎng)istView默認(rèn)是縱向,所以就是上下的展示方式,運(yùn)行項(xiàng)目后,如圖:

未展開的樣子,標(biāo)題右邊的菜單消失

展開的樣子,這就是我們要實(shí)現(xiàn)的小屏幕的樣子。也符合手機(jī)的使用習(xí)慣。

有沒(méi)有發(fā)現(xiàn)這個(gè)東東顯示看不清楚,接下來(lái)我們改造它,可我沒(méi)做過(guò),我怎么查呢?我這里分享我的經(jīng)驗(yàn),首先你要明白這里的關(guān)鍵字是什么,當(dāng)我鼠標(biāo)停留在上面的時(shí)候提示如圖

是一個(gè)navigation menu,所以我會(huì)在google里搜索,flutter scaffold drawer navigation menu,這么幾個(gè)關(guān)鍵字,然后搜到的如圖

,第一個(gè)是官方文檔如何添加,第二個(gè)是custom drawer,自定義的,有可能講到了如何修改,所以我就點(diǎn)進(jìn)去尋找

看到?jīng)],還是很好找的哈,然后copy過(guò)來(lái)代碼,發(fā)現(xiàn)它用的圖不對(duì),我們修改下,如圖,這樣是不是看著舒服了。

接下來(lái)就是給Scaffold的body填充內(nèi)容了,先看實(shí)現(xiàn)的效果
Home頁(yè)面黃色

About頁(yè)藍(lán)色

點(diǎn)擊切換,具體實(shí)現(xiàn)請(qǐng)看下面代碼
int _selectedDrawerIndex = 0;
///定義一個(gè)坐標(biāo)值
/// 添加動(dòng)態(tài)的body _getDrawerItemWidget
child: Scaffold(
body: _getDrawerItemWidget(_selectedDrawerIndex),
),
/// 省略部分代碼
/// 根據(jù)一個(gè)index值,來(lái)確定加載哪個(gè)頁(yè)面
_getDrawerItemWidget(int selectedDrawerIndex) {
switch(selectedDrawerIndex){
case 0:
return WidgetMenuHome();
break;
case 1:
return WidgetMenuAbout();
break;
}
}
/// 在_buildActions函數(shù)中的組件onPressed的時(shí)候調(diào)用,這樣就可以更新UI
setState(() {
_selectedDrawerIndex = 0;
});
好了這期我們就先這樣,學(xué)習(xí)到此結(jié)束,下期繼續(xù)。
總結(jié)
這次主要是對(duì)Scaffold的使用,以及如何構(gòu)建不同平臺(tái)的UI,確切說(shuō)是不同寬度屏幕的構(gòu)建,實(shí)現(xiàn)了大屏幕的Menu菜單,和小屏幕的Drawer菜單顯示,而且沒(méi)有考慮分包,下期將進(jìn)行分包設(shè)計(jì),并往頁(yè)面里面填充內(nèi)容,來(lái)構(gòu)建我們的需求UI 實(shí)現(xiàn)一個(gè)Flutter Jetpack,并把之前做的Android Jetpack也挪過(guò)來(lái)。
項(xiàng)目開源鏈接
Android Jetpack WebSite
Flutter Jetpack WebSite
Flutter Jetpack Github Source Code
Android Jetpack Github Source Code