做爲系列文章的第二篇,繼《Flutter完整開發實戰詳解(1、Dart語言和Flutter基礎)》以後,本篇將爲你着重展現:如何搭建一個通用的Flutter App 經常使用功能腳手架,快速開發一個完整的 Flutter 應用。前端
友情提示:本文全部代碼均在 GSYGithubAppFlutter ,文中示例代碼都可在其中找到,看完本篇相信你應該能夠輕鬆完成以下效果。相關基礎還請看篇章一。android
本篇內容結構以下圖,主要分爲: 基礎控件、數據模塊、其餘功能 三部分。每大塊中的小模塊,除了涉及的功能實現外,對於實現過程當中筆者遇到的問題,會一併展開闡述。本系列的最終目的是: 讓你感覺 Flutter 的愉悅! 那麼就讓咱們愉悅的往下開始吧!(◐‿◑)ios
所謂的基礎,大概就是砍柴功了吧!git
Tabbar 頁面是常有需求,而在Flutter中: Scaffold + AppBar + Tabbar + TabbarView 是 Tabbar 頁面的最簡單實現,但在加上 AutomaticKeepAliveClientMixin
用於頁面 keepAlive 以後,諸如#11895的問題便開始成爲Crash的元兇。直到 flutter v0.5.7 sdk 版本修復後,問題依舊沒有徹底解決,因此無奈最終修改了實現方案。github
目前筆者是經過 Scaffold + Appbar + Tabbar + PageView 來組合實現效果,從而解決上述問題。由於該問題較爲常見,因此目前已經單獨實現了測試Demo,有興趣的能夠看看 TabBarWithPageView。sql
下面咱們直接代碼走起,首先做爲一個Tabbar Widget,它確定是一個 StatefulWidget
,因此咱們先實現它的 State
:數據庫
class _GSYTabBarState extends State<GSYTabBarWidget> with SingleTickerProviderStateMixin {
///···省略非關鍵代碼
@override
void initState() {
super.initState();
///初始化時建立控制器
///經過 with SingleTickerProviderStateMixin 實現動畫效果。
_tabController = new TabController(vsync: this, length: _tabItems.length);
}
@override
void dispose() {
///頁面銷燬時,銷燬控制器
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
///底部TAbBar模式
return new Scaffold(
///設置側邊滑出 drawer,不須要能夠不設置
drawer: _drawer,
///設置懸浮按鍵,不須要能夠不設置
floatingActionButton: _floatingActionButton,
///標題欄
appBar: new AppBar(
backgroundColor: _backgroundColor,
title: _title,
),
///頁面主體,PageView,用於承載Tab對應的頁面
body: new PageView(
///必須有的控制器,與tabBar的控制器同步
controller: _pageController,
///每個 tab 對應的頁面主體,是一個List<Widget>
children: _tabViews,
onPageChanged: (index) {
///頁面觸摸做用滑動回調,用於同步tab選中狀態
_tabController.animateTo(index);
},
),
///底部導航欄,也就是tab欄
bottomNavigationBar: new Material(
color: _backgroundColor,
///tabBar控件
child: new TabBar(
///必須有的控制器,與pageView的控制器同步
controller: _tabController,
///每個tab item,是一個List<Widget>
tabs: _tabItems,
///tab底部選中條顏色
indicatorColor: _indicatorColor,
),
));
}
}
複製代碼
如上代碼所示,這是一個 底部 TabBar 的頁面的效果。TabBar 和 PageView 之間經過 _pageController
和 _tabController
實現 Tab 和頁面的同步,經過 SingleTickerProviderStateMixin
實現 Tab 的動畫切換效果 (ps 若是有須要多個嵌套動畫效果,你可能須要TickerProviderStateMixin
)。 從代碼中咱們能夠看到:json
手動左右滑動 PageView
時,經過 onPageChanged
回調調用 _tabController.animateTo(index);
同步TabBar狀態。redux
_tabItems 中,監聽每一個 TabBarItem 的點擊,經過 _pageController
實現PageView的狀態同步。bash
而上面代碼還缺乏了 TabBarItem 的點擊,由於這塊被放到了外部實現。固然你也能夠直接在內部封裝好控件,直接傳遞配置數據顯示,這個能夠根據我的須要封裝。
外部調用代碼以下:每一個 Tabbar 點擊時,經過pageController.jumpTo
跳轉頁面,每一個頁面須要跳轉座標爲:當前屏幕大小乘以索引 index 。
class _TabBarBottomPageWidgetState extends State<TabBarBottomPageWidget> {
final PageController pageController = new PageController();
final List<String> tab = ["動態", "趨勢", "個人"];
///渲染底部Tab
_renderTab() {
List<Widget> list = new List();
for (int i = 0; i < tab.length; i++) {
list.add(new FlatButton(onPressed: () {
///每一個 Tabbar 點擊時,經過jumpTo 跳轉頁面
///每一個頁面須要跳轉座標爲:當前屏幕大小 * 索引index。
topPageControl.jumpTo(MediaQuery
.of(context)
.size
.width * i);
}, child: new Text(
tab[i],
maxLines: 1,
)));
}
return list;
}
///渲染Tab 對應頁面
_renderPage() {
return [
new TabBarPageFirst(),
new TabBarPageSecond(),
new TabBarPageThree(),
];
}
@override
Widget build(BuildContext context) {
///帶 Scaffold 的Tabbar頁面
return new GSYTabBarWidget(
type: GSYTabBarWidget.BOTTOM_TAB,
///渲染tab
tabItems: _renderTab(),
///渲染頁面
tabViews: _renderPage(),
topPageControl: pageController,
backgroundColor: Colors.black45,
indicatorColor: Colors.white,
title: new Text("GSYGithubFlutter"));
}
}
複製代碼
若是到此結束,你會發現頁面點擊切換時,StatefulWidget
的子頁面每次都會從新調用initState
。這確定不是咱們想要的,因此這時你就須要AutomaticKeepAliveClientMixin
。
每一個 Tab 對應的 StatefulWidget
的 State ,須要經過with AutomaticKeepAliveClientMixin
,而後重寫 @override bool get wantKeepAlive => true;
,就能夠實不從新構建的效果了,效果以下圖。
既然底部Tab頁面都實現了,乾脆頂部tab頁面也一塊兒完成。以下代碼,和底部Tab頁的區別在於:
Scaffold
的 bottomNavigationBar
中。AppBar
的 bottom
中,也就是標題欄之下。 同時咱們在頂部 TabBar 增長 isScrollable: true
屬性,實現常見的頂部Tab的效果,以下方圖片所示。
return new Scaffold(
///設置側邊滑出 drawer,不須要能夠不設置
drawer: _drawer,
///設置懸浮按鍵,不須要能夠不設置
floatingActionButton: _floatingActionButton,
///標題欄
appBar: new AppBar(
backgroundColor: _backgroundColor,
title: _title,
///tabBar控件
bottom: new TabBar(
///頂部時,tabBar爲能夠滑動的模式
isScrollable: true,
///必須有的控制器,與pageView的控制器同步
controller: _tabController,
///每個tab item,是一個List<Widget>
tabs: _tabItems,
///tab底部選中條顏色
indicatorColor: _indicatorColor,
),
),
///頁面主體,PageView,用於承載Tab對應的頁面
body: new PageView(
///必須有的控制器,與tabBar的控制器同步
controller: _pageController,
///每個 tab 對應的頁面主體,是一個List<Widget>
children: _tabViews,
///頁面觸摸做用滑動回調,用於同步tab選中狀態
onPageChanged: (index) {
_tabController.animateTo(index);
},
),
);
複製代碼
在 TabBar 頁面中,通常還會出現:父頁面須要控制 PageView 中子頁的需求。這時候就須要用到GlobalKey
了。好比 GlobalKey<PageOneState> stateOne = new GlobalKey<PageOneState>();
,經過 globalKey.currentState 對象,你就能夠調用到 PageOneState 中的公開方法。這裏須要注意 GlobalKey
須要全局惟一,通常能夠在build
方法中建立。
毫無爭議,必備控件。Flutter 中 爲咱們提供了 RefreshIndicator
做爲內置下拉刷新控件;同時咱們經過給 ListView
添加 ScrollController
作滑動監聽,在最後增長一個 Item, 做爲上滑加載更多的 Loading 顯示。
以下代碼所示,經過 RefreshIndicator
控件能夠簡單完成下拉刷新工做。這裏須要注意一點是:能夠利用 GlobalKey<RefreshIndicatorState>
對外提供 RefreshIndicator
的 RefreshIndicatorState
,這樣外部就 能夠經過 GlobalKey 調用 globalKey.currentState.show();
,主動顯示刷新狀態並觸發 onRefresh
。
上拉加載更多在代碼中是經過 _getListCount()
方法,在本來的數據基礎上,增長實際須要渲染的 item 數量給 ListView 實現的,最後經過 ScrollController
監聽到底部,觸發 onLoadMore
。
以下代碼所示,經過 _getListCount()
方法,還能夠配置空頁面,頭部等經常使用效果。其實就是在內部經過改變實際item數量與渲染Item,以實現更多配置效果。
class _GSYPullLoadWidgetState extends State<GSYPullLoadWidget> {
///···
final ScrollController _scrollController = new ScrollController();
@override
void initState() {
///增長滑動監聽
_scrollController.addListener(() {
///判斷當前滑動位置是否是到達底部,觸發加載更多回調
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
if (this.onLoadMore != null && this.control.needLoadMore) {
this.onLoadMore();
}
}
});
super.initState();
}
///根據配置狀態返回實際列表數量
///實際上這裏能夠根據你的須要作更多的處理
///好比多個頭部,是否須要空頁面,是否須要顯示加載更多。
_getListCount() {
///是否須要頭部
if (control.needHeader) {
///若是須要頭部,用Item 0 的 Widget 做爲ListView的頭部
///列表數量大於0時,由於頭部和底部加載更多選項,須要對列表數據總數+2
return (control.dataList.length > 0) ? control.dataList.length + 2 : control.dataList.length + 1;
} else {
///若是不須要頭部,在沒有數據時,固定返回數量1用於空頁面呈現
if (control.dataList.length == 0) {
return 1;
}
///若是有數據,由於部加載更多選項,須要對列表數據總數+1
return (control.dataList.length > 0) ? control.dataList.length + 1 : control.dataList.length;
}
}
///根據配置狀態返回實際列表渲染Item
_getItem(int index) {
if (!control.needHeader && index == control.dataList.length && control.dataList.length != 0) {
///若是不須要頭部,而且數據不爲0,當index等於數據長度時,渲染加載更多Item(由於index是從0開始)
return _buildProgressIndicator();
} else if (control.needHeader && index == _getListCount() - 1 && control.dataList.length != 0) {
///若是須要頭部,而且數據不爲0,當index等於實際渲染長度 - 1時,渲染加載更多Item(由於index是從0開始)
return _buildProgressIndicator();
} else if (!control.needHeader && control.dataList.length == 0) {
///若是不須要頭部,而且數據爲0,渲染空頁面
return _buildEmpty();
} else {
///回調外部正常渲染Item,若是這裏有須要,能夠直接返回相對位置的index
return itemBuilder(context, index);
}
}
@override
Widget build(BuildContext context) {
return new RefreshIndicator(
///GlobalKey,用戶外部獲取RefreshIndicator的State,作顯示刷新
key: refreshKey,
///下拉刷新觸發,返回的是一個Future
onRefresh: onRefresh,
child: new ListView.builder(
///保持ListView任何狀況都能滾動,解決在RefreshIndicator的兼容問題。
physics: const AlwaysScrollableScrollPhysics(),
///根據狀態返回子孔健
itemBuilder: (context, index) {
return _getItem(index);
},
///根據狀態返回數量
itemCount: _getListCount(),
///滑動監聽
controller: _scrollController,
),
);
}
///空頁面
Widget _buildEmpty() {
///···
}
///上拉加載更多
Widget _buildProgressIndicator() {
///···
}
}
複製代碼
在上一小節中,咱們實現上滑加載更多的效果,其中就須要展現 Loading 狀態的需求。默認系統提供了CircularProgressIndicator
等,可是有追求的咱們怎麼可能侷限於此,這裏推薦一個第三方 Loading 庫 :flutter_spinkit ,經過簡單的配置就可使用豐富的 Loading 樣式。
繼續上一小節中的 _buildProgressIndicator
方法實現,經過 flutter_spinkit 能夠快速實現更不同的 Loading 樣式。
///上拉加載更多
Widget _buildProgressIndicator() {
///是否須要顯示上拉加載更多的loading
Widget bottomWidget = (control.needLoadMore)
? new Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
///loading框
new SpinKitRotatingCircle(color: Color(0xFF24292E)),
new Container(
width: 5.0,
),
///加載中文本
new Text(
"加載中···",
style: TextStyle(
color: Color(0xFF121917),
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
)
])
/// 不須要加載
: new Container();
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new Center(
child: bottomWidget,
),
);
}
複製代碼
矢量圖標對筆者是必不可少的。比起通常的 png 圖片文件,矢量圖標在開發過程當中:能夠輕鬆定義顏色,而且任意調整大小不模糊。矢量圖標庫是引入 ttf 字體庫文件實現,在 Flutter 中經過 Icon
控件,加載對應的 IconData
顯示便可。
Flutter 中默認內置的 Icons
類就提供了豐富的圖標,直接經過 Icons
對象便可使用,同時我的推薦阿里爸爸的 iconfont 。以下代碼,經過在 pubspec.yaml
中添加字體庫支持,而後在代碼中建立 IconData
指向字體庫名稱引用便可。
fonts:
- family: wxcIconFont
fonts:
- asset: static/font/iconfont.ttf
··················
///使用Icons
new Tab(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[new Icon(Icons.list, size: 16.0), new Text("趨勢")],
),
),
///使用iconfont
new Tab(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[new Icon(IconData(0xe6d0, fontFamily: "wxcIconFont"), size: 16.0), new Text("個人")],
),
)
複製代碼
Flutter 中的頁面跳轉是經過 Navigator
實現的,路由跳轉又分爲:帶參數跳轉和不帶參數跳轉。不帶參數跳轉比較簡單,默承認以經過 MaterialApp 的路由表跳轉;而帶參數的跳轉,參數經過跳轉頁面的構造方法傳遞。經常使用的跳轉有以下幾種使用:
///不帶參數的路由表跳轉
Navigator.pushNamed(context, routeName);
///跳轉新頁面而且替換,好比登陸頁跳轉主頁
Navigator.pushReplacementNamed(context, routeName);
///跳轉到新的路由,而且關閉給定路由的以前的全部頁面
Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));
///帶參數的路由跳轉,而且監聽返回
Navigator.push(context, new MaterialPageRoute(builder: (context) => new NotifyPage())).then((res) {
///獲取返回處理
});
複製代碼
同時咱們能夠看到,Navigator 的 push 返回的是一個 Future
,這個Future
的做用是在頁面返回時被調用的。也就是你能夠經過 Navigator
的 pop
時返回參數,以後在 Future
中能夠的監聽中處理頁面的返回結果。
@optionalTypeArgs
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
複製代碼
數據爲王,不過應該不是隔壁老王吧。
當前 Flutter 網絡請求封裝中,國內最受歡迎的就是 Dio 了,Dio 封裝了網絡請求中的數據轉換、攔截器、請求返回等。以下代碼所示,經過對 Dio 的簡單封裝便可快速網絡請求,真的很簡單,更多的能夠查 Dio 的官方文檔,這裏就不展開了。(真的不是懶(˶‾᷄ ⁻̫ ‾᷅˵))
///建立網絡請求對象
Dio dio = new Dio();
Response response;
try {
///發起請求
///url地址,請求數據,通常爲Map或者FormData
///options 額外配置,能夠配置超時,頭部,請求類型,數據響應類型,host等
response = await dio.request(url, data: params, options: option);
} on DioError catch (e) {
///http錯誤是經過 DioError 的catch返回的一個對象
}
複製代碼
在 Flutter 中,json 序列化是有些特殊的。不一樣與 JS ,好比使用上述 Dio 網絡請求返回,若是配置了返回數據格式爲 json ,實際上的到會是一個Map。而 Map 的 key-value 使用,在開發過程當中並非很方便,因此你須要對Map 再進行一次轉化,轉爲實際的 Model 實體。
因此 json_serializable
插件誕生了, 中文網Json 對其已有一段教程,這裏主要補充說明下具體的使用邏輯。
dependencies:
# Your other regular dependencies here
json_annotation: ^0.2.2
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^0.7.6
json_serializable: ^0.3.2
複製代碼
以下發代碼所示:
建立你的實體 Model 以後,繼承 Object 、而後經過 @JsonSerializable()
標記類名。
經過 with _$TemplateSerializerMixin
,將 fromJson
方法委託到 Template.g.dart
的實現中。 其中 *.g.dart
、_$* SerializerMixin
、_$*FromJson
這三個的引入, 和 Model 所在的 dart 的文件名與 Model 類名有關,具體可見代碼註釋和後面圖片。
最後經過 flutter packages pub run build_runner build
編譯自動生成轉化對象。(我的習慣完成後手動編譯)
import 'package:json_annotation/json_annotation.dart';
///關聯文件、容許Template訪問 Template.g.dart 中的私有方法
///Template.g.dart 是經過命令生成的文件。名稱爲 xx.g.dart,其中 xx 爲當前 dart 文件名稱
///Template.g.dart中建立了抽象類_$TemplateSerializerMixin,實現了_$TemplateFromJson方法
part 'Template.g.dart';
///標誌class須要實現json序列化功能
@JsonSerializable()
///'xx.g.dart'文件中,默認會根據當前類名如 AA 生成 _$AASerializerMixin
///因此當前類名爲Template,生成的抽象類爲 _$TemplateSerializerMixin
class Template extends Object with _$TemplateSerializerMixin {
String name;
int id;
///經過JsonKey從新定義參數名
@JsonKey(name: "push_id")
int pushId;
Template(this.name, this.id, this.pushId);
///'xx.g.dart'文件中,默認會根據當前類名如 AA 生成 _$AAeFromJson方法
///因此當前類名爲Template,生成的抽象類爲 _$TemplateFromJson
factory Template.fromJson(Map<String, dynamic> json) => _$TemplateFromJson(json);
}
複製代碼
上述操做生成後的 Template.g.dart
下的代碼以下,這樣咱們就能夠經過 Template.fromJson
和toJson
方法對實體與map進行轉化,再結合json.decode
和 json.encode
,你就能夠愉悅的在string 、map、實體間相互轉化了。
part of 'Template.dart';
Template _$TemplateFromJson(Map<String, dynamic> json) => new Template(
json['name'] as String, json['id'] as int, json['push_id'] as int);
abstract class _$TemplateSerializerMixin {
String get name;
int get id;
int get pushId;
Map<String, dynamic> toJson() =>
<String, dynamic>{'name': name, 'id': id, 'push_id': pushId};
}
複製代碼
相信在前端領域、Redux 並非一個陌生的概念。做爲全局狀態管理機,用於 Flutter 中再合適不過。若是你沒據說過,Don't worry,簡單來講就是:它能夠跨控件管理、同步State 。因此 flutter_redux 等着你征服它。
你們都知道在 Flutter 中 ,是經過實現 State
與 setState
來渲染和改變 StatefulWidget
的。若是使用了flutter_redux
會有怎樣的效果?
好比把用戶信息存儲在 redux
的 store
中, 好處在於: 好比某個頁面修改了當前用戶信息,全部綁定的該 State 的控件將由 Redux 自動同步修改。State 能夠跨頁面共享。
更多 Redux 的詳細就再也不展開,接下來咱們講講 flutter_redux 的使用。在 redux 中主要引入了 action、reducer、store 概念。
因此以下代碼,咱們先建立一個 State 用於存儲須要保存的對象,其中關鍵代碼在於 UserReducer
。
///全局Redux store 的對象,保存State數據
class GSYState {
///用戶信息
User userInfo;
///構造方法
GSYState({this.userInfo});
}
///經過 Reducer 建立 用於store 的 Reducer
GSYState appReducer(GSYState state, action) {
return GSYState(
///經過 UserReducer 將 GSYState 內的 userInfo 和 action 關聯在一塊兒
userInfo: UserReducer(state.userInfo, action),
);
}
複製代碼
下面是上方使用的 UserReducer
的實現。這裏主要經過 TypedReducer
將 reducer 的處理邏輯與定義的 Action 綁定,最後經過 combineReducers
返回 Reducer<State>
對象應用於上方 Store 中。
/// redux 的 combineReducers, 經過 TypedReducer 將 UpdateUserAction 與 reducers 關聯起來
final UserReducer = combineReducers<User>([
TypedReducer<User, UpdateUserAction>(_updateLoaded),
]);
/// 若是有 UpdateUserAction 發起一個請求時
/// 就會調用到 _updateLoaded
/// _updateLoaded 這裏接受一個新的userInfo,並返回
User _updateLoaded(User user, action) {
user = action.userInfo;
return user;
}
///定一個 UpdateUserAction ,用於發起 userInfo 的的改變
///類名隨你喜歡定義,只要經過上面TypedReducer綁定就好
class UpdateUserAction {
final User userInfo;
UpdateUserAction(this.userInfo);
}
複製代碼
下面正式在 Flutter 中引入 store,經過 StoreProvider
將建立 的 store 引用到 Flutter 中。
void main() {
runApp(new FlutterReduxApp());
}
class FlutterReduxApp extends StatelessWidget {
/// 建立Store,引用 GSYState 中的 appReducer 建立的 Reducer
/// initialState 初始化 State
final store = new Store<GSYState>(appReducer, initialState: new GSYState(userInfo: User.empty()));
FlutterReduxApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
/// 經過 StoreProvider 應用 store
return new StoreProvider(
store: store,
child: new MaterialApp(
home: DemoUseStorePage(),
),
);
}
}
複製代碼
在下方 DemoUseStorePage 中,經過 StoreConnector
將State 綁定到 Widget;經過 StoreProvider.of
能夠獲取 state 對象;經過 dispatch
一個 Action 能夠更新State。
class DemoUseStorePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
///經過 StoreConnector 關聯 GSYState 中的 User
return new StoreConnector<GSYState, User>(
///經過 converter 將 GSYState 中的 userInfo返回
converter: (store) => store.state.userInfo,
///在 userInfo 中返回實際渲染的控件
builder: (context, userInfo) {
return new Text(
userInfo.name,
style: Theme.of(context).textTheme.display1,
);
},
);
}
}
·····
///經過 StoreProvider.of(context) (帶有 StoreProvider 下的 context)
/// 能夠任意的位置訪問到 state 中的數據
StoreProvider.of(context).state.userInfo;
·····
///經過 dispatch UpdateUserAction,能夠更新State
StoreProvider.of(context).dispatch(new UpdateUserAction(newUserInfo));
複製代碼
看到這是否是有點想靜靜了?先無論靜靜是誰,可是Redux的實用性是應該比靜靜更吸引人,做爲一個有追求的程序猿,多動手擼擼還有什麼拿不下的山頭是不?更詳細的實現請看:GSYGithubAppFlutter 。
在 GSYGithubAppFlutter 中,數據庫使用的是 sqflite 的封裝,其實就是 sqlite 語法的使用而已,有興趣的能夠看看完整代碼 DemoDb.dart 。 這裏主要提供一種思路,按照 sqflite 文檔提供的方法,從新作了一小些修改,經過定義 Provider 操做數據庫:
在 Provider 中定義表名與數據庫字段常量,用於建立表與字段操做;
提供數據庫與數據實體之間的映射,好比數據庫對象與User對象之間的轉化;
在調用 Provider 時才先判斷表是否建立,而後再返回數據庫對象進行用戶查詢。
若是結合網絡請求,經過閉包實現,在須要數據庫時先返回數據庫,而後經過 next
方法將網絡請求的方法返回,最後外部能夠經過調用next
方法再執行網絡請求。以下所示:
UserDao.getUserInfo(userName, needDb: true).then((res) {
///數據庫結果
if (res != null && res.result) {
setState(() {
userInfo = res.data;
});
}
return res.next;
}).then((res) {
///網絡結果
if (res != null && res.result) {
setState(() {
userInfo = res.data;
});
}
});
複製代碼
其餘功能,只是由於想不到標題。
Flutter 中 ,經過WillPopScope
嵌套,能夠用於監聽處理 Android 返回鍵的邏輯。其實 WillPopScope
並非監聽返回按鍵,如名字通常,是當前頁面將要被pop時觸發的回調。
經過onWillPop
回調返回的Future
,判斷是否響應 pop 。下方代碼實現按下返回鍵時,彈出提示框,按下肯定退出App。
class HomePage extends StatelessWidget {
/// 單擊提示退出
Future<bool> _dialogExitApp(BuildContext context) {
return showDialog(
context: context,
builder: (context) => new AlertDialog(
content: new Text("是否退出"),
actions: <Widget>[
new FlatButton(onPressed: () => Navigator.of(context).pop(false), child: new Text("取消")),
new FlatButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: new Text("肯定"))
],
));
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
///若是返回 return new Future.value(false); popped 就不會被處理
///若是返回 return new Future.value(true); popped 就會觸發
///這裏能夠經過 showDialog 彈出肯定框,在返回時經過 Navigator.of(context).pop(true);決定是否退出
return _dialogExitApp(context);
},
child: new Container(),
);
}
}
複製代碼
WidgetsBindingObserver
包含了各類控件的生命週期通知,其中的 didChangeAppLifecycleState
就能夠用於作先後臺狀態監聽。
/// WidgetsBindingObserver 包含了各類控件的生命週期通知
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
///重寫 WidgetsBindingObserver 中的 didChangeAppLifecycleState
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
///經過state判斷App先後臺切換
if (state == AppLifecycleState.resumed) {
}
}
@override
Widget build(BuildContext context) {
return new Container();
}
}
複製代碼
通常觸摸收起鍵盤也是常見需求,以下代碼所示, GestureDetector
+ FocusScope
能夠知足這一需求。
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
///定義觸摸層
return new GestureDetector(
///透明也響應處理
behavior: HitTestBehavior.translucent,
onTap: () {
///觸摸手氣鍵盤
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
),
);
}
}
複製代碼
IOS啓動頁,在ios/Runner/Assets.xcassets/LaunchImage.imageset/
下, 有 Contents.json 文件和啓動圖片,將你的啓動頁放置在這個目錄下,而且修改 Contents.json 便可,具體尺寸自行谷歌便可。
Android啓動頁,在 android/app/src/main/res/drawable/launch_background.xml
中已經有寫好的啓動頁,<item><bitmap>
部分被屏蔽,只須要打開這個屏蔽,而且將你啓動圖修改成launch_image
並放置到各個 mipmap 文件夾便可,記得各個文件夾下提供相對於大小尺寸的文件。
自此,第二篇終於結束了!(///▽///)
《Flutter完整開發實戰詳解(1、Dart語言和Flutter基礎)》