Flutter 的项目开发比较简单,项目结构也不算太复杂,所以大家应该有信心去掌握和学习 Flutter 相关知识。接下来,就开始我们认识 Flutter 项目结构及配置文件的学习之旅吧。
Flutter 包括 Android 和 iOS 两个项目的目录以及相关的 dart 类源码。
前面讲到过 Flutter 的新建项目,这里就不再赘述了。我们新建项目后,会看到如下图所示的 Flutter 默认项目结构:
我们可以看到主要包含了:android、ios、lib、test 几个目录以及 pubspec.yaml 配置文件等。接下来我们先看这几个目录的作用。
android 目录主要是存放一个完整的 Android 项目的目录。编译后,我们的 Flutter 代码会加入整合到这个 Android 项目中,形成一个 Flutter Android 应用。所以 android 目录存放的 Android 项目结构都是完整的,可以通过 Android Studio 打开进行编译、运行、开发、调试等等操作。这里把 android 目录的项目用 Android Studio 打开进行一下预览:
可以看到它就是一个完整的 Android 项目,代码最终编译为对应的 Android 平台的应用。这里的启动 Activity 就是 MainActivity,看下里面的内容:
package com.example.flutter_samples;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
可以看到,里面几乎没有什么代码逻辑,只是继承了 FlutterActivity,然后将 Flutter 相关插件注册进来。当然后续如果我们需要编写插件、编写原生交互的时候(一般不用这种,除非一定要写代码逻辑的时候,否则就脱离了 Flutter 插件化跨平台的特性),需要在这里写一些回调插件逻辑。不过这种情况一般是:Flutter 插件库里没有可以实现你的复杂功能的插件库,Flutter SDK 里无法实现你要的功能,这种情况就可能要与原生交互了。推荐是编写插件库形式进行与原生交互,更加规范和方便,也有利于后续组件化维护与共享。
如果需要更改 Android 应用包名、版本号、签名文件,修改方式和修改 Android 项目一样的,都是在 app 目录下的 build.gradle 里进行修改:
android {
compileSdkVersion 28
...
defaultConfig {
//应用包名
applicationId "com.example.flutter_samples"
minSdkVersion 16
targetSdkVersion 28
//版本号
versionCode 1
//版本名称
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
...
ios 目录和 android 目录一样,存放的是 iOS 项目文件,里面的 iOS 项目结构就是一个完整的 iOS 目录结构。可以使用 XCode 打开进行编译、修改、开发、调试等等操作。例如 iOS 平台的图标的修改、版本号的修改等等操作都是在这里,就不再重复说明。
lib 目录就是我们存放核心的 Flutter 代码逻辑的地方了,里面放置了 dart 语言的类源码文件(例如 main.dart)。我们的项目核心逻辑源码文件都是放在这里的,lib 下的源码最终会编译到 Android 和 iOS 平台进行渲染。
默认有一个 main.dart 类,这个 main.dart 类名字不可以修改,必须放置在 lib 根目录,它是我们项目的入口文件,入口类。
test 目录里主要用于编写我们的测试用例,如测试 Widget UI、数据等等。默认里面有一个 widget_test.dart 文件。我们看下它的内容:
//这个一个测试用例的小例子
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_samples/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// 编译APP
await tester.pumpWidget(MyApp());
// 验证初始状态下ui是否有显示为0的Widget和1的Widget
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// 点击"+"号图标,触发事件
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
//验证点击后是否实现加一功能,寻找ui上是否有显示为0的Widget和1的Widget
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
// test(description, body);
}
如果在 Visual Studio Code 里看这段,默认是有 Run 和 Debug 按钮的,我们可以点击 Run 进行运行测试用例:
这个是我们的 Flutter 项目的配置文件,也比较重要,我们在这里可以配置引用第三方插件库、添加 assets 图片资源、font 字体资源、音视频资源路径等等。整个文件是参照 YAML(YAML Ain’t a Markup Language)语法规范进行定义的格式,其实就是通过缩进来形成结构目录,非常简单,因此就不过多描述语法了。我们看下 pubspec.yaml 配置文件的内容和结构:
//项目名称:要用英文,类似于Android中的包名,如果它修改了整个项目的引入的路径都要修改
//所以一般确定了就不要修改
name: flutter_samples
//项目描述
description: A new Flutter project.
//版本号,这个会覆盖对应Android和IOS的应用版本号
//+号前对应Android的versionCode,+号后对应Android的versionName
//+号前对应IOS的CFBundleVersion,+号后对应IOS的CFBundleShortVersionString
version: 1.0.0+1
//表示项目的编译环境要求为dart sdk版本号在2.1.0和3.0.0之间
environment:
sdk: ">=2.1.0 <3.0.0"
//项目的依赖插件库
//Flutter插件库在这里查找引用:https://pub.dartlang.org/flutter
dependencies:
flutter:
sdk: flutter
//我们可以在这里引入插件库
cupertino_icons: ^0.1.2
flutter_webview_plugin: ^0.3.1
dev_dependencies:
flutter_test:
sdk: flutter
//flutter相关配置
flutter:
//是否使用material图标,建议为true
uses-material-design: true
//配置项目文件里的图片路径
//如果需要使用项目目录内附带的图片、音视频等资源,必须在这里配置定义
assets:
- images/a_dot_burr.jpeg
- images/a_dot_ham.jpeg
//字体文件资源相关配置
fonts:
- family: Schyler
fonts:
- asset: fonts/Schyler-Regular.ttf
- asset: fonts/Schyler-Italic.ttf
style: italic
- family: Trajan Pro
fonts:
- asset: fonts/TrajanPro.ttf
- asset: fonts/TrajanPro_Bold.ttf
weight: 700
//下面这几项一般只有在编写插件库发布到Dart Pub时才写,一般不用写
//作者
authors:
- Natalie Weizenbaum <nweiz@google.com>
- Bob Nystrom <rnystrom@google.com>
//主页
homepage: https://example-pet-store.com/newtify
//文档地址
documentation: https://example-pet-store.com/newtify/docs
//发布到
publish_to: none
这里再多说一句,如果我们在配置文件里引入了一个插件库后,比如 sharedpreferences,我们再用 Android Studio 打开 Android 项目后,可以看到目录的依赖项里增加了 sharedpreferences:
然后 Android 项目主工程 GeneratedPluginRegistrant 类里增加了如下内容:
package io.flutter.plugins;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
/**
* 增加了注册shared_preferences插件库方法
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
}
里面指明了我们的 pubspec.yaml 等依赖包和项目依赖的库的具体版本号,我们可以在这里查看我们项目引用的依赖的具体版本号等信息,如果某个配置文件丢失,可以通过这个文件重新下载和恢复依赖库。为自动生成文件。
里面放置了项目依赖的库的具体在本机电脑上的绝对路径。为自动生成文件,如果项目出错或者无法找到某个库,可以把这个文件删除,重新自动配置即可。
里面记录了我们项目的属性信息,如使用 Flutter SDK 哪个分支开发的,项目属性等,用于切换分支、升级 SDK 使用。自动生成,无需修改删除。
前面介绍了 Flutter 的项目基本结构,我们来着重看下 lib 里的代码结构。
默认 lib 下有一个 main.dart 类,这个 main.dart 类名字不可以修改,必须放置在 lib 根目录,它是我们项目的入口文件,入口类。当然我们在实际开发中,还是根据需要进行源码类的包划分,这里给出一个示例:
我们可以根据实际需要在 lib 里建立相关的文件夹,可以按照项目功能分、类的类型和功能分等等。
我们再看下 main.dart 入口文件内容结构:
import 'package:flutter/material.dart';
//void main()为入口方法,main.dart文件独有
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
//应用的最顶层入口
@override
Widget build(BuildContext context) {
//这里使用了MaterialApp脚手架,当然也可以使用WidgetApp,建议入口使用MaterialApp。
//直接定义一个容器布局也可以
return MaterialApp(
//标题
title: 'Flutter Demo',
theme: ThemeData(
//可以自定义配置主题色调
primarySwatch: Colors.blue,
),
//页面
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
//使用StatefulWidget有状态Widget
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
//创建State状态
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
//每次执行这个方法_counter加一,然后用setState方法进行UI刷新
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
//Scaffold布局脚手架组件
return Scaffold(
//标题栏Widget组件
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
//定义了一个Button,点击执行_incrementCounter()方法
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
这个代码例子是官方默认新建后的内容,运行效果如下图:
实现的功能就是每点击+号浮动按钮一次,显示 0 的 Widget 组件就加一。
代码整体风格就是 React 组件化响应式风格,类似于 DOM 树结构,通过小的 Widget 来组成一个大的页面。部分代码类似于 Java 的写法,其实还是比较简单。刚开始看的时候可能会觉得有点迷惑,不过不要担心,接触 Widget的学习后,大家就会对 Flutter 的编写风格有更直观的了解。
再给大家一个简化版的 main.dart:
import 'package:flutter/material.dart';
//void main()为入口方法,main.dart文件独有
void main() => runApp(MyMainApp());
class MyMainApp extends StatefulWidget {
//这里可以简写为:State createState() => _MyMainPageState();
@override
State<StatefulWidget> createState() {
return _MyMainPageState();
}
}
class _MyMainPageState extends State<MyMainApp> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
//页面
home: Scaffold(
appBar: AppBar(
title: Text('标题'),
),
body: Center(
child: Text("我是内容"),
),
),
);
}
}
这样看着是不是更简单清晰了,效果如下图:
大家可以按照这个效果图倒推看代码逻辑结构。
本节课主要是给大家讲解 Flutter 项目结构及配置文件相关知识,为后面的 Flutter 学习打下基础。注意点和建议如下: