做为系列文章的第二篇,继《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基础)》