Flutter for React Native 開發者
本文件適用於希望將其現有 React Native (RN) 知識應用於使用 Flutter 構建移動應用的 RN 開發者。如果您理解 RN 框架的基本原理,那麼您可以將本文件作為學習 Flutter 開發的入門指南。
本文件可以像菜譜一樣使用,您可以隨意跳轉並查詢與您的需求最相關的問題。
面向 JavaScript (ES6) 開發者的 Dart 簡介
#與 React Native 一樣,Flutter 使用響應式檢視。但是,雖然 RN 轉譯為原生 widget,但 Flutter 完全編譯為原生程式碼。Flutter 控制螢幕上的每個畫素,這避免了由於需要 JavaScript 橋接而引起的效能問題。
Dart 是一種易於學習的語言,並提供以下功能:
- 提供一種開源、可擴充套件的程式語言,用於構建 Web、伺服器和移動應用。
- 提供一種面向物件、單繼承的語言,使用 C 風格語法,AOT 編譯為原生程式碼。
- 可選地轉譯為 JavaScript。
- 支援介面和抽象類。
以下描述了 JavaScript 和 Dart 之間的一些區別。
入口點
#JavaScript 沒有預定義的入口函式——您定義入口點。
// JavaScript
function startHere() {
// Can be used as entry point
}在 Dart 中,每個應用都必須有一個頂層 main() 函式,作為應用的入口點。
/// Dart
void main() {}在 DartPad 中嘗試。
列印到控制檯
#要在 Dart 中列印到控制檯,請使用 print()。
// JavaScript
console.log('Hello world!');/// Dart
print('Hello world!');在 DartPad 中嘗試。
變數
#Dart 是型別安全的——它結合使用靜態型別檢查和執行時檢查來確保變數的值始終與變數的靜態型別匹配。儘管型別是強制性的,但某些型別註釋是可選的,因為 Dart 執行型別推斷。
建立和分配變數
#在 JavaScript 中,變數不能帶型別。
在 Dart 中,變數必須明確指定型別,或者型別系統必須自動推斷出正確的型別。
// JavaScript
let name = 'JavaScript';/// Dart
/// Both variables are acceptable.
String name = 'dart'; // Explicitly typed as a [String].
var otherName = 'Dart'; // Inferred [String] type.在 DartPad 中嘗試。
有關更多資訊,請參閱 Dart 的型別系統。
預設值
#在 JavaScript 中,未初始化的變數是 undefined。
在 Dart 中,未初始化的變數的初始值為 null。因為數字在 Dart 中是物件,所以即使是未初始化的數字型別變數也具有 null 值。
// JavaScript
let name; // == undefined// Dart
var name; // == null; raises a linter warning
int? x; // == null在 DartPad 中嘗試。
有關更多資訊,請參閱 變數 文件。
檢查 null 或零
#在 JavaScript 中,當使用 == 比較運算子時,1 或任何非空物件的值都被視為 true。
// JavaScript
let myNull = null;
if (!myNull) {
console.log('null is treated as false');
}
let zero = 0;
if (!zero) {
console.log('0 is treated as false');
}在 Dart 中,只有布林值 true 被視為 true。
/// Dart
var myNull = potentiallyNull();
if (myNull == null) {
print('use "== null" to check null');
}
var zero = 0;
if (zero == 0) {
print('use "== 0" to check zero');
}在 DartPad 中嘗試。
函式
#Dart 和 JavaScript 函式通常相似。主要區別在於宣告。
// JavaScript
function fn() {
return true;
}/// Dart
/// You can explicitly define the return type.
bool fn() {
return true;
}在 DartPad 中嘗試。
有關更多資訊,請參閱 函式 文件。
非同步程式設計
#Future
#與 JavaScript 一樣,Dart 支援單執行緒執行。在 JavaScript 中,Promise 物件表示非同步操作的最終完成(或失敗)及其結果值。
Dart 使用 Future 物件來處理這個問題。
// JavaScript
class Example {
_getIPAddress() {
const url = 'https://httpbin.org/ip';
return fetch(url)
.then(response => response.json())
.then(responseJson => {
const ip = responseJson.origin;
return ip;
});
}
}
function main() {
const example = new Example();
example
._getIPAddress()
.then(ip => console.log(ip))
.catch(error => console.error(error));
}
main();// Dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class Example {
Future<String> _getIPAddress() {
final url = Uri.https('httpbin.org', '/ip');
return http.get(url).then((response) {
final ip = jsonDecode(response.body)['origin'] as String;
return ip;
});
}
}
void main() {
final example = Example();
example
._getIPAddress()
.then((ip) => print(ip))
.catchError((error) => print(error));
}有關更多資訊,請參閱 Future 物件文件。
async 和 await
#async 函式宣告定義了一個非同步函式。
在 JavaScript 中,async 函式返回一個 Promise。await 運算子用於等待 Promise。
// JavaScript
class Example {
async function _getIPAddress() {
const url = 'https://httpbin.org/ip';
const response = await fetch(url);
const json = await response.json();
const data = json.origin;
return data;
}
}
async function main() {
const example = new Example();
try {
const ip = await example._getIPAddress();
console.log(ip);
} catch (error) {
console.error(error);
}
}
main();在 Dart 中,async 函式返回一個 Future,並且函式體被安排在稍後執行。await 運算子用於等待 Future。
// Dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class Example {
Future<String> _getIPAddress() async {
final url = Uri.https('httpbin.org', '/ip');
final response = await http.get(url);
final ip = jsonDecode(response.body)['origin'] as String;
return ip;
}
}
/// An async function returns a `Future`.
/// It can also return `void`, unless you use
/// the `avoid_void_async` lint. In that case,
/// return `Future<void>`.
void main() async {
final example = Example();
try {
final ip = await example._getIPAddress();
print(ip);
} catch (error) {
print(error);
}
}有關更多資訊,請參閱 async 和 await 文件。
基礎知識
#如何建立 Flutter 應用?
#要使用 React Native 建立應用,您需要在命令列執行 create-react-native-app。
create-react-native-app <projectname>要在 Flutter 中建立應用,請執行以下操作之一:
- 使用安裝了 Flutter 和 Dart 外掛的 IDE。
- 在命令列使用
flutter create命令。確保 Flutter SDK 在您的 PATH 中。
flutter create <projectname>有關更多資訊,請參閱 入門,其中詳細介紹瞭如何建立按鈕點選計數器應用。建立 Flutter 專案會構建執行 Android 和 iOS 裝置上的示例應用所需的所有檔案。
如何執行我的應用?
#在 React Native 中,您將在專案目錄中執行 npm run 或 yarn run。
您可以透過以下幾種方式執行 Flutter 應用:
- 在安裝了 Flutter 和 Dart 外掛的 IDE 中使用“執行”選項。
- 在專案的根目錄中使用
flutter run。
您的應用將在連線的裝置、iOS 模擬器或 Android 模擬器上執行。
有關更多資訊,請參閱 Flutter 入門 文件。
如何匯入 widget?
#在 React Native 中,您需要匯入每個必需的元件。
// React Native
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';在 Flutter 中,要使用 Material Design 庫中的 widget,請匯入 material.dart 包。要使用 iOS 風格的 widget,請匯入 Cupertino 庫。要使用更基本的 widget 集,請匯入 Widgets 庫。或者,您可以編寫自己的 widget 庫並匯入它。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:my_widgets/my_widgets.dart';無論您匯入哪個 widget 包,Dart 都只拉取您應用中使用的 widget。
有關更多資訊,請參閱 Flutter Widget 目錄。
Flutter 中 React Native 的“Hello world!”應用等效於什麼?
#在 React Native 中,HelloWorldApp 類擴充套件 React.Component 並透過返回檢視元件來實現渲染方法。
// React Native
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Text>Hello world!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
});
export default App;在 Flutter 中,您可以使用核心 widget 庫中的 Center 和 Text widget 建立一個相同的“Hello world!”應用。Center widget 成為 widget 樹的根,並有一個子項,即 Text widget。
// Flutter
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text('Hello, world!', textDirection: TextDirection.ltr),
),
);
}以下影像顯示了基本的 Flutter“Hello world!”應用的 Android 和 iOS UI。


