Flutter开源项目QuitSmoke阅读-第一篇
前言
这个GitHub仓库汇总了一些开源的flutter项目,我挑选了Health and Fitness中的QuitSmoke项目来阅读。
将项目clone至本地,其主要工程代码位于lib/
目录下。
fuming@fumingdeMacBook-Pro lib % tree
.
├── comps
│ ├── cigaratte.dart
│ ├── getlang.dart
│ ├── particle.dart
│ ├── particlePainer.dart
│ ├── particleSpawner.dart
│ ├── progress_painter.dart
│ └── snappable.dart
├── constants.dart
├── generated_plugin_registrant.dart
├── main.dart
├── notification_manager.dart
├── screens
│ ├── guide_screen.dart
│ ├── guideview_screen.dart
│ ├── home_screen.dart
│ ├── progress_screen.dart
│ ├── reason_screen.dart
│ ├── settings_screen.dart
│ ├── splash_screen.dart
│ ├── wallet_screen.dart
│ └── welcome_screen.dart
├── size_config.dart
├── static
│ ├── currencies.dart
│ ├── htimes.dart
│ └── lang.dart
└── theme.dart
可以看到工程量并不大,对于初学者来说比较友好。
在阅读本项目源码之前,要先了解程序的功能,操作逻辑,本文不再描述这些。在项目的screens/
目录下有软件运行时的截图。
本次阅读的源码版本如下
commit 20bc05bd3a0a3361b506480af0d0f6af095fbba9
Author: trizin <25263018+trizin@users.noreply.github.com>
Date: Thu Jun 3 23:25:56 2021 +0300
正文
main()函数
Flutter的入口在"lib/main.dart"的main()
函数中,它是Dart应用程序的起点。
对main()
函数的解读可以查看Flutter实战·第二版 14.3.1 启动,有比较详细的解释。
本项目main()
首先执行了WidgetsFlutterBinding.ensureInitialized()
,由上面的解读可以知道runApp()
其实也做了这样的操作,在runApp()
执行之前先手动执行一次WidgetsFlutterBinding.ensureInitialized()
的原因是因为main()
使用了async
,如果想要在里面使用await
,就必须先有一个WidgetsBinding
实例。
接下来main()
配置了时区、时间格式和通知,然后便执行了runApp()
,然后配置了SystemChrome.setPreferredOrientations
,使程序可以随重力感应变化方向,本程序只配置了竖直的两个方向,未适应横屏。
runApp(Widget app)
程序的显示内容由runApp(Widget app)这个方法来执行,此方法接收一个widget为参数,此widget就是我们要现实的内容,接下来主要看MyApp
这个class。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus.unfocus();
}
},
child: MaterialApp(
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
child: child,
);
},
debugShowCheckedModeBanner: false,
title: 'Quit Smoking',
theme: themeData(context),
home: SplashScreen(),
darkTheme: darkThemeData(context),
themeMode: ThemeMode.light,
),
);
}
}
可以看到,MyApp继承了StatelessWidget,仅重写了其build方法。
在写应用的过程中,取决于是否需要管理状态,你通常会创建一个新的Widget继承
StatelessWidget
或StatefulWidget
。 Widget 的主要工作是实现build()
方法,该方法根据其它较低级别的 widget 来描述这个 widget。框架会逐一构建这些 widget,直到最底层的描述 widget 几何形状的RenderObject
。
MyApp的build方法返回了一个GestureDetector,内部分为三部分:behavior,onTap,child。
behavior
behavior有三个可选项:deferToChild,opaque,translucent。默认值是deferToChild,具体分析可见Flutter事件分发-源码角度解析HitTestBehavior。
本项目behavior被设置为了opaque,是为了使空白处有手势操作(单击,双击,拖动等)时,程序可以接收到事件并响应,响应方正是onTap(具体看是什么操作,onTap只响应单击操作)。
onTap
GestureDetector可以监听很多种手势操作,如单击,双击,拖动等。单击操作的响应器就是onTap,双击是onDoubleTap,长按是onLongPress,具体可以查看GestureDetector的源码。
本项目中只监听了onTap手势事件,所以也只会对单击事件进行响应。
本项目的behavior+onTap实现了当有光标聚焦时(此时键盘也会处于弹出状态),点击其他空白处会失去焦点,关闭键盘。
child
child就是GestureDetector要显示的widget内容了,本项目返回了一个MaterialApp,对其介绍可以看Flutter之MaterialApp使用详解。
MaterialApp的builder设置了字体放大的倍数,其他配置都便于理解,最重要的便是home了。
home: SplashScreen(),SplashScreen()在lib/screens/splash_screen.dart被定义。
SharedPreferences pref = await SharedPreferences.getInstance();
if (pref.getString("startTime") != null) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => HomeScreen()));
} else {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => WelcomeScreen()));
}
简单来看就是根据是否有"startTime"
这个值来判断显示主页面HomeScreen()
还是欢迎页面WelcomeScreen()
。
将在下篇继续分析。