使用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+1environment:sdk: ^3.9.0dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.8dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^5.0.0flutter: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: fluttercupertino_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});@overrideWidget 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;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {int _counter = 0;void _incrementCounter() {setState(() {_counter++;});}@overrideWidget 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});@overrideWidget 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關聯的狀態類@overrideState<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頁面
@overrideWidget 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{@overrideWidget 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的概要
flutter專欄–dart基礎知識
關注我,有空一起閑聊