現在您已經看到了最基本的 Flutter 應用,下一節將展示如何利用 Flutter 豐富的 widget 庫來建立一個現代、精緻的應用。
如何使用 widget 並巢狀它們以形成 widget 樹?
#在 Flutter 中,幾乎所有東西都是 widget。
Widget 是應用使用者介面的基本構建塊。您將 widget 組合成一個層次結構,稱為 widget 樹。每個 widget 都巢狀在其父 widget 中並從其父級繼承屬性。甚至應用程式物件本身也是一個 widget。沒有單獨的“應用程式”物件。相反,根 widget 充當此角色。
一個 widget 可以定義
- 結構元素——如按鈕或選單
- 樣式元素——如字型或配色方案
- 佈局的一個方面——如填充或對齊方式
以下示例顯示了使用 Material 庫中的 widget 的“Hello world!”應用。在此示例中,widget 樹巢狀在 MaterialApp 根 widget 中。
// Flutter
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: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(title: const Text('Welcome to Flutter')),
body: const Center(child: Text('Hello world')),
),
);
}
}以下影像顯示了由 Material Design widget 構建的“Hello world!”。您比基本“Hello world!”應用獲得了更多的免費功能。


編寫應用程式時,您將使用兩種型別的 widget:StatelessWidget 或 StatefulWidget。StatelessWidget 顧名思義——一個沒有狀態的 widget。StatelessWidget 建立一次,並且永遠不會改變其外觀。StatefulWidget 根據接收到的資料或使用者輸入動態改變狀態。
無狀態和有狀態 widget 之間的重要區別在於 StatefulWidget 具有一個 State 物件,該物件儲存狀態資料並將其跨樹重建傳遞,因此它不會丟失。
在簡單或基本應用中,巢狀 widget 很容易,但隨著程式碼庫變大和應用變得複雜,您應該將深度巢狀的 widget 分解為返回 widget 的函式或更小的類。建立單獨的函式和 widget 允許您在應用中重用元件。
如何建立可重用元件?
#在 React Native 中,您將定義一個類來建立可重用元件,然後使用 props 方法設定或返回所選元素的屬性和值。在下面的示例中,定義了 CustomCard 類,然後將其用於父類中。
// React Native
const CustomCard = ({ index, onPress }) => {
return (
<View>
<Text> Card {index} </Text>
<Button
title="Press"
onPress={() => onPress(index)}
/>
</View>
);
};
// Usage
<CustomCard onPress={this.onPress} index={item.key} />在 Flutter 中,定義一個類來建立自定義 widget,然後重用該 widget。您還可以定義並呼叫一個函式,該函式返回一個可重用 widget,如以下示例中的 build 函式所示。
/// Flutter
class CustomCard extends StatelessWidget {
const CustomCard({super.key, required this.index, required this.onPress});
final int index;
final void Function() onPress;
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: <Widget>[
Text('Card $index'),
TextButton(onPressed: onPress, child: const Text('Press')),
],
),
);
}
}
class UseCard extends StatelessWidget {
const UseCard({super.key, required this.index});
final int index;
@override
Widget build(BuildContext context) {
/// Usage
return CustomCard(
index: index,
onPress: () {
print('Card $index');
},
);
}
}在前面的示例中,CustomCard 類的建構函式使用 Dart 的花括號語法 { } 來指示命名引數。
要使這些欄位成為必需項,請從建構函式中刪除花括號,或向建構函式新增 required。
以下螢幕截圖顯示了可重用 CustomCard 類的一個示例。


