使用fvm管理flutter版本
如果你有使用多版本flutter的需求,那么fvm将会给你提供较大的帮助。下面我列举一下mac + flutter3.35.2的版本的操作命令,完成之后,你将可以随意切换flutter版本
# 下载fvm相关的依赖
brew tap leoafarias/fvm
brew install fvm
# 检查版本号
fvm --version
# 3.2.1
# 下载flutter, 并使用
fvm install 3.35.2 #或者直接下载具体版本号也行
fvm use 3.35.2
# 检查flutter版本号
fvm flutter --version
# 3.35.2
这里由于笔者已经全局安装一个公司私域的flutter, 并且配置了全局的path, fvm目前识别不到已经安装的flutter,由于不想影响已经安装的全局私域flutter,这里可以在flutter命令前直接加fvm,即可使用到fvm安装的官方flutter,如果不是我这种特殊情况的话,直接使用flutter命令理论是可以切到fvm设置的global源的。
创建你的第一flutter应用
前置步骤
创建flutter之前,你需要配置你的开发环境。具体的步骤官网做了较为详细的概括,这里将不再赘述,主要分为几个步骤。
- 下载flutter sdk, vscode配置flutter相关插件
- 下载配置android studio,下载jdk
- 下载配置xcode
- 测试手机开启授权
创建flutter应用
fvm flutter create test_app
cd test_app
fvm flutter run
选择浏览器打开,第一个flutter demo就创建完成了

源代码剖析
结构列表总结
在test_app这个根目录下,大约有十几个子目录,如下。
名称 | 类型 | 主要作用 |
---|---|---|
test_app | 目录 | 项目根目录 |
.dart_tool | 目录 | Dart 工具链配置与缓存 |
.idea | 目录 | IDE 配置 |
android | 目录 | Android 平台原生代码 |
build | 目录 | 构建产物(可删除) |
ios | 目录 | iOS 平台原生代码 |
lib | 目录 | 核心 Dart 应用代码 |
linux | 目录 | Linux 桌面端原生代码 |
macos | 目录 | macOS 桌面端原生代码 |
test | 目录 | 测试代码 |
web | 目录 | Web 平台代码 |
windows | 目录 | Windows 桌面端原生代码 |
.gitignore | 文件 | Git 忽略规则 |
.metadata | 文件 | Flutter 工具元数据 |
analysis_options.yaml | 文件 | 静态代码分析规则配置 |
pubspec.lock | 文件 | 依赖包精确版本锁定 |
pubspec.yaml | 文件 | 项目依赖与元数据配置(非常重要) |
README.md | 文件 | 项目说明文档 |
test_app.iml | 文件 | IntelliJ 模块配置 |
作为入门,我们可以重点关注pubspec.yaml、web、和lib
pubspec.yaml目录
去除了多余的代码之后,整体的结果如下
name: test_app
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
name字段
表示当前flutter应用的包名,是一个最基本的字段
我们在导入其他文件时,就需要使用如下方式,如果包名发生变化的话,相应的路径也需要发生变化
import 'package:flutter_demo/listview_demo/listview_demo.dart';
publish_to
此属性意为包发布到哪里去
none
:表示此包不发布;- 也可以指定发布的服务器,如果删除此项配置,那么默认发布到
pub.dev
version
此属性表示当前工程的版本,分为应用程序的版本
和内部版本号
,格式为x.x.x+x
,比如1.0.0+1
,称为语义版本号
;
+
号前面的叫做version number
;+
号后面的叫做build number
;
在test_app/android/app/build.gradle.kts这个文件里可以看到安卓打包定版本的具体逻辑
environment
可以配置Flutter
和Dart
版本
dependencies
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
添加我们用到的第三方的sdk
sdk: flutter
意为默认获取flutter
的最新版本,也就是我们机器上的flutter
版本,我们也可以在此处添加version
来指定flutter
的版本;cupertino_icons
:给应用程序添加Cupertino
图标的,一般用于iOS
;
其实这个本质上跟js项目的生产依赖是一样的
dev_dependencies
开发依赖,只有运行时才会用到
flutter
Flutter
相关的配置
# 确保我们的应用程序中包含Material Icons字体,以使我们能够使用material Icons类中的图标;
uses-material-design: true
我们当资源的配置也是在这个配置下进行设置:
assets
:配置图片;fonts
:配置字体;plugin
:该配置只存在于插件项目中,用来配置适配的平台,一般不要修改;如需添加新平台,直接添加即可;
web目录

主要包含了一些生成web页面的静态资源和模版信息
lib目录
flutter项目的源代码

main.dart整个应用的入口文件
完整的代码信息如下
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
导入依赖
import 'package:flutter/material.dart';
导入了 Material UI 组件库。Material(opens new window)是一种标准的移动端和web端的视觉设计语言, Flutter 默认提供了一套丰富的 Material 风格的UI组件。
应用入口
void main() {
runApp(const MyApp());
}
Flutter 应用中 main
函数为应用程序的入口。main
函数中调用了runApp
方法,它的功能是启动Flutter应用。runApp
它接受一个 Widget
参数,在本示例中它是一个MyApp
对象,MyApp()
是 Flutter 应用的根组件。
也可以简写成单行函数
void main() => runApp(MyApp());
应用代码结构
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
MyApp
类代表 Flutter 应用,它继承了StatelessWidget
类,这也就意味着应用本身也是一个widget。- 在 Flutter 中,大多数东西都是 widget,包括对齐(Align)、填充(Padding)、手势处理(GestureDetector)等,它们都是以 widget 的形式提供。
- Flutter 在构建页面时,会调用组件的
build
方法,widget 的主要工作是提供一个 build() 方法来描述如何构建 UI 界面(通常是通过组合、拼装其他基础 widget )。 MaterialApp
是Material 库中提供的 Flutter APP 框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp
也是一个 widget。home
为 Flutter 应用的首页,它也是一个 widget。
具体代码解析
MyHomePage
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
// 重写createState方法,创建与这个Widget关联的状态类
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// ...
}
MyHomePage
是应用的首页,它继承自StatefulWidget
类,表示它是一个有状态的组件(Stateful widget)。
- Stateful widget 可以拥有状态,这些状态在 widget 生命周期中是可以变的,而 Stateless widget 是不可变的。
- Stateful widget 至少由两个类组成:
-
-
一个
StatefulWidget
类。 -
一个
State
类;StatefulWidget
类本身是不变的,但是State
类中持有的状态在 widget 生命周期中可能会发生变化。 -
_MyHomePageState
类是MyHomePage
类对应的状态类。这里可以看到,和MyApp
类不同,MyHomePage
类中并没有build
方法,取而代之的是,build
方法被挪到了_MyHomePageState
方法中,至于为什么,后面会进行解读const MyHomePage({super.key, required this.title});
-
构造函数,用于初始化一个名为 MyHomePage
的页面(Widget)。
State类
_MyHomePageState
在就是一个state类,在MyHomePage
里面被createState出来,里面主要有两部分内容, 一是定义了一个计数器状态,再定义一个状态自增函数,当按钮点击时,会调用此函数,该函数的作用是先自增_counter
,然后调用setState
方法。setState
方法的作用是通知 Flutter 框架,有状态发生了改变,Flutter 框架收到通知后,会执行 build
方法来根据新的状态重新构建界面, Flutter 对此方法做了优化,使重新执行变的很快,所以你可以重新构建任何需要更新的东西,而无需分别去修改各个 widget。
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
二是定一个build函数,用于构建UI页面
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
当MyHomePage
第一次创建时,_MyHomePageState
类会被创建,当初始化完成后,Flutter框架会调用 widget 的build
方法来构建 widget 树,最终将 widget 树渲染到设备屏幕上。具体解读如下:
Scaffold
是 Material 库中提供的页面脚手架,它提供了默认的导航栏、标题和包含主屏幕 widget 树(后同"组件树"或"部件树")的body
属性,组件树可以很复杂。本书后面示例中,路由默认都是通过Scaffold
创建。body
的组件树中包含了一个Center
组件,Center
可以将其子组件树对齐到屏幕中心。此例中,Center
子组件是一个Column
组件,Column
的作用是将其所有子组件沿屏幕垂直方向依次排列; 此例中Column
子组件是两个Text
,第一个Text
显示固定文本 "You have pushed the button this many times:",第二个Text
显示_counter
状态的数值。floatingActionButton
是页面右下角的带"+"的悬浮按钮,它的onPressed
属性接受一个回调函数,代表它被点击后的处理器,本例中直接将_incrementCounter
方法作为其处理函数。
完整流程如下:
当右下角的floatingActionButton
按钮被点击之后,会调用_incrementCounter
方法。在_incrementCounter
方法中,首先会自增_counter
计数器(状态),然后setState
会通知 Flutter 框架状态发生变化,接着,Flutter 框架会调用build
方法以新的状态重新构建UI,最终显示在设备屏幕上。
为什么把build放在State类中
1.方便状态访问
如果我们的StatefulWidget
有很多状态,而每次状态改变都要调用build
方法,由于状态是保存在 State 中的,如果build
方法在StatefulWidget
中,那么build
方法和状态分别在两个类中,那么构建时读取状态将会很不方便。如果真的将build
方法放在 StatefulWidget 中的话,由于构建用户界面过程需要依赖 State,所以build
方法将必须加一个State
参数,大概是下面这样:
Widget build(BuildContext context, State state){
//state.counter
...
}
这样的话就只能将State的所有状态声明为公开的状态,这样才能在State类外部访问状态!但是,将状态设置为公开后,状态将不再具有私密性,这就会导致对状态的修改将会变的不可控。但如果将build()
方法放在State中的话,构建过程不仅可以直接访问状态,而且也无需公开私有状态,这会非常方便。
2.方便继承StatefulWidget
例如,Flutter 中有一个动画 widget 的基类AnimatedWidget
,它继承自StatefulWidget
类。AnimatedWidget
中引入了一个抽象方法build(BuildContext context)
,继承自AnimatedWidget
的动画 widget 都要实现这个build
方法。现在设想一下,如果StatefulWidget
类中已经有了一个build
方法,正如上面所述,此时build
方法需要接收一个 State 对象,这就意味着AnimatedWidget
必须将自己的 State 对象(记为_animatedWidgetState)提供给其子类,因为子类需要在其build
方法中调用父类的build
方法
class MyAnimationWidget extends AnimatedWidget{
@override
Widget build(BuildContext context, State state){
//由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
//所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState
//暴露给其子类
super.build(context, _animatedWidgetState)
}
}
这样很显然是不合理的,因为
AnimatedWidget
的状态对象是AnimatedWidget
内部实现细节,不应该暴露给外部。- 如果要将父类状态暴露给子类,那么必须得有一种传递机制,而做这一套传递机制是无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。
总结来说
build
方法需要根据可变状态(如_counter
) 来构建用户界面。这些可变状态保存在State
子类(如_MyHomePageState
)中。将build
方法放在State
子类里,可以直接访问(通过context)这些状态(例如_counter
),无需通过复杂的传递机制或将状态设置为公开,从而破坏了状态的封装性。StatefulWidget
是对外 的,负责接收不可变的配置数据。State
是对内 的,负责管理可变的状态。
参考
《Flutter实战·第二版》
https://juejin.cn/post/7033933629403168799
4.拓展阅读
flutter专栏--移动开发技术的发展与flutter的概要
关注我,有空一起闲聊
