Skip to content

Flutter开源项目QuitSmoke阅读-第一篇

前言

这个GitHub仓库汇总了一些开源的flutter项目,我挑选了Health and Fitness中的QuitSmoke项目来阅读。

将项目clone至本地,其主要工程代码位于lib/目录下。

shell
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/目录下有软件运行时的截图。

本次阅读的源码版本如下

shell
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。

dart
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继承 StatelessWidgetStatefulWidget。 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被定义。

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()

将在下篇继续分析。