專案結構和資源
#從哪裡開始編寫程式碼?
#從 lib/main.dart 檔案開始。建立 Flutter 應用時會自動生成它。
// Dart
void main() {
print('Hello, this is the main function.');
}在 Flutter 中,入口點檔案是 {project_name}/lib/main.dart,執行從 main 函式開始。
Flutter 應用中的檔案是如何組織的?
#當您建立新的 Flutter 專案時,它會構建以下目錄結構。您可以稍後自定義它,但這是您開始的地方。
┬
└ project_name
┬
├ android - Contains Android-specific files.
├ build - Stores iOS and Android build files.
├ ios - Contains iOS-specific files.
├ lib - Contains externally accessible Dart source files.
┬
└ src - Contains additional source files.
└ main.dart - The Flutter entry point and the start of a new app.
This is generated automatically when you create a Flutter
project.
It's where you start writing your Dart code.
├ test - Contains automated test files.
└ pubspec.yaml - Contains the metadata for the Flutter app.
This is equivalent to the package.json file in React Native.我應該把資源和資產放在哪裡,以及如何使用它們?
#Flutter 資源或資產是與您的應用捆綁和部署並在執行時可訪問的檔案。Flutter 應用可以包含以下資產型別:
- 靜態資料,例如 JSON 檔案
- 配置檔案
- 圖示和影像(JPEG、PNG、GIF、動畫 GIF、WebP、動畫 WebP、BMP 和 WBMP)
Flutter 使用位於專案根目錄的 pubspec.yaml 檔案來識別應用所需的資產。
flutter:
assets:
- assets/my_icon.png
- assets/background.pngassets 子部分指定應隨應用包含的檔案。每個資產都透過相對於 pubspec.yaml 檔案的顯式路徑來標識,其中資產檔案位於。資產宣告的順序無關緊要。使用的實際目錄(本例中為 assets)無關緊要。但是,雖然資產可以放置在任何應用目錄中,但最佳實踐是將其放置在 assets 目錄中。
在構建期間,Flutter 將資產放入一個名為 *資產包* 的特殊存檔中,應用在執行時從中讀取。當資產的路徑在 pubspec.yaml 的 assets 部分中指定時,構建過程會在相鄰子目錄中查詢任何同名檔案。這些檔案也與指定的資產一起包含在資產包中。Flutter 在為您的應用選擇適合解析度的影像時使用資產變體。
在 React Native 中,您將透過將影像檔案放置在原始碼目錄中並引用它來新增靜態影像。
<Image source={require('./my-icon.png')} />
// OR
<Image
source={{
url: 'https://reactnative.dev/img/tiny_logo.png'
}}
/>在 Flutter 中,在 widget 的構建方法中使用 Image.asset 建構函式將靜態影像新增到您的應用中。
Image.asset('assets/background.png');有關更多資訊,請參閱 在 Flutter 中新增資產和影像。
如何透過網路載入影像?
#在 React Native 中,您將在 Image 元件的 source prop 中指定 uri,如果需要,還會提供大小。
在 Flutter 中,使用 Image.network 建構函式從 URL 包含影像。
Image.network('https://docs.flutter.club.tw/assets/images/docs/owl.jpg');如何安裝包和包外掛?
#Flutter 支援使用其他開發者為 Flutter 和 Dart 生態系統貢獻的共享包。這使您無需從頭開發所有內容即可快速構建應用。包含平臺特定程式碼的包稱為包外掛。
在 React Native 中,您將使用 yarn add {package-name} 或 npm install --save {package-name} 從命令列安裝包。
在 Flutter 中,按照以下說明安裝包:
- 要新增
google_sign_in包作為依賴項,請執行flutter pub add
flutter pub add google_sign_in- 透過使用
flutter pub get從命令列安裝包。如果使用 IDE,它通常會為您執行flutter pub get,或者它可能會提示您這樣做。 - 將包匯入到您的應用程式碼中,如下所示:
import 'package:flutter/material.dart';您可以在 pub.dev 的 Flutter 包 部分找到 Flutter 開發者共享的許多包。
Flutter widget
#在 Flutter 中,您使用 widget 構建 UI,這些 widget 描述了在給定當前配置和狀態下其檢視應該是什麼樣子。
Widget 通常由許多小型、單一用途的 widget 組成,這些 widget 巢狀在一起以產生強大的效果。例如,Container widget 由多個負責佈局、繪畫、定位和大小調整的 widget 組成。具體來說,Container widget 包括 LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox 和 Transform widget。您可以透過新的獨特方式組合這些和其他簡單 widget,而不是透過子類化 Container 來產生自定義效果。
Center widget 是您如何控制佈局的另一個示例。要使 widget 居中,請將其包裝在 Center widget 中,然後使用佈局 widget 進行對齊、行、列和網格。這些佈局 widget 沒有自己的視覺表示。相反,它們唯一的目的是控制另一個 widget 佈局的某些方面。要了解 widget 以某種方式渲染的原因,檢查相鄰的 widget 通常很有幫助。
有關更多資訊,請參閱 Flutter 技術概述。
有關 Widgets 包中的核心 widget 的更多資訊,請參閱 Flutter 基本 widget、Flutter widget 目錄 或 Flutter widget 索引。
檢視
#View 容器等效於什麼?
#在 React Native 中,View 是一個支援 Flexbox、樣式、觸控處理和輔助功能控制元件佈局的容器。
在 Flutter 中,您可以使用 Widgets 庫中的核心佈局 widget,例如 Container、Column、Row 和 Center。有關更多資訊,請參閱 佈局 widget 目錄。
FlatList 或 SectionList 等效於什麼?
#List 是一個垂直排列的可滾動元件列表。
在 React Native 中,FlatList 或 SectionList 用於渲染簡單或分段列表。
// React Native
<FlatList
data={[ ... ]}
renderItem={({ item }) => <Text>{item.key}</Text>}
/>ListView 是 Flutter 最常用的滾動 widget。預設建構函式接受一個顯式子項列表。ListView 最適合少量 widget。對於大型或無限列表,請使用 ListView.builder,它按需構建其子項,並且只構建那些可見的子項。
var data = ['Hello', 'World'];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Text(data[index]);
},
);

要了解如何實現無限滾動列表,請參閱官方 infinite_list 示例。
如何使用 Canvas 進行繪圖?
#在 React Native 中,不存在畫布元件,因此使用 react-native-canvas 等第三方庫。
// React Native
const CanvasComp = () => {
const handleCanvas = (canvas) => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'skyblue';
ctx.beginPath();
ctx.arc(75, 75, 50, 0, 2 * Math.PI);
ctx.fillRect(150, 100, 300, 300);
ctx.stroke();
};
return (
<View>
<Canvas ref={this.handleCanvas} />
</View>
);
}在 Flutter 中,您可以使用 CustomPaint 和 CustomPainter 類來繪製到畫布上。
以下示例展示瞭如何在繪畫階段使用 CustomPaint widget 進行繪製。它實現了抽象類 CustomPainter,並將其傳遞給 CustomPaint 的 painter 屬性。CustomPaint 子類必須實現 paint() 和 shouldRepaint() 方法。
class MyCanvasPainter extends CustomPainter {
const MyCanvasPainter();
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()..color = Colors.amber;
canvas.drawCircle(const Offset(100, 200), 40, paint);
final Paint paintRect = Paint()..color = Colors.lightBlue;
final Rect rect = Rect.fromPoints(
const Offset(150, 300),
const Offset(300, 400),
);
canvas.drawRect(rect, paintRect);
}
@override
bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
}
class MyCanvasWidget extends StatelessWidget {
const MyCanvasWidget({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(body: CustomPaint(painter: MyCanvasPainter()));
}
}

佈局
#如何使用 widget 定義佈局屬性?
#在 React Native 中,大部分佈局可以透過傳遞給特定元件的 props 完成。例如,您可以在 View 元件上使用 style prop 來指定 flexbox 屬性。要將元件排列成一列,您需要指定一個 prop,例如:flexDirection: 'column'。
// React Native
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'center'
}}
>在 Flutter 中,佈局主要由專門設計用於提供佈局的 widget,結合控制 widget 及其樣式屬性來定義。
例如,Column 和 Row widget 接受一個子項陣列,並分別垂直和水平對齊它們。一個 Container widget 接受佈局和樣式屬性的組合,一個 Center widget 使其子 widget 居中。
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
Container(color: Colors.red, width: 100, height: 100),
Container(color: Colors.blue, width: 100, height: 100),
Container(color: Colors.green, width: 100, height: 100),
],
),
);Flutter 在其核心 widget 庫中提供了各種佈局 widget。例如,Padding、Align 和 Stack。
有關完整列表,請參閱 佈局 Widget。


