# Flutter筆記- 入門篇 - 總綱 Flutter 教學文件, 給予初學者參考用基本App元件教學, 以官網資訊為腳本。需搭配教學用APP做學習。本文的示範App使用 Visual Studio Code 做為開發工具。本文章與APP 皆由本人(Kerr) 撰寫。 教學用APP請至以下連結下載:([App範例 Git](https://github.com/KerrShine/flutter_teach)) # 功能大綱: - Visual Code 1. 常用指令 2. 專案初始配置 3. 套件(**https://pub.dev/**) 4. 插件(輔助開發) - 前端 1. 畫面佈局 - StatefulWidget vs StatelessWidget - MaterialApp and Scaffold - Container - Row , Column and Stack 2. 組件 - Text & TextSpan - TextField - Button - Radio - CheckBox - Switch - Slider - Image - BottomSheet - DateTimePicker - ListView - Dialog 3. 路由設定 - Navigator - Named vs NoNamed - 參數轉頁傳遞 4. 多語系 - flutter_localizations.dart 5. 儲存 - ShareReference - Sqlite - 後端 1. API介接 - package : http - Json - Json to class & class to Json - Json 產生器 : https://app.quicktype.io/ - Provide - get_it 套件 2. 架構 - MVVM方式實踐程式開發 - 總結 - 總結&通往進階之路 # Visual Code: | 指令 | | | --------------------------- | ---------------------------------------------------------------- | | flutter doctor | 查詢目前本機Flutter安裝狀況 | | flutter --help —verbose | 查詢可執行指令 | | flutter pub get | 取得封裝套件 | | flutter pub add <Packet> | 新增套件,終端會自行抓取線上資源 | | flutter pub upgrade | 現有套件升級 | | flutter run <path> | (EX:flutter run lib/main.dart) 執行程式若不指定路徑則跑main.dart | | flutter upgrade | 更新Flutter 本體 | | flutter build apk --release | 發行Android 封包 | | flutter build appbundle | 發行iOS 封包 | # 專案初始配置 - App開發專案初始配置介紹。 ![01](https://hackmd.io/_uploads/Sk7oq8RCa.png =70%x) | 配置 | | | ------------ | ---------------------------------------------------------------------------------- | | android | Android系統配置:Android icon、權限、專案簽章設定。Firebase 等第三方元件相關配置。 | | ios | IOS系統配置 :Xcode相關資源配置,IOS Firebase 第三方元件配置。 | | lib | Flutter 程式配置:雙平台主體程式在此資料夾內。 | | pubspec.yaml | Flutter 資源,套件 定義文件。 | | main.dart | Flutter 預設程式起始頁。 | - Android 資料夾配置: ![02](https://hackmd.io/_uploads/S15C6L0AT.png =70%x) | 配置 | | | --------------- | ------------------------------------------------------------------------------------------------------- | | App/src/main | AndroidManifest.xml:Android系統權限宣告配置檔案。(EX: 讀寫權限) Minmap:系統圖檔,(EX:app 啟動 icon ) | | build.gradle | 這裡可以指定應用程式的版本號、依賴套件、應用程式簽章設定。 | | settings.gradle | 文件用於包含和設定 Gradle 專案的其他模塊。通常不需要手動修改這個文件。 | - IOS 資料夾配置 ![03](https://hackmd.io/_uploads/HyAQxvCA6.png =70%x) | 配置 | | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Assets.xcassets | 存儲應用程序的圖標、啟動圖像和其他圖形資源。 | | Info.plist | 包含應用程序配置信息的 XML 文件,用於指定應用程序的名稱、Bundle ID、版本號、啟動圖像等。 | | Podfile | Podfile 是 CocoaPods 的配置文件,用於管理 iOS 專案中使用的原生庫和依賴。你可以在這裡指定 Flutter 應用程序的相依性,例如 Firebase、Flare、或其他原生庫。 | | Runner.xcworkspace | 這是一個 Xcode 工作區文件,你可以編輯 Flutter 應用程序的Bundle ID 和 簽證配置。 | # 套件: 套件的使用皆需要宣告在**pubspec.yaml**中,用於定義和管理 Flutter 專案的相依套件、資源文件、專案資訊等。內部定義可以大略分成以下幾種: **專案資訊:** - name:指定專案的名稱。 - description:提供對專案的簡短描述。 - version:指定專案的版本號。 ![04](https://hackmd.io/_uploads/BkntKvC06.png) **相依套件:** - dependencies:指定專案所依賴的 Dart 套件和 Flutter 插件。可以在這裡列出相依套件的名稱和版本號。 ![05](https://hackmd.io/_uploads/rJNR5wCRp.png) **開發相依套件:** - dev_dependencies:指定只在開發階段需要的相依套件,例如測試框架。 ![06](https://hackmd.io/_uploads/BJOyCvAR6.png) **資源文件:** - assets:指定專案中需要包含的靜態資源文件,例如圖片、字體等。 ![07](https://hackmd.io/_uploads/rkdbM_CRp.png) **其他配置:** ![08](https://hackmd.io/_uploads/BkhmUuACa.png) # 插件: Flutter官方套件發佈於(https://pub.dev/),相依套件可以在宣告後輸入上個單元得指令(flutter pub get)即會自行安專 常用套件有以下列表: | 元件 | | | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | provider | Flutter提供狀態管理套件,用於將數據提供給 Widget 樹。 | | get_it | get_it 是一個 Flutter 中用於依賴注入(Dependency Injection)的庫,主要用途是使得在應用程式中能夠輕鬆地獲取和管理依賴關係。 | | http | API管理原生套件 , 溝通後端使用。 | | path_provider | 取得外部連結。 | | flutter_localizations | MaterialApp and Cupertino 多國語元件。 | | intl | APP內部實作多國語套件。 | | flutter_launcher_icons |自動產生App icon ,可以讓iOS系統直接套用對應icon。 | # 前端: <div style="text-align: center; font-size: 24px; font-weight: bold;"> Every thing is Widgets! </div><br> **Widget:** Flutter FrameWork 最基礎的元件。所有畫面的顯示,皆是由Widget組成。 ## 畫面佈局: <div style="text-align: center; font-size: 22px; font-weight: bold;"> StatefulWidget vs StatelessWidget </div><br> - **StatelessWidget** - **特點:** 是不可變的,即一旦創建,它們的內部狀態就不能改變。 - **使用場景:** 適用於當 UI 不依賴於對象內部的變化時,或者當 UI 可以單純地通過構造函數的參數進行構建,而不需要內部的狀態。<br> - **StatefulWidget** - **特點:** 是可變的,允許在內部保存和修改狀態。它由兩個類組成 —— StatefulWidget 本身和 State 對象。 - **使用場景:** 適用於 UI 內容需要在內部保持狀態並根據該狀態進行更新的情況,例如使用者交互、動畫等。 <br> | | StatelessWidget | StatefulWidget | | ------------ | ---------------------------------------- | ----------------------------------------------------------------- | | 可變性 | 不可變的,一旦創建,內部狀態不能改變。 | 可變的,允許在內部保持狀態並在需要時進行修改。 | | 建構函數參數 | 透過構造函數參數接收數據,內部狀態不變。 | 透過 createState 方法返回的 State 對象保存內部狀態。 | | 生命週期方法 | 沒有生命週期方法,因為它是不可變的。 | 有與生命週期相關的方法,例如 createState、initState、dispose 等。 | | 性能考慮 | 在不需要內部狀態的情況下,性能通常較好。 | 有一些額外的開銷,因為需要維護內部狀態,但提供了更大的靈活性。 | ### **MaterialApp:** **Material Design** 是Google提出的視覺設計組件, MaterialApp Widget 本身是一種容器,主要目的是實現具有 Material Design 風格的UI元素,例如按鈕、卡片、文本框等。它提供了一個規範化的設計,使得應用程序的外觀和感覺保持一致。 **MaterialApp** 也是繼承自**StatefulWidget**,一般來說宣告於Widget Tree中的最外層。 | 屬性 | | | --------------- | ------------------------------------------------------------------------------------------------------------------ | | Title | 應用程序的標題,顯示在應用程序切換器(recents screen)或任何其他顯示應用程序名稱的地方。(非App Title 僅在系統顯示) | | home | 定義應用程序的主頁面。通常是一個 Widget,表示應用的初始頁面。 | | routes | 定義應用程序中的命名路由。這使得可以在應用程序中輕鬆導航。(一般來說 home , route 擇一) | | onGenerateRoute | 當尋找的路由名稱在 routes 中未找到時,這個回調將被調用,可以在此動態生成路由。 | | initialRoute | 初始的(路由)Router定義 | | Theme | 定義應用程序的整體主題,包括顏色、字體等。 | ### **Scaffold:** Flutter 中用於實現基本的應用程序頁面結構的 Widget。它提供了一個標準的應用程序頁面框架,包括頂部的應用程序欄、底部的導航欄、側邊欄位(如果需要),以及主要的內容區域。 | 屬性 | | | -------------------- | ------------------------------------------------- | | appBar | 用於定義應用程序頁面頂部的應用程序欄(App Bar)。 | | body | 用於定義應用程序頁面的主要內容。 | | bottomNavigationBar | 用於定義應用程序頁面底部的導航欄。 | | floatingActionButton | 用於定義應用程序頁面底部的浮動操作按鈕。 | | drawer | 用於定義側邊抽屜,通常包含應用程序的導航菜單。 | ![09](https://hackmd.io/_uploads/SyOWgpRCp.png) - **Container:** 在 Flutter 中,我們通常使用 Container 來創建矩形區域,這個區域可以包含子元件並進行裝飾。Container 是一個基本的布局元件,它可以用來設定子元件的大小、間距、邊界以及背景等。 | 屬性 | | | --------------- | ----------------------------- | | child | 定義 Container 包含的子元件。 | | width 和 height |指定 Container 的寬度和高度。<br>不指定 = warp_content<br> match parent = double.infinity | | color |設定 Container 的背景顏色。 | | decoration |設定 Container 的裝飾,包括邊框、陰影等。若使用decoration color須在此宣告,否則將報錯。 | | alignment |設定 Container 子元件的對齊方式。 | | padding |指定 Container 內部的填充(內邊距)。 | | margin |指定 Container 外部的邊界(外邊距)。 | ![10](https://hackmd.io/_uploads/Bk8p-ll1R.png) ### **Row & Column** 在 Flutter 中,Row 和 Column 是兩種常用的布局元件,分別用於水平和垂直方向的排列子元件。 | 屬性 | | | ------------------ | -------------------------------------------------------------------------------------------------------------- | | children | Widget 中包含的子部件的列表。可以在 children 中放置多個 Widget,它們將按照它們在列表中的順序垂直或水平排列。 | | mainAxisAlignment | 用於控制子部件在主軸(垂直軸)上的對齊方式 | | crossAxisAlignment | 用於控制子部件在交叉軸(水平軸)上的對齊方式。 | | mainAxisSize | 用於控制 Column 在主軸上的大小。<br>&emsp;MainAxisSize.max(擴展到最大)<br>&emsp;MainAxisSize.min(只包含子部件的大小)。 | | verticalDirection | 用於控制子部件在主軸上的排列方向<br>&emsp;VerticalDirection.down(從上到下)。<br>&emsp;VerticalDirection.up(從下到上)。 | | Expanded | 子物件可以依方向擴展其子物件以填滿可用空間。 | ![11](https://hackmd.io/_uploads/HkMRsegJC.png) ### **Stack** 用來堆疊子 Widget 的容器,它允許你將多個子 Widget 放置在同一個區域,並可以使用不同的定位方式將它們堆疊在一起。 | 屬性 | | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | children | Widget 中包含的子部件的列表。可以在 children 中放置多個 Widget,它們將按照它們在列表中的堆疊於畫面上。 | | alignment | alignment 屬性用於指定 Stack 中所有子 Widget 的對齊方式。<br>預設情況下,alignment 被設置為 AlignmentDirectional.topStart,即左上角對齊。 | | fit | fit 屬性用於指定子 Widget 在 Stack 中的擴展方式。<br>StackFit.looseWidget 會根據自身大小進行擴展。 <br>StackFit.expand Widget 充滿整個 Stack。 | | overflow | overflow 屬性用於指定當子 Widget 的大小超過 Stack 大小時的處理方式。<br>Overflow.clip Widget 會被裁剪。<br>Overflow.visible 溢出時仍然可見。 | ![12](https://hackmd.io/_uploads/rkx5Efe1C.png =50%x) ## 組件: ### Text: 是用來顯示文字的Widget。 | 屬性 | | | ---- | ---- | | textAlign |文字對齊屬性包含:<br>&emsp;-TextAlign.left , <br>&emsp;-TextAlign.center , <br>&emsp;-TextAlign.right | | maxLines | 限制Text最大行數屬性。 | | softWrap |文字是否可以換行,如果設置為true,文字會根據容器的大小換行,否則將不換行。 | |overflow|文字超出現字時,超出文字顯示模式。 | | Style | 用來定義文字樣式的屬性,可以設置文字的字體大小、顏色、粗體等。內部使用須包含於TextStyle 屬性。<br>常用的屬性<br>&emsp;- fontsize:字體大小<br>&emsp;- color:顏色 <br>&emsp;- fontweight:字體粗細 | ### TextSpan: 使用RichText widget中實現文本樣式的一個重要類型。TextSpan允許你在文本中的不同部分應用不同的樣式,如不同的字體、顏色、大小等。內部屬性包含Text本身prperty並新增了以下。 | 屬性 | | | ---------- | ---------------------------------------------------------------------------------------------- | |Children | TextSpan內可包含多個子 Text or TextSpan | | recognizer | 用於定義點擊TextSpan時的手勢應對,比如可以在TextSpan上添加TapGestureRecognizer以處理點擊事件。 | <br>![13](https://hackmd.io/_uploads/rJb0Lbb10.png =50%x) ### TextField: TextField是用來收集用戶輸入的widget,它允許用戶在應用程序中輸入文字。 | 屬性 | | | --------------- | ---- | | controller | 內部class屬性為TextEditingController (類似FindbyViewId or Viewbinding) , 用來取的TextField內部的值。 | | decoration |用來配置TextField的外觀,包括輸入框的邊框、提示文本、背景顏色等。 | | inputFormatters |可以用來限制TextField內文輸入, 如僅允許數字輸入。 | | obscureText |如果設置為true,則輸入的輸入將被隱藏,通常用於密碼輸入。 | | onChange |TextField 文字改動時回傳函式 | | onSubmit |TextField 文字打入Enter鍵時回傳函數 | | enable | 設置是否可被輸入屬性 | | TextAlign |文字排版對齊格式 <br>&emsp;-TextAlign.center , <br>&emsp;-TextAlign.start , <br>&emsp;-TextAlign.right | | style | 定義輸入文本的樣式,包括字體大小、顏色、粗體等。 | <br>![14](https://hackmd.io/_uploads/BJB5Nz-JC.png =50%x) ### Button: 在Flutter中,有多種類型的按鈕(widget)可供使用,每種按鈕都有不同的特性。原生提供的按鈕有以下幾種: 1. ElevatedButton 2. TextButton 3. OutlinedButton 4. IconButton 5. FloatingActionButton 內部皆有以下共用屬性: | 屬性 | | | ------- | ------------------------------------------ | | child | 放置在按鈕內的widget,通常是Text或Icon。 | | style | 用來設置按鈕的樣式,例如文字樣式、邊框等。 | | onPress | 定義當按鈕被點擊時執行的回調函數。 | <br>![15](https://hackmd.io/_uploads/BkQ3ZEZkC.png =50%x) ### Radio 是一個用於實現單選框的widget,通常與RadioListTile一起使用。Radio允許用戶從一組選項中選擇一個選項。 | 屬性 | | | ---------- | ------------------------------------------------------------------------------------------ | | value | 勾選radio所代表的值 | | groupValue | 這是一個與Radio組相關聯的值,表示當前選中的選項。通常,groupValue是整個Radio組中的共享值。 | | onChanged | 當Radio的選中狀態發生變化時,這個回調函數將被調用。 | <br>![16](https://hackmd.io/_uploads/B1MyEN-1R.png =50%x) ### CheckBox: 是一個用於實現勾選框的widget,用戶可以通過點擊來切換勾選狀態。 | 屬性 | | | ----------- | --------------------------------------------------- | | onChanged | 限定為Boolean,當前CheckBox是否為勾選。 | | onChanged | Checkbox勾選時調用函數,與上述value通常一起被調用。 | | checkColor | 用於指定已選中Checkbox中勾選的顏色。 | | activeColor | 用於指定已選中Checkbox時的顏色。 | <br>![17](https://hackmd.io/_uploads/ryOh6E-kA.png =50%x) ### Switch: Switch是一個用於實現開關(Switch)的widget,用戶可以通過滑動來切換開關的狀態。 | 屬性 | | | ---- | ---- | | value |表示Switch的當前狀態,即開啟(true)或關閉(false)。 | |onChanged |Switch的狀態發生變化時,這個回調函數將被調用。 | |activeColor |用於指定已開啟Switch時的顏色。 | |inactiveThumbColor | 用於指定關閉Switch時的選單球顏色。 | |activeTrackColor |用於指定開啟Switch時的軌道顏色。 | | inactiveTrackColor | 用於指定關閉Switch時的軌道顏色。 | <br>![18](https://hackmd.io/_uploads/rk6b_9PkR.png =50%x) ### Slider: 是一個用於實現滑動輸入的widget,用戶可以通過滑動手勢在一定範圍內選擇數值。 | 屬性 | | | -------- | -------- | |value |數值為double ,表示Slider的當前數值。 | |min | Slider的最小值。 | |max | Slider的最大值。 | |divisions |Slider的軌道分為幾個區段,並在這些區段上顯示刻度標記。| |activeColor | 用於指定活動狀態(被選中)的顏色。 | |inactiveColor | 用於指定非活動狀態(未被選中)的顏色。 | | onChange | 當Slider的數值發生變化時,調用的回傳函數。 | ### Image: widget用於顯示圖像,可以是本地資源或網路資源圖片。需要注意的是本地圖片需要到 pubspec.yaml 做路徑宣告(請參考第一章節資源文件宣告)。顯示圖片機制有以下三種: | 屬性 | | | ---------------------- | ---------------------------------------------------- | | Image.asset | 取得app內部的圖片,參數應為本地圖片路徑字串。 | | Image.network | 取得網路圖片檔案,參數為線上圖片路徑字串。 | | Image.file | 取得手機內部圖片,參數應為手機內部檔案位置路徑字串。 | | width | 指定圖片寬度。 | | height | 指定圖片長度。 | | fit | 用於指定圖片如何適應其容器。 | | aligment | 指定圖片在容器中的對齊方式。 | | colorBlendMode & color | 用於對圖片進行顏色渲染。兩者屬性需同時存在才有作用。 | | errorBuilder | 用於在加載圖片時出現錯誤時構建自定義錯誤 widget。 | | loadingBuilder | 用於在圖片加載過程中構建自定義的 loadingwidget。 | <br>![19](https://hackmd.io/_uploads/rJfmT9DyC.png =50%x) ### BottomSheet : Bottom Sheet(底部表單)是一種 UI 元素,通常用於顯示與主要內容相對應的補充資訊、選項或操作。Bottom Sheet 可以從屏幕底部滑出,覆蓋部分或全部主屏幕,提供用戶一種快速獲取相關內容的方式。 Flutter提供了一種快速的BottomSheet函式,快速實作底部表單。函式名稱為showModalBottomSheet。 ``` Dart showModalBottomSheet( context: context, builder: (BuildContext context) { return Container( height: 200.0, child: Center( child: Text('This is a Persistent Bottom Sheet'), ), ); }, ); ``` <br>![20](https://hackmd.io/_uploads/HJdRJivyC.png =70%x) DraggableScrollableSheet:BottomSheet的第二種實作方式,它允許用戶通過拖動手勢來調整表單的高度,同時提供一個包含滾動內容的區域。 | 屬性 | | | ----------------- | ---------------------------------------------- | | initialChildSize | 定義底部表單的初始高度(佔整個屏幕的百分比)。 | | minChildSize | 定義底部表單允許的最小高度。 | | maxChildSize | 定義底部表單最大高度。 | | build | 建構底部表單函式,回傳內的表單內部widget | | draggableBehavior | 指定拖曳事件時是否可以scrolled。 | <br>![21](https://hackmd.io/_uploads/HJXALjPJR.png =50%x) ### DateTimePick: Flutter 提供的兩種函數,用於顯示日期選擇(DatePicker)與時間選擇(TimePick)對話框。它允許用戶選擇日期,並提供了一些屬性以自定義 DatePicker 的外觀和行為。 1. showDatePicker:日期選擇函式。 2. showTimePicker:時間選擇函式。 | 屬性 | | | ----------- | ---- | | initialTime | DatePicker 初始化時顯示的日期。 | | helpText | 日期表頭名稱。 | | cancelText | 取消選擇日期自定文字。 | | confirmText | 確定選擇日期自定文字。 | | builder | 自定義 DatePicker 的外觀 | | firstDate | 可選的最早日期,用戶可以選擇的最小日期。 | | lastDate | 可選的最晚日期,用戶可以選擇的最大日期。 | | initialDatePickerMode | 選擇初始顯示的格式。 | <br>![22](https://hackmd.io/_uploads/ryFq5oDkR.png) ### ListView: Flutter 中用於顯示一組垂直或水平方向的可滾動元素的 widget。它是一個強大的滾動容器,可以包含多個子元素,並提供一個滾動視窗,使用戶能夠在屏幕上查看和滾動這些元素。 | 屬性 | | | --------------- | -------------------------------------------------------------------------------------- | | scrollDirection | 指定 ListView 的滾動方向。<br>&emsp;Axis.vertical :垂直<br>&emsp;Axis.horizontal :水平 | | children | 子 widget 的列表,這些子 widget 將顯示在 ListView 中。 | | padding | 指定 ListView 的內邊距。 | | physics | 用於指定 ListView 的滾動物理效果 | <br>![23](https://hackmd.io/_uploads/SJ2waiDy0.png =50%x) ### ListTitle: 是一個長用於構建列表的基本元素,通常用於ListView或其他需要顯示垂直列表的地方。 | 屬性 | | | -------- | -------- | |title | ListTile 的標題文字。 | |subTiitle | ListTile 的次標題文字。 | |leading |ListTile 的前綴圖標 | |trailing |ListTile 的尾部圖標,位於 ListTile 的最右邊。 | |onTab | ListTile 點擊時的回調函數。 | |selected & selectedColor | 用於指定 ListTile 是否被選中以及選中時的背景顏色。 | |enabled | 指定 ListTile 是否可用(點擊是否生效)。 | <br>![24](https://hackmd.io/_uploads/ByEahaw1C.png =50%x) ### GridView: 是一個用於顯示網格布局的 widget。它可以以垂直或水平方向排列子元素,形成一個二維的網格結構。Flutter 提供了兩種實踐Gridview的函示分別是: 1. SliverGridDelegateWithFixedCrossAxisCount:需要指定每行(列)顯示的項目數,並且這些項目的寬度(水平方向)或高度(垂直方向)是固定的。這種委託的配置適用於希望在網格中以相同大小顯示所有子元素的情況。 2. SliverGridDelegateWithMaxCrossAxisExtent:需要指定每行(列)顯示的項目的最大寬度(水平方向)或最大高度(垂直方向),而不是具體的項目數。這種委託的配置適用於希望在網格中顯示可變大小的子元素的情況。 | 屬性 | | | ---------------- | -------- | | itemBuilder | 用於構建每個網格(widget)的函數。 | | itemCount | 指定網格中的總項目數。 | | scrollDirection | 用於指定網格的滾動方向,<br>&emsp;-Axis.vertical(垂直)<br>&emsp;-Axis.horizontal(水平)。 | | padding | 用於設置整個 GridView 的內邊距。 | | crossAxisSpacing | 用於指定網格元素之間的水平方向的間距。 | | mainAxisSpacing | 用於指定網格元素之間的垂直方向的間距。 | | crossAxisCount | 用於指定每行(列)顯示的項目數。 | | childAspectRatio | 用於指定每個子widget的寬高比。 | | maxCrossAxisExtent | 用於指定每行(列)顯示的項目的最大寬度 | <br>![25](https://hackmd.io/_uploads/HkdzwRvJC.png) ### ExpansionPannelList: 是一個用於創建擴展面板列表的 widget,每個擴展面板都可以展開或收縮。 | 屬性 | | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | expansionCallback | 一個回調函數,當擴展面板的擴展狀態更改時調用。該函數接受兩個參數,int index 表示擴展面板的索引,bool isExpanded 表示擴展面板的新狀態。 | | children | 一個包含 ExpansionPanel 對象的列表,每個對象表示一個擴展面板。 | | expandedHeaderPadding | 用於設置擴展面板標題內邊距的屬性。 | | animationDuration | 控制展開/收縮動畫的持續時間。 | | dividerColor | 用於設置擴展面板之間分隔線的顏色。 | | elevation | 擴展面板的陰影高度。 | ### ExpansionPanel: 擴展面板到基礎widget。 | 屬性 | | | --------------- | --------------------------------------------------------------------- | | headerBuilder | 一個返回 Widget 的回調函數,用於構建擴展面板的標題部分。 | | body | 擴展面板內容的主體部分。 | | isExpanded | 一個 bool 值,表示擴展面板的初始狀態,true 表示展開,false 表示收縮。 | | canTapOnHeader | 一個 bool 值,表示是否可以點擊擴展面板的標題區域進行展開/收縮。 | | backgroundColor | 用於設置擴展面板的背景顏色。 | <br>![26](https://hackmd.io/_uploads/ByDS7yOyC.png) ### Dialog: 是一個用於顯示對話框的 widget,通常用於提示、確認或顯示額外的信息給用戶。Flutter提供了以下方式實踐Dialog。 1. Dialog:來自定義外觀和布局的通用對話框。 2. AlertDialog:來快速創建包含標題和按鈕的簡單提示或確認框。 3. SimpleDialog:來顯示包含多個選項的對話框,用戶可以在其中進行選。 | 屬性 | | | --------------- | ---- | | title |對話框的標題。 | | content | 對話框的內容。 | | actions | 對話框的按鈕或操作。 | | backgroundColor | 對話框的背景顏色。 | | elevation | 對話框的陰影高度。 | | shape | 對話框的形狀,可以使用 RoundedRectangleBorder 或自定義的形狀。 | <br>![27](https://hackmd.io/_uploads/rkZ0qyO10.png) ### Router: Router(路由) 主要用於管理應用程式的導航和頁面切換。Flutter 的導航系統基於 Navigator 和 Route 這兩個主要的類別。Navigator 負責管理路由佇列,而 Route 定義了如何在畫面之間切換。Router的實作可以用以下兩種方式實踐。 1. 指定路由 - 指定路由名稱 - 使用 Navigator.pushNamed 或 Navigator.pushReplacementNamed 時,可以通過 routes 映射中的路由名稱進行導航。 ``` Dart MaterialApp( initialRoute: '/', routes: { '/': (context) => HomePage(), '/second': (context) => SecondPage(), }, ) ``` - 使用定義好的Router進行導航至目標頁面 ``` Dart //對應頁面執行導航 Navigator.pushNamed(context, ‘/second'); ``` - 跨頁參數傳遞方式 ``` Dart //指定arguments 參數進行傳遞,傳遞資料可為物件。 Navigator.pushNamed(context, 'Router_Path', arguments: '透過construct外部丟來的資料'); //接受端頁面,以下方法接收物件 var arguments = ModalRoute.of(context)?.settings.arguments; ``` 2. 不指定路由 - 使用的是 Navigator.push 或類似的方法,並且沒有指定路由名稱。此方法通常用於沒有特定路由名稱的動態頁面。 ``` Dart Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage(), ), ) ``` - 不定義路由跨頁取得參數方式 ``` Dart Navigator.of(context).push( MaterialPageRoute( builder: (context) => RouterPassDataPage(ParamExample: '透過construct外部丟來的資料'), ), ); ``` - Widget內部取得參數方法: ``` Dart class RouterPassDataPage extends StatefulWidget { String? ParamExample; RouterPassDataPage({super.key, this.ParamExample}); @override State<RouterPassDataPage> createState() => _RouterPassDataPageState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Router 取得資料'), ), body: Center( child: Text(‘${widget.ParamExample}'),//取得資料 ), ); } ``` <br>![28](https://hackmd.io/_uploads/S17kMlKyA.png) ### 多語系: Flutter 提供了良好的多國語系(Localization)支持,可以讓你輕鬆實現應用程式的國際化。具體做法如下6個步驟。 1. 引入元件 flutter_localizations, intl。 2. 設定pubspec.yaml 中 General 屬性為 true。 3. 新增i10n.yaml 於根目錄中。設定初始屬性。 4. 於目錄.lib/i10n/新增app_國家代碼.arb,並新增對應Json物件。 5. 終端機輸入flutter gen-l10n,動態生成物件。 6. 對應資料使用AppLocalizations.of(context)!.ObjectName使用物件。 <br><br> - 引入元件 flutter_localizations, intl: ![29](https://hackmd.io/_uploads/r19MEgKy0.png) &emsp;&emsp;引入後pubsepc.yaml中,新增會自動新增以下屬性。 ![30](https://hackmd.io/_uploads/SJRhBlFJR.png) <br> | 元件 | | | ---- | ---- | | flutter_localizations |Flutter 提供的一個插件,用於支援應用程式的本地化和多國語系。本地元件多國語係。 | | intl | intl 庫可以針對字串進行本地化(Internationalization) | - 設定pubspec.yaml 中設定 General 屬性為 true。 ![31](https://hackmd.io/_uploads/B1feDxFyA.png) - 新增i10n.yaml 於根目錄中。設定初始屬性。 ![32](https://hackmd.io/_uploads/rkWyFgK10.png) - 於目錄.lib/i10n/新增app_國家代碼.arb,並新增對應Json物件。 ![33](https://hackmd.io/_uploads/rypXilFkR.png) ![34](https://hackmd.io/_uploads/BybUslKk0.png =80%x) - 終端機輸入flutter gen-l10n,動態生成物件。 ![35](https://hackmd.io/_uploads/rJgIhxY1C.png) &emsp;&emsp;自動生成物件於.dart_tool/flutter_gen/gen_i10n/name 中 ![36](https://hackmd.io/_uploads/r1IchgK1A.png =60%x) ![37](https://hackmd.io/_uploads/ByKq6xYJR.png) ![38](https://hackmd.io/_uploads/Hy_6TxtkR.png) - 對應資料使用AppLocalizations.of(context)!.ObjectName使用物件。 ![39](https://hackmd.io/_uploads/HJudxWY1C.png) ![40](https://hackmd.io/_uploads/HJ0ox-K10.png =60%x) ### App本地儲存: 應用程式儲存本地端資料,以下提供兩種方式達成。 1. SharePreferences 2. SQLite #### SharePreferences(共享偏好設置): APP本地端資料儲存的一種方法,對應至雙平台 Android:SharedPreferences 或者 IOS:UserDefaults 機制。此機制提供一個簡易方式儲存小型且非敏感資料至APP中。即便關掉App後資料依舊會存在。可儲存的型態包含:bool , String , double 。 - 引入shared_preferences 元件 ![41](https://hackmd.io/_uploads/H1tloQt1C.png) 1. 儲存資料 ![42](https://hackmd.io/_uploads/rk7OsmKJA.png) 2. 讀取資料 ![43](https://hackmd.io/_uploads/HkEl3Qt1A.png) 3. 刪除資料 ![44](https://hackmd.io/_uploads/SJhj3QFJA.png) 4. 刪除SharePreference ![45](https://hackmd.io/_uploads/H1gkaQY1C.png) <br> ![46](https://hackmd.io/_uploads/S1KGpmKJC.png) #### SQLite: App本地端儲存表格資料一種方式。適用於保留大量且需要查詢的資料至本地端。 1. 引入 sqflite path。執行: flutter pub add sqflite path。 2. 定義TableModel class 。 3. 透過元件創建資料庫檔案,並依造 Model 創建 Table。 4. 執行insert , Query , Update , Delete 事件。 - 引入 sqflite path。執行: flutter pub add sqflite path ![61](https://hackmd.io/_uploads/H1MkiZrlC.png) - 定義TableModel class 。 ![62](https://hackmd.io/_uploads/H1qZjbBeA.png) - 透過元件創建資料庫檔案,並依造 Model 創建 Table。 ![63](https://hackmd.io/_uploads/BJmFmfreA.png) - 執行insert , Query , Update , Delete 事件。 - insert ![64](https://hackmd.io/_uploads/HJf1UfBl0.png =60%x) - Query ![65](https://hackmd.io/_uploads/ryLsPGBlC.png =80%x) - Update ![66](https://hackmd.io/_uploads/Sk5jymHx0.png =70%x) - Delete ![67](https://hackmd.io/_uploads/S1DEQXSgC.png =70%x) <br> ![68](https://hackmd.io/_uploads/BkNerQBlR.png =50%x) # 後端: Flutter 可以使用諸多元件來達到 http 元件或 dio 元件。這兩個元件都提供了在 Flutter 應用中發送 HTTP 請求的方法。本篇將以Http元件做範例。並且透過Provider 與 get_it 元件組成基本MVVM架構。 首先我們先取得以下3個元件:http , provider , get_it。 ![48](https://hackmd.io/_uploads/BJXBl1-xC.png) ### Http 是一個用於發送 HTTP 請求的常用 Dart 庫,它提供了一個簡單且方便的 API,用於與 Web 服務進行通信。這個庫支援多種 HTTP 請求方法,包括 GET、POST、PUT、DELETE 等。 <br> | 屬性 | | | ---- | ---- | | Uri |請求位址 ( URL ) | | headers |類型:Map<String, String> <br>包含 HTTP 響應的標頭信息,例如 Content-Type、Content-Length 等。 | | body |類型:String Json Type <br>發出Post請求時,給予後端的資訊。 | | statusCode | TTP 響應的狀態碼,例如 200 表示成功,404 表示未找到,500 表示服務器錯誤等。 | Http Get 示範 ![49](https://hackmd.io/_uploads/r1VFmzbxR.png) Http Post 示範 ![50](https://hackmd.io/_uploads/Hk5lqMZxA.png) response 的status code 為200時可以取得API回應內容。 ![51](https://hackmd.io/_uploads/Hy2kpMWlR.png =50%x) ### Json Object JSON(JavaScript Object Notation)是一種用於數據交換的輕量級數據格式。Http物件成功回傳response 格式即是以Json物件回傳。Json在Dart定義中為一個 Map<String ,dynamic> 型態。 - Json => Class 以上述Http Get 範例為樣版,我們回頭分析一下Flutter如何組成Json物件。回傳的response將有會以下個物件。 ![52](https://hackmd.io/_uploads/rJNpYpNgR.png =70%x) Response 回來後由Json轉成物件的方法如下圖所示,factory為Dart所擁有的宣告方式,以此宣告者在此程式生命週期時,僅只會有一個實例。 ![53](https://hackmd.io/_uploads/S1IXi6Nx0.png) Widget中取得物件方式。 ![54](https://hackmd.io/_uploads/BJ85aTNgC.png) - Class => Json 以下方法則是由Class轉換成Json物件。 ![55](https://hackmd.io/_uploads/Syj2mCEeA.png =70%x) 使用此函式將class,轉至成Json格式。 ![56](https://hackmd.io/_uploads/rylRJkHgA.png =100%x) ### Json產生器: https://app.quicktype.io/ 網路上已有不少套件可幫助Programer自動產生Json物件。(EX:Json_annotaion)這邊就不加以贅述。這邊提供一組線上自動轉換Json物件網址供大家使用。 ![57](https://hackmd.io/_uploads/SkWw2ergR.png) 僅只需要將Json物件傳給網站並挑選輸出為Dart語言。即可自動生成完整的Json宣告,與Json => class 及 class => Json 雙向轉換函式。 ### Provider Provider 是一個狀態管理套件,用於有效地管理應用程序的狀態並在 Widget 樹中共享數據。使用方式可以分成以下4個步驟。 1. ChangeNotifier 2. MultiProvider 3. Consumer Widget 4. Provider.of - ChangeNotifier: provider 提供了一個 ChangeNotifier 類,開發者可以通過擴展這個類來創建自己的可監聽狀態對象。當狀態發生變化時,ChangeNotifier 將通知其聽眾,並觸發 UI 的重建。使用方式讓class繼承 ChangeNotifier,但改動發生時執行notifyListeners。 ![58](https://hackmd.io/_uploads/Sy3ab-HxC.png =80%x) - MultiProvider: 宣告於父層級Provider widget,內部可以宣告多個ChangeNotifierProvider提供子層級Widget共用Provider內部資料。 ![59](https://hackmd.io/_uploads/BygYM-BeA.png =80%x) - Consumer Widget: 這是一個能夠自動訂閱和重新構建的 Widget。它只重新構建其子 Widget,而不是整個 Widget 樹,這有助於App提高性能。 ![82](https://hackmd.io/_uploads/ByxKSqrH0.png) - Provider.of: 通過 Provider.of<T>(context) 方法,你可以在 Widget 樹的任何地方獲取到提供的狀態對象。這讓你可以在需要的地方直接訪問狀態,並根據其變化更新 UI。 ![60](https://hackmd.io/_uploads/SktDtZrgA.png) &emsp;&emsp;需注意以Provider.of取得參數方式會使statefulWidget重新呼喚一次build動作。 ### Get_it get_it: Flutter 中的一個依賴注入(Dependency Injection)套件。它允許開發者在應用程序中方便地註冊、查找和解析依賴關係,以實現代碼的解耦和更好的可測性。 1. 冊依賴關係: - registerSingleton:註冊單例 - registerLazySingleton:註冊延遲單例 - registerFactory:註冊工廠依賴 2. 取得依賴的實例: - MyService myService = GetIt.I.get<MyService>()。 3. 移除依賴關係: - GetIt.I.unregister<MyService>(); get_it的宣告需至於App的最外層以供全部的子層級Widget tree 做調用。 ![69](https://hackmd.io/_uploads/r1ihAj94R.png =80%x ) 依據依賴注入(Dependency Injection)設計原則,我們會有UserService服務,提供我們的View使用。而UserService執行取值行為我們會委託給UserRepository,由UserRepository進行實際取值動作以此完成解耦合與分離關注。 ![70](https://hackmd.io/_uploads/Hyzxl25ER.png =80%x ) 最後直接於View Widget 透過 GetIt.I 直接取得我們已建置好的UserService。進行資料取值的動作。 ![71](https://hackmd.io/_uploads/rk9zf35NA.png =80%x ) ## 架構 MVVM架構建制:前段介紹我們已經認識了3個重要的套件。以下重點列出其功能。 1. Http :溝通API取的後端資料。 2. Provider :ChangeNotify通知UI Widget 進行局部畫面改動。 3. get_it :註冊依賴,使Class在子Widget中可直接被調用。 綜合上述3個套件,我們已經具有實作MVVM with Repository Design Pattern 的條件。下圖為 MVVM with Repository Design Pattern 的示意圖。我們直接以範例演示怎麼執行。 ![72](https://hackmd.io/_uploads/S1pDsh9NR.png) **Http:** 執行Repository角色,負責與後端API進行溝通。我們先定義Interface 抽象化我們取得資料的行為。 ![73](https://hackmd.io/_uploads/HJTnDRcN0.png =50%x ) 定義實作的Repository,後續依賴注入於我們使用的對象。 ![74](https://hackmd.io/_uploads/BJCiO1jVR.png =90%x ) **Provider:** 執行ViewModel角色,溝通後端API 並通知 View Widget 進行頁面的顯示。 ![76](https://hackmd.io/_uploads/rJe0BdysV0.png =80%x ) **Get_it:** 註冊 ViewModel 與 Repository 依賴關係。 ![77](https://hackmd.io/_uploads/rkuyZxiE0.png =80%x ) 在父層級Widget 中 宣告ViewModel 於MultiProvider中 ![78](https://hackmd.io/_uploads/rJfAfeiNA.png) 上述設定執行完成後,即可在APP內部與使用API的 View Widget中,透過Provider提供的Consume<T>,當API取值回來進行畫面更改。 ![79](https://hackmd.io/_uploads/HydYXxo4A.png) ## 總結&通往進階道路 學習完以上所有內容的成員恭喜你們,你們已經完成了 Flutter 的基礎課程。諸君可以正式開始你們Flutter開發的旅程。這裡僅只是你的旅程開始的起點而已。通往進階道路需要在實戰中磨練而成。<br><br>未來可能會分享一些跟實戰有關的開發技巧。那些將不再屬於基礎的課程內。讓我想想,到時候可能就會再出一集Flutter筆記進階篇吧。(笑) 最後讓我表明:<br> 以上內容即是本筆記的全部內容。本筆記適用於初學者入門Flutter基礎。 用於強化基本元件利用與初階架構所使用。 本筆記與首頁git範例程式碼皆為免費分享。請勿商用!萬分感謝!