导航
import 'package:flutter/material.dart';
import 'home/Home.dart';
import 'study/Study.dart';
import 'mine/Mine.dart';
class Index extends StatefulWidget {
Index({Key key}) : super(key: key);
@override
_IndexState createState() => _IndexState();
}
class _IndexState extends State<Index> {
final List<BottomNavigationBarItem> bottomNavItems = [
BottomNavigationBarItem(
backgroundColor: Colors.blue,
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
backgroundColor: Colors.green,
icon: Icon(Icons.message),
label: '学习',
),
BottomNavigationBarItem(
backgroundColor: Colors.red,
icon: Icon(Icons.person),
label: '我',
),
];
final List pages = [
{
"appBar": AppBar(
title: Text("拉勾教育"),
centerTitle: true,
elevation: 0,
),
"page": Home(),
},
{
"appBar": AppBar(
title: Text("学习中心"),
centerTitle: true,
elevation: 0,
),
"page": Study(),
},
{
"appBar": AppBar(
title: Text("个人中心"),
centerTitle: true,
elevation: 0,
),
"page": Mine(),
},
];
int currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: pages[currentIndex]['appBar'],
bottomNavigationBar: BottomNavigationBar(
items: bottomNavItems,
currentIndex: currentIndex,
selectedItemColor: Colors.green,
type: BottomNavigationBarType.fixed,
onTap: (index) async {
setState(() {
currentIndex = index
});
},
),
body: pages[currentIndex ]['page']
);
}
}
Fluro 路由
-
企业级的路由框架 - Fluro
-
创建 lib/routes/RoutesHandler.dart
import 'package:flutter/material.dart'; import 'package:fluro/fluro.dart'; import '../pages/Index.dart'; import '../pages/notfound/NotFound.dart'; import '../pages/user/Login.dart'; import '../pages/mine/ProviderTest.dart'; import '../pages/course/CourseDetail.dart'; import '../pages/mine/Profile.dart'; import '../pages/pay/Pay.dart'; import '../pages/splash/Splash.dart'; /// 空页面 var notfoundHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return NotFound(); } ); /// 首页 var indexHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return Index(); } ); /// 登陆页 var loginHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return Login(); } ); /// Provider 功能测试页 var providerTestHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return ProviderTest(); } ); /// 课程详情 var courseDetailHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { print('详情参数'); print(params); return CourseDetail(id: int.parse(params['id'].first), title: params['title'].first); } ); /// 编辑个人信息 var profileHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return Profile(); } ); /// 支付页面 var payHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { print(params); return Pay(id: int.parse(params['id'].first), title: params['title'].first); } ); /// Splash 页 var splashHandler = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return Splash(); } );
-
声明路由
创建 lib/routes/Routes.dartimport 'package:fluro/fluro.dart'; import 'RoutesHandler.dart'; class Routes { static void configureRoutes(FluroRouter router) { router.define('/', handler: indexHandler); router.define('/login', handler: loginHandler); router.define('/provider_test', handler: providerTestHandler); router.define('/course_detail', handler: courseDetailHandler); router.define('/profile', handler: profileHandler); router.define('/pay', handler: payHandler); router.define('/splash', handler: splashHandler); router.notFoundHandler = notfoundHandler; } }
-
然后把路由相关的内容,也放到 lib/utils/Global.dart 中
import 'package:fluro/fluro.dart'; class G { /// 路由 static FluroRouter router; }
-
在入口文件(lib/main.dart)中初始化 router
import 'package:fluro/fluro.dart'; import 'routes/Routes.dart'; import 'utils/Global.dart'; void main() { // 初始化路由 FluroRouter router = new FluroRouter(); Routes.configureRoutes(router); // 初始化后的路由,放到全局组件中 G.router = router; }
状态管理
接口调用 - Dio
-
初始化 Dio
创建 lib/api/initDio.dartimport 'package:dio/dio.dart'; import '../utils/Global.dart'; import '../providers/UserProvider.dart'; import 'package:provider/provider.dart'; Dio initDio() { // 声明 Dio 的配置项 BaseOptions _baseOptions = BaseOptions( baseUrl: "http://eduboss.lagou.com", // 接口请求地址 connectTimeout: 5000, // 请求服务器的超时时间 ); Dio dio = Dio(_baseOptions); // 初始化 // 添加拦截器 dio.interceptors.add( InterceptorsWrapper( // 请求拦截 onRequest: (RequestOptions options) { print('请求之前进行拦截'); /// 将 access_token 封装到 header 中 var user = G.getCurrentContext().read<UserProvider>().user; if (user.isNotEmpty) { // print(user['access_token']); options.headers['Authorization'] = user['access_token']; } }, // 响应拦截 onResponse: (Response response) { print('响应之前进行拦截'); if (response.data == null || response.data['state'] != 1) { print('响应失败:'+response.data['message']); response.data = null; } return response; }, // 报错拦截 onError: (DioError e) { return e; } ) ); // 返回 dio return dio; }
-
使用 Dio
-
创建 lib/api/API.dart
import 'package:dio/dio.dart'; import 'InitDio.dart'; import 'AdAPI.dart'; class API { Dio _dio; API() { _dio = initDio(); } /// 广告接口 AdAPI get ad => AdAPI(_dio); }
-
创建 lib/api/API.dart
import 'package:dio/dio.dart'; class AdAPI { final Dio _dio; AdAPI(this._dio); /// 广告列表 = 首页 Future<dynamic> adList({ String spaceKeys = '999'}) async { Response res = await _dio.get('/front/ad/getAllAds', queryParameters: { "spaceKeys": spaceKeys } ); List adList = res.data['content'][0]['adDTOList']; return adList; } }
-
为了操作方便,我们可以把常用内容统一放到一个全局文件中
import 'package:flutter/material.dart'; import '../api/API.dart'; class G { /// 初始化 API static final API api = API(); }
-
屏幕适配 – flutter_screenutil
ScreenUtilInit(
designSize: Size(750, 1334), // 初始化设计尺寸
allowFontScaling: false, // 字体大小是否跟随终端
builder: () => MaterialApp(
navigatorKey: G.navigatorKey,
title: 'Flutter Demo',
// home: Index(),
onGenerateRoute: G.router.generator,
initialRoute: '/splash',
),
);
flutter_html – 富文本内容展示
-
配置 gradle-plugin 的中文镜像
- 修改两个文件
- Flutter_SDK/packages/flutter_tools/gradle/flutter.gradle
- Flutter 项目/android/build.gradle
- 添加一行内容
- maven{url’https://maven.aliyun.com/repository/gradle-plugin/’
- 修改两个文件
-
使用
import 'package:flutter_html/flutter_html.dart'; //...... Html(data: "<h1>标题1</h1>"); /// 在 Flutter 中展示 HTML 代码
image_picker (调用终端的摄像头)
详情查看:https://pub.dev/packages/image_picker
showModalBottomSheet (弹出底部列表)
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return renderBottomSheet(context);
}
);
Widget renderBottomSheet(BuildContext context) {
return Container(
height: 160,
child: Column(
children: [
InkWell(
onTap: () {
_takePhoto();
G.router.pop(context);
},
child: Container(
child: Text('拍照'),
height: 50,
alignment: Alignment.center,
)
),
InkWell(
onTap: () {
_openGallery();
G.router.pop(context);
},
child: Container(
child: Text('从相册中选取'),
height: 50,
alignment: Alignment.center,
)
),
Container(
color: Colors.grey[200],
height: 10,
),
InkWell(
onTap: () {
G.router.pop(context);
},
child: Container(
child: Text('取消'),
height: 50,
alignment: Alignment.center,
)
)
],
)
);
}
跳转到指定 URL 地址
-
在 APP 中,跳转到指定 URL 地址,需要使用第三方插件 url_launcher。
详情查看:https://pub.dev/packages/url_launcherimport 'package:url_launcher/url_launcher.dart'; /// ... doPay() { // 发起支付 G.api.order.createPay(orderNo: orderNo, channel: payment).then((value) { if (value != false) { print('支付成功'); print(value); /// 跳转到支付链接 _launchURL(value['payUrl']); } else { print('支付失败'); } }); } void _launchURL(_url) async => await canLaunch(_url) ? await launch(_url) : throw '不能跳转到 $_url';
Splash 页面
Splash 页面就是打开 APP 时,看到的第一个广告页
import 'package:flutter/material.dart';
import 'dart:async';
import '../../utils/Global.dart';
class Splash extends StatefulWidget {
Splash({Key key}) : super(key: key);
@override
_SplashState createState() => _SplashState();
}
class _SplashState extends State<Splash> {
Timer _timer;
int counter = 3;
/// 倒计时
countDown() async {
var _duration = Duration(seconds: 1);
Timer(_duration, () {
/// 等待1秒之后,再计时
_timer = Timer.periodic(_duration, (timer) {
counter--;
if (counter == 0) {
// 执行跳转
goHome();
} else {
setState(() {
// 标记当前组件为 dirty,然后触发 rebuild
});
}
});
return _timer;
});
}
void goHome() {
_timer.cancel();
G.router.navigateTo(context, '/');
}
@override
void initState() {
super.initState();
countDown(); // 指定倒计时
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment(1.0, -1.0),
children: [
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Image.asset(
"lib/assets/images/splash.jpeg",
fit: BoxFit.fill
)
),
Container(
color: Colors.grey,
margin: EdgeInsets.fromLTRB(0, 50, 10, 0),
padding: EdgeInsets.all(5),
child: TextButton(
onPressed: () {
goHome();
},
child: Text(
"$counter 跳过广告",
style: TextStyle(
color: Colors.white,
fontSize: 14
)
),
)
),
]
);
}
@override
void dispose() {
super.dispose();
}
}
项目优化
异步 UI 更新
试想这样一种场景:异步请求接口,在数据还未请求回来的时候,UI 就已经更新了。此时,UI 会因为拿不到数据而报错。
而异步 UI 更新,就是为了解决这一问题的
-
future 接收Future类型的值,实际上就是我们的异步函数,通常是接口请求函数
-
initialData初始数据,在异步请求完成之前使用
-
builder:是一个回调函数,接收两个参数一个AsyncWidgetBuilder类型的值
builder: ( BuildContext context, AsyncSnapshot<dynamic>snapshot ) { ///... }
AsyncSnapshot(即 snapshot)中封装了三个内容:
- connectionState(连接状态 - 一共有四个)
- none:当前未连接到任何异步计算。
- waiting:连接成功等待交互
- active:正在交互中,可以理解为正在返回数据
- done:交互完成,可以理解为数据返回完成。通过 snapshot.data 获取数据
- data(实际上就是 future 执行后返回的数据)
- error(实际上就是 future 错误时返回的错误信息)
FutureBuilder( // future: Future.delayed(Duration(seconds: 3), () async { // return await G.api.course.courseDetail(id: widget.id); // }), future: G.api.course.courseDetail(id: widget.id), builder: (context, snapshot) { switch (snapshot.connectionState) { // case ConnectionState.none: // 初始态 // return ElevatedButton( // onPressed: () {}, // child: Text('点击发送请求i') // ); // break; case ConnectionState.waiting: // 正在等待 return Center( child: CircularProgressIndicator() ); case ConnectionState.done: if (snapshot.hasError) { return Center( child: Text( '${snapshot.error}', style: TextStyle(color: Colors.red) ) ); } else if (snapshot.hasData) { print('异步请求成功'); var course = snapshot.data; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ... ] ) ); } else { return Center( child: CircularProgressIndicator() ); } break; default: return Container(); break; } } ) );
- connectionState(连接状态 - 一共有四个)
保持页面状态
-
IndexedStack
一次加载多有的 Tab 页面,但同时,只展示其中一个。body: IndexedStack( index: curIndex, children: pages.map<Widget>((e) => e['page']).toList(), ),
-
AutomaticKeepAliveClientMixin
保持某些页面的状态// Home page class Home extends StatefulWidget { Home({Key key}) : super(key: key); @override _HomeState createState() => _HomeState(); } // 1. 使用 AutomaticKeepAliveClientMixin class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; // 2. 声明 wantKeepAlive // 避免 initState 重复调用 @override void initState() { super.initState(); } @override Widget build(BuildContext context) { super.build(context); // 3. 在构造方法中调用父类的 build 方法 } @override void dispose() { super.dispose(); } }
-
Tab 中,只保持某些页面的状态(需要修改 Tab 实现)
-
声明 PageController
PageController _pageController;
-
初始化 PageController
@override void initState() { // 2. 初始化 PageController _pageController = PageController( initialPage: G.getCurrentContext().watch<CurrentIndexProvider>().currentIndex ); super.initState(); }
-
修改 Tab 的 body
body: PageView( controller: _pageController, children: pages.map<Widget>((e) => e['page']).toList(), )
-
跳转到指定页面
onTap: (index) async { // 4. 跳转到指定页面 setState(() { _pageController.jumpToPage(index); }); },
-
DevTools
DevTools 是一套Dart和Flutter性能调试工具。
安装 DevTools
-
编辑器中
在 Android Studio 或 VS Code 中,只要你安装了 Flutter 插件,则 DevTools 也已经默认安装了。 -
在命令行中
-
如果在你的环境变量PATH中有pub, 可以运行:
pub global activate devtools
-
如果环境变量PATH中有flutter , 可以运行:
flutter pub global activate devtools
-
启动 DevTools
-
VS Code 中
-
命令行中
-
如果在你的环境变量PATH中有pub, 可以运行:
pub global run devtools
-
如果环境变量PATH中有flutter , 可以运行:
flutter pub global run devtools
-
启动应用
-
debug 模式启动
flutter run
-
profile 模式启动
flutterrun--profile
使用 DevTools
-
Flutter Inspector
这是一款用于可视化和浏览 Flutter Widget 树的工具。 -
Performance
性能分析 -
CPU Profiler
CPU 分析器,可以通过此视图记录应用的运行会话,并查看 CPU 在哪些方法中耗费了大量时间,然后就可以决定应该在哪里进行优化。 -
Memory
查看应用在特定时刻的内存使用情况 -
Debugger
调试器 -
Network
网络请求调试,例如:接口调试,HTTP 分析等 -
Logging
查看日志,支持关键词搜索。日志内容包括:- Dart 运行时的垃圾回收事件。
- Flutter 框架事件,比如创建帧的事件。
- 应用的 stdout 和 stderr 输出。
- 应用的自定义日志事件。
-
App Size
App 打包后,可以对 APP 的大小进行分析
以 profile 方式启动 flutter (flutter run --profile)
报错: Flutter Profile mode is not supported by sdk gphone x86.
原因:模拟器是 x86 的,不支持 profile方式运行,将模拟器换成 x64 的即可。