如何分層 widget?
#在 React Native 中,元件可以使用 absolute 定位進行分層。
Flutter 使用 Stack widget 以分層方式排列子 widget。widget 可以完全或部分覆蓋基礎 widget。
Stack widget 相對於其盒子邊緣定位其子項。如果您只是想重疊多個子 widget,此類別非常有用。
@override
Widget build(BuildContext context) {
return Stack(
alignment: const Alignment(0.6, 0.6),
children: <Widget>[
const CircleAvatar(
backgroundImage: NetworkImage(
'https://avatars3.githubusercontent.com/u/14101776?v=4',
),
),
Container(color: Colors.black45, child: const Text('Flutter')),
],
);前面的示例使用 Stack 將一個容器(在半透明黑色背景上顯示其 Text)疊加在 CircleAvatar 之上。Stack 使用對齊屬性和 Alignment 座標來偏移文字。


有關更多資訊,請參閱 Stack 類文件。
樣式
#如何為我的元件設定樣式?
#在 React Native 中,內聯樣式和 stylesheets.create 用於為元件設定樣式。
// React Native
<View style={styles.container}>
<Text style={{ fontSize: 32, color: 'cyan', fontWeight: '600' }}>
This is a sample text
</Text>
</View>
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
});在 Flutter 中,Text widget 可以為其 style 屬性採用 TextStyle 類。如果您想在多個地方使用相同的文字樣式,您可以建立一個 TextStyle 類並將其用於多個 Text widget。
const TextStyle textStyle = TextStyle(
color: Colors.cyan,
fontSize: 32,
fontWeight: FontWeight.w600,
);
return const Center(
child: Column(
children: <Widget>[
Text('Sample text', style: textStyle),
Padding(
padding: EdgeInsets.all(20),
child: Icon(
Icons.lightbulb_outline,
size: 48,
color: Colors.redAccent,
),
),
],
),
);

如何使用 Icons 和 Colors?
#React Native 不支援圖示,因此使用第三方庫。
在 Flutter 中,匯入 Material 庫還會引入豐富的 Material 圖示 和 顏色。
return const Icon(Icons.lightbulb_outline, color: Colors.redAccent);使用 Icons 類時,請務必在專案的 pubspec.yaml 檔案中設定 uses-material-design: true。這可確保顯示圖示的 MaterialIcons 字型包含在您的應用中。通常,如果您打算使用 Material 庫,則應包含此行。
name: my_awesome_application
flutter:
uses-material-design: trueFlutter 的 Cupertino (iOS 風格) 包為當前的 iOS 設計語言提供高保真 widget。要使用 CupertinoIcons 字型,請在專案的 pubspec.yaml 檔案中新增 cupertino_icons 的依賴項。
name: my_awesome_application
dependencies:
cupertino_icons: ^1.0.8要全域性自定義元件的顏色和樣式,請使用 ThemeData 指定主題各個方面的預設顏色。將 MaterialApp 中的主題屬性設定為 ThemeData 物件。Colors 類提供 Material Design 調色盤 中的顏色。
以下示例將顏色方案從種子設定為 deepPurple,並將文字選擇設定為 red。
class SampleApp extends StatelessWidget {
const SampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textSelectionTheme: const TextSelectionThemeData(
selectionColor: Colors.red,
),
),
home: const SampleAppPage(),
);
}
}如何新增樣式主題?
#在 React Native 中,為樣式表中的元件定義通用主題,然後在元件中使用。
在 Flutter 中,透過在 ThemeData 類中定義樣式並將其傳遞給 MaterialApp widget 中的主題屬性,可以為幾乎所有內容建立統一的樣式。
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.cyan, brightness: Brightness.dark),
home: const StylingPage(),
);
}即使不使用 MaterialApp widget,也可以應用 Theme。Theme widget 在其 data 引數中接受一個 ThemeData,並將 ThemeData 應用於其所有子 widget。
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(primaryColor: Colors.cyan, brightness: brightness),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
//...
),
);
}狀態管理
#狀態是當 widget 構建時可以同步讀取的資訊,或者是在 widget 的生命週期內可能發生變化的資訊。要在 Flutter 中管理應用狀態,請使用 StatefulWidget 和一個 State 物件。
有關 Flutter 中管理狀態的方法的更多資訊,請參閱 狀態管理。
StatelessWidget
#Flutter 中的 StatelessWidget 是一個不需要狀態更改的 widget——它沒有內部狀態需要管理。
當您描述的使用者介面部分不依賴於物件本身中的配置資訊以及 widget 膨脹的 BuildContext 之外的任何內容時,無狀態 widget 會很有用。
AboutDialog、CircleAvatar 和 Text 是子類 StatelessWidget 的無狀態 widget 的示例。
import 'package:flutter/material.dart';
void main() => runApp(
const MyStatelessWidget(
text: 'StatelessWidget Example to show immutable data',
),
);
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Center(child: Text(text, textDirection: TextDirection.ltr));
}
}前面的示例使用 MyStatelessWidget 類的建構函式傳遞 text,該文字被標記為 final。此類擴充套件 StatelessWidget——它包含不可變資料。
無狀態 widget 的 build 方法通常只在三種情況下呼叫:
- 當 widget 插入到樹中時
- 當 widget 的父級更改其配置時
- 當它所依賴的
InheritedWidget發生更改時
StatefulWidget
#A StatefulWidget 是一個狀態會改變的 widget。使用 setState 方法來管理 StatefulWidget 的狀態變化。呼叫 setState() 會告訴 Flutter 框架狀態發生了變化,這會導致應用程式重新執行 build() 方法,以便應用程式能夠反映變化。
狀態是當 widget 構建時可以同步讀取的資訊,並且在 widget 的生命週期內可能會發生變化。widget 實現者有責任確保當狀態發生變化時,狀態物件會及時收到通知。當 widget 可以動態變化時使用 StatefulWidget。例如,widget 的狀態透過在表單中輸入或移動滑塊而改變。或者,它可能會隨著時間的推移而改變——也許資料來源更新了 UI。
Checkbox、Radio、Slider、InkWell、Form 和 TextField 是子類化 StatefulWidget 的有狀態 widget 的示例。
以下示例聲明瞭一個需要 createState() 方法的 StatefulWidget。此方法建立管理 widget 狀態的 State 物件 _MyStatefulWidgetState。
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key, required this.title});
final String title;
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}以下狀態類 _MyStatefulWidgetState 實現了 widget 的 build() 方法。當狀態改變時,例如,當用戶切換按鈕時,會使用新的切換值呼叫 setState()。這會導致框架在 UI 中重建此 widget。
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool showText = true;
bool toggleState = true;
Timer? t2;
void toggleBlinkState() {
setState(() {
toggleState = !toggleState;
});
if (!toggleState) {
t2 = Timer.periodic(const Duration(milliseconds: 1000), (t) {
toggleShowText();
});
} else {
t2?.cancel();
}
}
void toggleShowText() {
setState(() {
showText = !showText;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
if (showText)
const Text('This execution will be done before you can blink.'),
Padding(
padding: const EdgeInsets.only(top: 70),
child: ElevatedButton(
onPressed: toggleBlinkState,
child: toggleState
? const Text('Blink')
: const Text('Stop Blinking'),
),
),
],
),
),
);
}
}StatefulWidget 和 StatelessWidget 的最佳實踐是什麼?
#在設計 widget 時,需要考慮以下幾點:
- 確定 widget 應該是
StatefulWidget還是StatelessWidget。
在 Flutter 中,widget 要麼是有狀態的,要麼是無狀態的——這取決於它們是否依賴於狀態變化。
- 如果 widget 發生變化——使用者與它互動或資料來源中斷了 UI,那麼它就是 *有狀態的*。
- 如果 widget 是最終的或不可變的,那麼它就是 *無狀態的*。
- 確定哪個物件管理 widget 的狀態(對於
StatefulWidget)。
在 Flutter 中,管理狀態有三種主要方式:
- widget 管理自己的狀態
- 父 widget 管理 widget 的狀態
- 混合搭配方法
在決定使用哪種方法時,請考慮以下原則:
- 如果所討論的狀態是使用者資料,例如複選框的選中或未選中模式,或滑塊的位置,則狀態最好由父 widget 管理。
- 如果所討論的狀態是美學方面的,例如動畫,則 widget 本身最好管理狀態。
- 如有疑問,讓父 widget 管理子 widget 的狀態。
- 子類化 StatefulWidget 和 State。
MyStatefulWidget 類管理自己的狀態——它擴充套件了 StatefulWidget,它重寫了 createState() 方法來建立 State 物件,並且框架呼叫 createState() 來構建 widget。在此示例中,createState() 建立 _MyStatefulWidgetState 的例項,該例項在下一個最佳實踐中實現。
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key, required this.title});
final String title;
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
//...
}
}- 將 StatefulWidget 新增到 widget 樹中。
將自定義 StatefulWidget 新增到應用程式構建方法中的 widget 樹中。
class MyStatelessWidget extends StatelessWidget {
// This widget is the root of your application.
const MyStatelessWidget({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyStatefulWidget(title: 'State Change Demo'),
);
}
}

