导航
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.dart
import '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
创建 lib/api/initDio.dart
import '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',
),
);
安装 (https://pub.dev/packages/flutter_html)
配置 gradle-plugin 的中文镜像
使用
import 'package:flutter_html/flutter_html.dart';
//......
Html(data: "标题1
"); /// 在 Flutter 中展示 HTML 代码
详情查看: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_launcher
import '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 更新,就是为了解决这一问题的
FutureBuilder
future 接收Future类型的值,实际上就是我们的异步函数,通常是接口请求函数
initialData初始数据,在异步请求完成之前使用
builder:是一个回调函数,接收两个参数一个AsyncWidgetBuilder类型的值
builder: (
BuildContext context,
AsyncSnapshot<dynamic>snapshot
) {
///...
}
AsyncSnapshot(即 snapshot)中封装了三个内容:
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;
}
}
)
);
保持页面状态
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
命令行中
如果在你的环境变量PATH中有pub, 可以运行:
pub global run devtools
如果环境变量PATH中有flutter , 可以运行:
flutter pub global run devtools
debug 模式启动
flutter run
profile 模式启动
flutterrun--profile
Flutter Inspector
这是一款用于可视化和浏览 Flutter Widget 树的工具。
Performance
性能分析
CPU Profiler
CPU 分析器,可以通过此视图记录应用的运行会话,并查看 CPU 在哪些方法中耗费了大量时间,然后就可以决定应该在哪里进行优化。
Memory
查看应用在特定时刻的内存使用情况
Debugger
调试器
Network
网络请求调试,例如:接口调试,HTTP 分析等
Logging
查看日志,支持关键词搜索。日志内容包括:
App Size
App 打包后,可以对 APP 的大小进行分析
以 profile 方式启动 flutter (flutter run --profile)
报错: Flutter Profile mode is not supported by sdk gphone x86.
原因:模拟器是 x86 的,不支持 profile方式运行,将模拟器换成 x64 的即可。