屬性
#在 React Native 中,大多陣列件在建立時可以使用不同的引數或屬性(稱為 props)進行自定義。這些引數可以使用 this.props 在子元件中使用。
// React Native
const CustomCard = ({ index, onPress }) => {
return (
<View>
<Text> Card {index} </Text>
<Button
title='Press'
onPress={() => onPress(index)}
/>
</View>
);
};
const App = () => {
const onPress = (index) => {
console.log('Card ', index);
};
return (
<View>
<FlatList
data={[ /* ... */ ]}
renderItem={({ item }) => (
<CustomCard onPress={onPress} index={item.key} />
)}
/>
</View>
);
};在 Flutter 中,您將標記為 final 的區域性變數或函式分配給引數化建構函式中接收的屬性。
/// Flutter
class CustomCard extends StatelessWidget {
const CustomCard({super.key, required this.index, required this.onPress});
final int index;
final void Function() onPress;
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: <Widget>[
Text('Card $index'),
TextButton(onPressed: onPress, child: const Text('Press')),
],
),
);
}
}
class UseCard extends StatelessWidget {
const UseCard({super.key, required this.index});
final int index;
@override
Widget build(BuildContext context) {
/// Usage
return CustomCard(
index: index,
onPress: () {
print('Card $index');
},
);
}
}

本地儲存
#如果您不需要儲存大量資料,並且它不需要結構,您可以使用 shared_preferences,它允許您讀寫基本資料型別的持久鍵值對:布林值、浮點數、整數、長整數和字串。
如何儲存對應用全域性的持久鍵值對?
#在 React Native 中,您使用 AsyncStorage 元件的 setItem 和 getItem 函式來儲存和檢索對應用持久且全域性的資料。
// React Native
const [counter, setCounter] = useState(0)
...
await AsyncStorage.setItem( 'counterkey', json.stringify(++this.state.counter));
AsyncStorage.getItem('counterkey').then(value => {
if (value != null) {
setCounter(value);
}
});在 Flutter 中,使用 shared_preferences 外掛來儲存和檢索對應用持久且全域性的鍵值資料。shared_preferences 外掛封裝了 iOS 上的 NSUserDefaults 和 Android 上的 SharedPreferences,為簡單資料提供持久儲存。
要新增 shared_preferences 包作為依賴項,請執行 flutter pub add
flutter pub add shared_preferencesimport 'package:shared_preferences/shared_preferences.dart';要實現持久資料,請使用 SharedPreferences 類提供的 setter 方法。setter 方法適用於各種基本型別,例如 setInt、setBool 和 setString。要讀取資料,請使用 SharedPreferences 類提供的相應 getter 方法。對於每個 setter,都有一個對應的 getter 方法,例如 getInt、getBool 和 getString。
Future<void> updateCounter() async {
final prefs = await SharedPreferences.getInstance();
int? counter = prefs.getInt('counter');
if (counter is int) {
await prefs.setInt('counter', ++counter);
}
setState(() {
_counter = counter;
});
}路由
#大多數應用包含多個螢幕,用於顯示不同型別的資訊。例如,您可能有一個產品螢幕,顯示圖片,使用者可以點選產品圖片在新螢幕上獲取更多產品資訊。
在 Android 中,新螢幕是新的 Activity。在 iOS 中,新螢幕是新的 ViewController。在 Flutter 中,螢幕只是 Widget!要在 Flutter 中導航到新螢幕,請使用 Navigator widget。
如何在螢幕之間導航?
#在 React Native 中,有三種主要的導航器:StackNavigator、TabNavigator 和 DrawerNavigator。每個都提供了一種配置和定義螢幕的方法。
// React Native
const MyApp = TabNavigator(
{ Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
{ tabBarOptions: { activeTintColor: '#e91e63' } }
);
const SimpleApp = StackNavigator({
Home: { screen: MyApp },
stackScreen: { screen: StackScreen }
});
export default (MyApp1 = DrawerNavigator({
Home: {
screen: SimpleApp
},
Screen2: {
screen: drawerScreen
}
}));在 Flutter 中,有兩個主要 widget 用於在螢幕之間導航:
Navigator 被定義為一個管理一組子 widget 的 widget,具有堆疊原則。導航器管理 Route 物件堆疊,並提供管理堆疊的方法,例如 Navigator.push 和 Navigator.pop。路由列表可以在 MaterialApp widget 中指定,或者它們可以動態構建,例如在英雄動畫中。以下示例在 MaterialApp widget 中指定命名路由。
class NavigationApp extends StatelessWidget {
// This widget is the root of your application.
const NavigationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
//...
routes: <String, WidgetBuilder>{
'/a': (context) => const UsualNavScreen(),
'/b': (context) => const DrawerNavScreen(),
},
//...
);
}
}要導航到命名路由,使用 Navigator.of() 方法指定 BuildContext(widget 樹中 widget 位置的控制代碼)。將路由名稱傳遞給 pushNamed 函式以導航到指定的路由。
Navigator.of(context).pushNamed('/a');您還可以使用 Navigator 的 push 方法,該方法將給定的 Route 新增到最緊密地包含給定 BuildContext 的導航器歷史中,並轉換到它。在以下示例中,MaterialPageRoute widget 是一個模態路由,它用平臺自適應過渡替換整個螢幕。它接受一個 WidgetBuilder 作為必需引數。
Navigator.push(
context,
MaterialPageRoute<void>(builder: (context) => const UsualNavScreen()),
);如何使用選項卡導航和抽屜導航?
#在 Material Design 應用中,Flutter 導航有兩種主要選項:選項卡和抽屜。當空間不足以支援選項卡時,抽屜提供了一個很好的替代方案。
選項卡導航
#在 React Native 中,createBottomTabNavigator 和 TabNavigation 用於顯示選項卡和選項卡導航。
// React Native
import { createBottomTabNavigator } from 'react-navigation';
const MyApp = TabNavigator(
{ Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
{ tabBarOptions: { activeTintColor: '#e91e63' } }
);Flutter 為抽屜和選項卡導航提供了幾個專門的 widget:
TabController- 協調
TabBar和TabBarView之間的選項卡選擇。 TabBar- 顯示一行水平選項卡。
Tab- 建立 Material Design TabBar 選項卡。
TabBarView- 顯示與當前選定選項卡對應的 widget。
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late TabController controller = TabController(length: 2, vsync: this);
@override
Widget build(BuildContext context) {
return TabBar(
controller: controller,
tabs: const <Tab>[
Tab(icon: Icon(Icons.person)),
Tab(icon: Icon(Icons.email)),
],
);
}
}TabController 是協調 TabBar 和 TabBarView 之間選項卡選擇所必需的。TabController 建構函式的 length 引數是選項卡的總數。TickerProvider 需要在每次幀觸發狀態變化時觸發通知。TickerProvider 是 vsync。每當您建立新的 TabController 時,將 vsync: this 引數傳遞給 TabController 建構函式。
TickerProvider 是一個介面,由能夠提供 Ticker 物件的類實現。Ticker 可以被任何需要在幀觸發時收到通知的物件使用,但它們最常透過 AnimationController 間接使用。AnimationController 需要一個 TickerProvider 來獲取其 Ticker。如果您從 State 建立一個 AnimationController,那麼您可以使用 TickerProviderStateMixin 或 SingleTickerProviderStateMixin 類來獲取合適的 TickerProvider。
Scaffold widget 包裝了一個新的 TabBar widget 並建立了兩個選項卡。TabBarView widget 作為 Scaffold widget 的 body 引數傳遞。所有對應於 TabBar widget 選項卡的螢幕都是 TabBarView widget 的子項,並帶有相同的 TabController。
class _NavigationHomePageState extends State<NavigationHomePage>
with SingleTickerProviderStateMixin {
late TabController controller = TabController(length: 2, vsync: this);
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Material(
color: Colors.blue,
child: TabBar(
tabs: const <Tab>[
Tab(icon: Icon(Icons.person)),
Tab(icon: Icon(Icons.email)),
],
controller: controller,
),
),
body: TabBarView(
controller: controller,
children: const <Widget>[HomeScreen(), TabScreen()],
),
);
}
}抽屜導航
#在 React Native 中,匯入所需的 react-navigation 包,然後使用 createDrawerNavigator 和 DrawerNavigation。
// React Native
export default (MyApp1 = DrawerNavigator({
Home: {
screen: SimpleApp
},
Screen2: {
screen: drawerScreen
}
}));在 Flutter 中,我們可以使用 Drawer widget 結合 Scaffold 來建立帶有 Material Design 抽屜的佈局。要嚮應用新增 Drawer,請將其包裝在 Scaffold widget 中。Scaffold widget 為遵循 Material Design 指南的應用提供一致的視覺結構。它還支援特殊的 Material Design 元件,例如 Drawers、AppBars 和 SnackBars。
Drawer widget 是一個 Material Design 面板,它從 Scaffold 的邊緣水平滑動以在應用程式中顯示導航連結。您可以提供 ElevatedButton、Text widget 或專案列表作為 Drawer widget 的子項顯示。在以下示例中,ListTile widget 提供點選導航。
@override
Widget build(BuildContext context) {
return Drawer(
elevation: 20,
child: ListTile(
leading: const Icon(Icons.change_history),
title: const Text('Screen2'),
onTap: () {
Navigator.of(context).pushNamed('/b');
},
),
);
}Scaffold widget 還包含一個 AppBar widget,當 Scaffold 中有 Drawer 時,它會自動顯示一個合適的 IconButton 來顯示 Drawer。Scaffold 會自動處理邊緣滑動手勢以顯示 Drawer。
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
elevation: 20,
child: ListTile(
leading: const Icon(Icons.change_history),
title: const Text('Screen2'),
onTap: () {
Navigator.of(context).pushNamed('/b');
},
),
),
appBar: AppBar(title: const Text('Home')),
body: Container(),
);
}

手勢檢測和觸控事件處理
#為了監聽和響應手勢,Flutter 支援輕擊、拖動和縮放。Flutter 中的手勢系統有兩個獨立的層。第一層包括原始指標事件,它們描述了螢幕上指標(例如觸控、滑鼠和觸控筆移動)的位置和移動。第二層包括手勢,它們描述了由一個或多個指標移動組成的語義操作。
如何向 widget 新增點選或按壓監聽器?
#在 React Native 中,使用 PanResponder 或 Touchable 元件將監聽器新增到元件中。
// React Native
<TouchableOpacity
onPress={() => {
console.log('Press');
}}
onLongPress={() => {
console.log('Long Press');
}}
>
<Text>Tap or Long Press</Text>
</TouchableOpacity>對於更復雜的手勢和將多個觸控組合成一個手勢,使用 PanResponder。
// React Native
const App = () => {
const panResponderRef = useRef(null);
useEffect(() => {
panResponderRef.current = PanResponder.create({
onMoveShouldSetPanResponder: (event, gestureState) =>
!!getDirection(gestureState),
onPanResponderMove: (event, gestureState) => true,
onPanResponderRelease: (event, gestureState) => {
const drag = getDirection(gestureState);
},
onPanResponderTerminationRequest: (event, gestureState) => true
});
}, []);
return (
<View style={styles.container} {...panResponderRef.current.panHandlers}>
<View style={styles.center}>
<Text>Swipe Horizontally or Vertically</Text>
</View>
</View>
);
};在 Flutter 中,要向 widget 新增點選(或按壓)監聽器,請使用具有 onPress: field 的按鈕或可觸控 widget。或者,透過將其包裝在 GestureDetector 中,向任何 widget 新增手勢檢測。
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(title: const Text('Gestures')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Tap, Long Press, Swipe Horizontally or Vertically'),
],
),
),
),
onTap: () {
print('Tapped');
},
onLongPress: () {
print('Long Pressed');
},
onVerticalDragEnd: (value) {
print('Swiped Vertically');
},
onHorizontalDragEnd: (value) {
print('Swiped Horizontally');
},
);
}有關更多資訊,包括 Flutter GestureDetector 回撥列表,請參閱 GestureDetector 類。


進行 HTTP 網路請求
#從網際網路獲取資料對於大多數應用程式來說是常見的。在 Flutter 中,http 包提供了從網際網路獲取資料的最簡單方法。
如何從 API 呼叫中獲取資料?
#React Native 提供 Fetch API 用於網路請求——您發出 fetch 請求,然後接收響應以獲取資料。
// React Native
const [ipAddress, setIpAddress] = useState('')
const _getIPAddress = () => {
fetch('https://httpbin.org/ip')
.then(response => response.json())
.then(responseJson => {
setIpAddress(responseJson.origin);
})
.catch(error => {
console.error(error);
});
};Flutter 使用 http 包。
要將 http 包新增為依賴項,請執行 flutter pub add。
flutter pub add httpFlutter 使用 dart:io 核心 HTTP 支援客戶端。要建立 HTTP 客戶端,請匯入 dart:io。
import 'dart:io';客戶端支援以下 HTTP 操作:GET、POST、PUT 和 DELETE。
final url = Uri.parse('https://httpbin.org/ip');
final httpClient = HttpClient();
Future<void> getIPAddress() async {
final request = await httpClient.getUrl(url);
final response = await request.close();
final responseBody = await response.transform(utf8.decoder).join();
final ip = jsonDecode(responseBody)['origin'] as String;
setState(() {
_ipAddress = ip;
});
}

表單輸入
#文字欄位允許使用者在您的應用中輸入文字,因此它們可用於構建表單、訊息應用、搜尋體驗等。Flutter 提供了兩個核心文字欄位 widget:TextField 和 TextFormField。
如何使用文字欄位 widget?
#在 React Native 中,要輸入文字,您可以使用 TextInput 元件顯示一個文字輸入框,然後使用回撥將值儲存在變數中。
// React Native
const [password, setPassword] = useState('')
...
<TextInput
placeholder="Enter your Password"
onChangeText={password => setPassword(password)}
/>
<Button title="Submit" onPress={this.validate} />在 Flutter 中,使用 TextEditingController 類管理 TextField widget。每當文字欄位被修改時,控制器都會通知其監聽器。
監聽器讀取文字和選擇屬性以瞭解使用者在欄位中輸入了什麼。您可以透過控制器的 text 屬性訪問 TextField 中的文字。
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Type something',
labelText: 'Text Field',
),
),
ElevatedButton(
child: const Text('Submit'),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Alert'),
content: Text('You typed ${_controller.text}'),
);
},
);
},
),
],
);
}在此示例中,當用戶單擊提交按鈕時,警報對話方塊會顯示文字欄位中當前輸入的文字。這是透過使用 AlertDialog widget 實現的,該 widget 顯示警報訊息,並且透過 TextEditingController 的 text 屬性訪問 TextField 中的文字。
如何使用 Form widget?
#在 Flutter 中,使用 Form widget,其中 TextFormField widget 和提交按鈕作為子項傳遞。TextFormField widget 有一個名為 onSaved 的引數,它接受一個回撥並在表單儲存時執行。FormState 物件用於儲存、重置或驗證作為此 Form 後代的每個 FormField。要獲取 FormState,您可以使用 Form.of() 和其祖先是 Form 的上下文,或將 GlobalKey 傳遞給 Form 建構函式並呼叫 GlobalKey.currentState()。
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if (value != null && value.contains('@')) {
return null;
}
return 'Not a valid email.';
},
onSaved: (val) {
_email = val;
},
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
),
ElevatedButton(onPressed: _submit, child: const Text('Login')),
],
),
);
}以下示例展示瞭如何使用 Form.save() 和 formKey(這是一個 GlobalKey)在提交時儲存表單。
void _submit() {
final form = formKey.currentState;
if (form != null && form.validate()) {
form.save();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Alert'),
content: Text('Email: $_email, password: $_password'),
);
},
);
}
}

平臺特定程式碼
#構建跨平臺應用時,您希望儘可能在不同平臺重用程式碼。但是,可能會出現根據作業系統不同而程式碼需要不同的情況。這需要透過宣告特定平臺來單獨實現。
在 React Native 中,將使用以下實現:
// React Native
if (Platform.OS === 'ios') {
return 'iOS';
} else if (Platform.OS === 'android') {
return 'android';
} else {
return 'not recognised';
}在 Flutter 中,使用以下實現:
final platform = Theme.of(context).platform;
if (platform == TargetPlatform.iOS) {
return 'iOS';
}
if (platform == TargetPlatform.android) {
return 'android';
}
if (platform == TargetPlatform.fuchsia) {
return 'fuchsia';
}
return 'not recognized ';除錯
#在 Flutter 中,我可以使用哪些工具來除錯我的應用?
#使用 DevTools 套件除錯 Flutter 或 Dart 應用。
DevTools 支援效能分析、檢查堆、檢查 widget 樹、日誌診斷、除錯、觀察已執行程式碼行、除錯記憶體洩漏和記憶體碎片。有關更多資訊,請檢視 DevTools 文件。
如果您正在使用 IDE,您可以使用 IDE 的偵錯程式除錯您的應用程式。
如何執行熱過載?
#Flutter 的有狀態熱過載功能可幫助您快速輕鬆地試驗、構建 UI、新增功能和修復錯誤。您無需在每次進行更改時都重新編譯應用程式,而是可以立即熱過載應用程式。應用程式會更新以反映您的更改,並且應用程式的當前狀態會保留。
首先,在您首選的 IDE 中,啟用自動儲存和儲存時熱過載。
VS Code
將以下內容新增到你的 .vscode/settings.json 檔案中
json "files.autoSave": "afterDelay", "dart.flutterHotReloadOnSave": "all", Android Studio 和 IntelliJ
* 開啟 Settings > Tools > Actions on Save 並選擇 Configure autosave options。 - 選中 Save files if the IDE is idle for X seconds 選項。 - **推薦:**設定較小的延遲時間。例如,2 秒。
* 開啟 Settings > Languages & Frameworks > Flutter。 - 選中 Perform hot reload on save 選項。
在 React Native 中,iOS 模擬器上的快捷方式是 ⌘R,Android 模擬器上是兩次點選 R。
在 Flutter 中,如果您使用的是 IntelliJ IDE 或 Android Studio,您可以選擇 Save All (⌘s/ctrl-s),或者單擊工具欄上的 Hot Reload 按鈕。如果您使用 flutter run 在命令列執行應用,請在終端視窗中輸入 r。您還可以透過在終端視窗中輸入 R 來執行完全重啟。
如何訪問應用內開發者選單?
#在 React Native 中,可以透過搖動裝置訪問開發者選單:iOS 模擬器為 ⌘D,Android 模擬器為 ⌘M。
在 Flutter 中,如果您正在使用 IDE,則可以使用 IDE 工具。如果您使用 flutter run 啟動應用程式,您還可以透過在終端視窗中鍵入 h 來訪問選單,或者鍵入以下快捷方式:
| 動作 | 終端快捷方式 | 除錯函式和屬性 |
|---|---|---|
| 應用的 Widget 層次結構 | w | debugDumpApp() |
| 應用的渲染樹 | t | debugDumpRenderTree() |
| 圖層 | L | debugDumpLayerTree() |
| 無障礙功能 | S(遍歷順序)或U(反向命中測試順序) | debugDumpSemantics() |
| 切換 widget 檢查器 | i | WidgetsApp. showWidgetInspectorOverride |
| 切換施工線的顯示 | p | debugPaintSizeEnabled |
| 模擬不同的作業系統 | o | defaultTargetPlatform |
| 顯示效能疊加 | P | WidgetsApp. showPerformanceOverlay |
| 將螢幕截圖儲存到 flutter.png | s | |
| 退出 | q |
動畫
#精心設計的動畫讓 UI 感覺直觀,有助於打造精緻應用的觀感,並改善使用者體驗。Flutter 的動畫支援使其易於實現簡單和複雜的動畫。Flutter SDK 包含許多 Material Design widget,其中包含標準運動效果,您可以輕鬆自定義這些效果以個性化您的應用。
在 React Native 中,Animated API 用於建立動畫。
在 Flutter 中,使用 Animation 類和 AnimationController 類。Animation 是一個抽象類,它瞭解其當前值和狀態(已完成或已解除)。AnimationController 類允許您向前或向後播放動畫,或者停止動畫並設定動畫的特定值以自定義運動。
如何新增簡單的淡入動畫?
#在下面的 React Native 示例中,使用 Animated API 建立了一個動畫元件 FadeInView。定義了初始不透明度狀態、最終狀態以及過渡發生持續時間。動畫元件新增在 Animated 元件內部,不透明度狀態 fadeAnim 對映到我們要動畫的 Text 元件的不透明度,然後呼叫 start() 啟動動畫。
// React Native
const FadeInView = ({ style, children }) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 10000
}).start();
}, []);
return (
<Animated.View style={{ ...style, opacity: fadeAnim }}>
{children}
</Animated.View>
);
};
...
<FadeInView>
<Text> Fading in </Text>
</FadeInView>
...要在 Flutter 中建立相同的動畫,請建立一個名為 controller 的 AnimationController 物件並指定持續時間。預設情況下,AnimationController 在給定持續時間內線性生成從 0.0 到 1.0 的值。每當執行您的應用程式的裝置準備顯示新幀時,動畫控制器都會生成一個新值。通常,這個速率約為每秒 60 個值。
定義 AnimationController 時,必須傳入一個 vsync 物件。vsync 的存在可以防止螢幕外動畫消耗不必要的資源。您可以透過將 TickerProviderStateMixin 新增到類定義中,將您的有狀態物件用作 vsync。AnimationController 需要一個 TickerProvider,它透過建構函式上的 vsync 引數進行配置。
A Tween 描述了起始值和結束值之間的插值,或者從輸入範圍到輸出範圍的對映。要將 Tween 物件與動畫一起使用,請呼叫 Tween 物件的 animate() 方法並傳入您要修改的 Animation 物件。
對於此示例,使用了 FadeTransition widget,並且 opacity 屬性對映到 animation 物件。
要啟動動畫,請使用 controller.forward()。也可以使用控制器執行其他操作,例如 fling() 或 repeat()。對於此示例,FlutterLogo widget 在 FadeTransition widget 內部使用。
import 'package:flutter/material.dart';
void main() {
runApp(const Center(child: LogoFade()));
}
class LogoFade extends StatefulWidget {
const LogoFade({super.key});
@override
State<LogoFade> createState() => _LogoFadeState();
}
class _LogoFadeState extends State<LogoFade>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 3000),
vsync: this,
);
final CurvedAnimation curve = CurvedAnimation(
parent: controller,
curve: Curves.easeIn,
);
animation = Tween(begin: 0.0, end: 1.0).animate(curve);
controller.forward();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animation,
child: const SizedBox(height: 300, width: 300, child: FlutterLogo()),
);
}
}

如何向卡片新增滑動動畫?
#在 React Native 中,PanResponder 或第三方庫用於滑動動畫。
在 Flutter 中,要新增滑動動畫,請使用 Dismissible widget 並巢狀子 widget。
return Dismissible(
key: Key(widget.key.toString()),
onDismissed: (dismissDirection) {
cards.removeLast();
},
child: Container(
//...
),
);

React Native 和 Flutter widget 等效元件
#下表列出了常用的 React Native 元件及其對應的 Flutter widget 和常用 widget 屬性。
| React Native 元件 | Flutter Widget | 描述 |
|---|---|---|
Button | 凸起按鈕 | 一個基本的凸起按鈕。 |
| onPressed [必需] | 按鈕被點選或以其他方式啟用時的回撥。 | |
| Child | 按鈕的標籤。 | |
Button | 文字按鈕 | 一個基本的平面按鈕。 |
| onPressed [必需] | 按鈕被點選或以其他方式啟用時的回撥。 | |
| Child | 按鈕的標籤。 | |
ScrollView | ListView | 一個線性排列的可滾動 widget 列表。 |
| children | ( <Widget> [ ]) 要顯示的子 widget 列表。 | |
| controller | [ ScrollController ] 一個可用於控制可滾動 widget 的物件。 | |
| itemExtent | [ double ] 如果非空,則強制子項在滾動方向上具有給定範圍。 | |
| scrollDirection | [ Axis ] 滾動檢視滾動的軸。 | |
FlatList | ListView.builder | 按需建立的線性 widget 陣列的建構函式。 |
| itemBuilder [必需] | [IndexedWidgetBuilder] 幫助按需構建子項。此回撥僅在索引大於或等於零且小於 itemCount 時呼叫。 | |
| itemCount | [ int ] 提高了 ListView 估算最大滾動範圍的能力。 | |
Image | Image | 顯示影像的 widget。 |
| image [必需] | 要顯示的影像。 | |
| Image.asset | 提供了幾個建構函式,用於指定影像的各種方式。 | |
| width, height, color, alignment | 影像的樣式和佈局。 | |
| fit | 將影像刻入佈局期間分配的空間。 | |
Modal | ModalRoute | 阻止與以前路由互動的路由。 |
| animation | 驅動路由轉換和前一個路由向前轉換的動畫。 | |
ActivityIndicator | CircularProgressIndicator | 顯示沿圓形進度的 widget。 |
| strokeWidth | 用於繪製圓的線的寬度。 | |
| backgroundColor | 進度指示器的背景色。預設為當前主題的 ThemeData.backgroundColor。 | |
ActivityIndicator | LinearProgressIndicator | 顯示沿直線進度的 widget。 |
| value | 此進度指示器的值。 | |
RefreshControl | RefreshIndicator | 支援 Material“滑動重新整理”慣用語的 widget。 |
| color | 進度指示器的前景色。 | |
| onRefresh | 當用戶將重新整理指示器拖動足夠遠以表明他們希望應用程式重新整理時呼叫的函式。 | |
View | 容器 | 一個圍繞子 widget 的 widget。 |
View | Column | 一個以垂直陣列顯示其子項的 widget。 |
View | Row | 一個以水平陣列顯示其子項的 widget。 |
View | Center | 一個將其子項居中的 widget。 |
View | Padding | 一個透過給定填充內嵌其子項的 widget。 |
| padding [必需] | [ EdgeInsets ] 要內嵌子項的空間量。 | |
TouchableOpacity | GestureDetector | 檢測手勢的 widget。 |
| onTap | 發生點選時的回撥。 | |
| onDoubleTap | 在同一位置快速連續點選兩次時的回撥。 | |
TextInput | TextInput | 系統文字輸入控制元件的介面。 |
| controller | [ TextEditingController ] 用於訪問和修改文字。 | |
文字 | 文字 | 顯示具有單一樣式的字串文字的 Text widget。 |
| data | [ String ] 要顯示的文字。 | |
| textDirection | [ TextAlign ] 文字流動的方向。 | |
Switch | Switch | 一個 Material Design 開關。 |
| value [必需] | [ boolean ] 此開關是否開啟或關閉。 | |
| onChanged [必需] | [ callback ] 當用戶開啟或關閉開關時呼叫。 |