Flutter 面向 React Native 開發者的介紹
瞭解如何在構建 Flutter 應用時應用 React Native 開發者的知識。
本文件面向希望將現有的 RN 知識應用於使用 Flutter 構建移動應用的 React Native (RN) 開發者。如果您瞭解 RN 框架的基礎知識,則可以使用本文件作為開始學習 Flutter 開發的一種方式。
本文件可以像菜譜一樣使用,您可以隨意跳轉並查詢與您的需求最相關的問題。
Dart 語言簡介(針對 JavaScript 開發者 ES6)
#與 React Native 類似,Flutter 使用類 React 樣式的檢視。但是,雖然 RN 轉譯為原生元件,但 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 中試用。
有關更多資訊,請參閱有關 變數的文件。
檢查空值或零
#在 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 中試用。
有關更多資訊,請參閱有關 函式的文件。
非同步程式設計
#Futures
#與 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 入門 文件。
如何匯入元件?
#在 React Native 中,您需要匯入每個必需的元件。
// React Native
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
在 Flutter 中,要使用 Material Design 庫中的元件,請匯入 material.dart 包。要使用 iOS 風格的元件,請匯入 Cupertino 庫。要使用更基本的元件集,請匯入 Widgets 庫。或者,您可以編寫自己的元件庫並匯入它。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:my_widgets/my_widgets.dart';
無論您匯入哪個元件包,Dart 都會僅提取應用程式中使用的元件。
有關更多資訊,請參閱 Flutter 元件目錄。
Flutter 中 React Native 的 "Hello world!" 應用等效於什麼?
#在 React Native 中,HelloWorldApp 類擴充套件了 React.Component 並實現了 render 方法,透過返回 view 元件。
// 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 中,您可以使用核心元件庫中的 Center 和 Text 元件建立相同的 "Hello world!" 應用程式。Center 元件成為元件樹的根,並具有一個子元件,即 Text 元件。
// Flutter
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text('Hello, world!', textDirection: TextDirection.ltr),
),
);
}
以下影像顯示了 Android 和 iOS UI 的基本 Flutter "Hello world!" 應用程式。
現在您已經看到了最基本的 Flutter 應用程式,下一節將展示如何利用 Flutter 豐富的元件庫來建立現代、精緻的應用程式。
如何使用元件並將它們巢狀以形成元件樹?
#在 Flutter 中,幾乎所有東西都是一個元件。
元件是應用程式使用者介面的基本構建塊。您將元件組合成一個層次結構,稱為元件樹。每個元件巢狀在父元件內部並從其父元件繼承屬性。甚至應用程式物件本身也是一個元件。沒有單獨的“應用程式”物件。相反,根元件扮演此角色。
一個元件可以定義
- 一個結構元素——例如按鈕或選單
- 一個樣式元素——例如字型或配色方案
- 一個佈局方面——例如填充或對齊方式
以下示例顯示了使用 Material 庫中的元件的 "Hello world!" 應用程式。在此示例中,元件樹巢狀在 MaterialApp 根元件內部。
// 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 元件構建的 "Hello world!"。您比在基本的 "Hello world!" 應用程式中獲得更多的免費功能。
在編寫應用程式時,您將使用兩種型別的元件:StatelessWidget 或 StatefulWidget。StatelessWidget 正如其名稱所示——一個沒有狀態的元件。StatelessWidget 一旦建立,就永遠不會更改其外觀。StatefulWidget 會根據接收到的資料或使用者輸入動態更改狀態。
有狀態元件和無狀態元件之間的重要區別在於,StatefulWidget 具有一個 State 物件,該物件儲存狀態資料並在樹重建期間攜帶它,因此不會丟失。
在簡單或基本的應用程式中,巢狀元件很容易,但隨著程式碼庫的增大和應用程式變得複雜,您應該將深度巢狀的元件分解為返回元件或更小類的函式。建立單獨的函式和元件允許您在應用程式內重用這些元件。
如何建立可重用的元件?
#在 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/// 包含 Android 特定的檔案。
- build/// 儲存 iOS 和 Android 構建檔案。
- ios/// 包含 iOS 特定的檔案。
lib/// 包含外部可訪問的 Dart 原始碼檔案。
- src/// 包含額外的原始碼檔案。
- main.dart// Flutter 的入口點和新應用的開始。這在建立 Flutter 專案時自動生成。這是你開始編寫 Dart 程式碼的地方。
- test/// 包含自動化測試檔案。
- pubspec.yaml// 包含 Flutter 應用的元資料。這相當於 React Native 中的 package.json 檔案。
我應該將資源和資產放在哪裡以及如何使用它們?
#Flutter 資源或資產是與你的應用捆綁和部署的檔案,並且可以在執行時訪問。Flutter 應用可以包含以下資產型別
- 靜態資料,例如 JSON 檔案
- 配置檔案
- 圖示和影像(JPEG、PNG、GIF、動畫 GIF、WebP、動畫 WebP、BMP 和 WBMP)
Flutter 使用位於專案根目錄的 pubspec.yaml 檔案來識別應用所需的資產。
flutter:
assets:
- assets/my_icon.png
- assets/background.png
assets 子部分指定應包含在應用中的檔案。每個資產透過相對於 pubspec.yaml 檔案顯式路徑標識,該檔案是資產檔案所在的位置。資產宣告的順序無關緊要。使用的實際目錄(在本例中為 assets)無關緊要。但是,雖然可以將資產放置在任何應用目錄中,但最好將它們放置在 assets 目錄中。
在構建過程中,Flutter 將資產放入一個名為 asset bundle 的特殊存檔中,應用從執行時讀取該存檔。當在 pubspec.yaml 的 assets 部分中指定資產的路徑時,構建過程會查詢相鄰子目錄中具有相同名稱的任何檔案。這些檔案也包含在資產包中以及指定的資產中。Flutter 在為你的應用選擇適當解析度的影像時使用資產變體。
在 React Native 中,你會透過將影像檔案放置在原始碼目錄中並引用它來新增靜態影像。
<Image source={require('./my-icon.png')} />
// OR
<Image
source={{
url: 'https://reactnative.dev/img/tiny_logo.png'
}}
/>
在 Flutter 中,使用 Image.asset 建構函式在 widget 的 build 方法中新增靜態影像。
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';
你可以在 Flutter 包 部分的 pub.dev 上找到 Flutter 開發人員共享的許多包。
Flutter 元件
#在 Flutter 中,你透過描述其檢視應該在給定其當前配置和狀態下如何顯示的 widget 來構建你的 UI。
widget 通常由許多小的、單一用途的 widget 組成,這些 widget 被巢狀以產生強大的效果。例如,Container widget 由幾個負責佈局、繪製、定位和調整大小的 widget 組成。具體來說,Container widget 包含 LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox 和 Transform widget。與其透過子類化 Container 來產生自定義效果,不如以新的獨特方式組合這些和其他簡單的 widget。
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。預設建構函式採用明確的子列表。對於少量 widget,ListView 最合適。對於大型或無限列表,請使用 ListView.builder,它按需構建其子項,並且僅構建可見的子項。
var data = ['Hello', 'World'];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Text(data[index]);
},
);
要了解如何實現無限滾動列表,請參閱官方 infinite_list 示例。
如何使用 Canvas 進行繪製或繪畫?
#在 React Native 中,沒有 canvas 元件,因此使用第三方庫,例如 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 元件進行繪製。它實現了抽象類 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()));
}
}
佈局
#如何使用元件定義佈局屬性?
#在 React Native 中,大部分佈局都可以透過傳遞給特定元件的 props 來完成。例如,您可以使用 View 元件上的 style prop 來指定 flexbox 屬性。要將您的元件排列成一列,您將指定一個 prop,例如:flexDirection: 'column'。
// React Native
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
在 Flutter 中,佈局主要由專門設計用於提供佈局的元件定義,並結合控制組件及其樣式屬性。
例如,Column 和 Row 元件接受一個子元件陣列,並分別將它們垂直和水平對齊。一個 Container 元件接受佈局和樣式屬性的組合,而一個 Center 元件將其子元件居中。
@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 在其核心元件庫中提供了各種佈局元件。例如,Padding、Align 和 Stack。
有關完整列表,請參閱 佈局元件。
如何分層元件?
#在 React Native 中,可以使用 absolute 定位來分層元件。
Flutter 使用 Stack 元件以層疊方式排列子元件。這些元件可以完全或部分重疊基礎元件。
Stack 元件根據其框的邊緣定位其子元件。如果只想重疊幾個子元件,則此類很有用。
@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 將一個 Container(顯示其 Text 在半透明黑色背景上)疊加在 CircleAvatar 之上。Stack 使用 alignment 屬性和 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 元件可以接受 TextStyle 類作為其樣式屬性。如果您想在多個地方使用相同的文字樣式,可以建立一個 TextStyle 類,並將其用於多個 Text 元件。
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: true
Flutter 的 Cupertino (iOS 風格) 包為當前的 iOS 設計語言提供高保真元件。要使用 CupertinoIcons 字型,請在專案的 pubspec.yaml 檔案中新增 cupertino_icons 依賴項。
name: my_awesome_application
dependencies:
cupertino_icons: ^1.0.8
要全域性自定義元件的顏色和樣式,請使用 ThemeData 指定主題各個方面的預設顏色。將主題屬性設定為 MaterialApp 中的 ThemeData 物件。Colors 類提供來自 Material Design 顏色調色盤 的顏色。
以下示例將顏色方案從 seed 設定為 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 元件中的 theme 屬性,為幾乎所有內容建立統一的樣式。
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.cyan, brightness: Brightness.dark),
home: const StylingPage(),
);
}
即使不使用 MaterialApp 元件,也可以應用 Theme。Theme 元件在 data 引數中接受 ThemeData,並將 ThemeData 應用於其所有子元件。
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(primaryColor: Colors.cyan, brightness: brightness),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
//...
),
);
}
狀態管理
#狀態是指在構建元件時可以同步讀取的資訊,或者在元件生命週期內可能發生變化的資訊。要在 Flutter 中管理應用程式狀態,請使用 StatefulWidget 與 State 物件配對。
有關在 Flutter 中處理狀態管理方式的更多資訊,請參閱 狀態管理。
StatelessWidget
#Flutter 中的 StatelessWidget 是不需要狀態更改的元件——它沒有要管理的內部狀態。
當您描述的使用者介面部分不依賴於物件本身中的配置資訊以及元件膨脹的 BuildContext 時,無狀態元件很有用。
AboutDialog、CircleAvatar 和 Text 是繼承自 StatelessWidget 的無狀態元件示例。
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,該 text 被標記為 final。此類擴充套件了 StatelessWidget——它包含不可變資料。
無狀態元件的 build 方法通常僅在三種情況下呼叫
- 當元件插入樹時
- 當父元件更改其配置時
- 當它所依賴的
InheritedWidget發生更改時
StatefulWidget
#一個 StatefulWidget 是狀態發生變化的元件。使用 setState 方法來管理 StatefulWidget 的狀態更改。呼叫 setState() 會告訴 Flutter 框架狀態發生變化,這將導致應用程式重新執行 build() 方法,以便應用程式可以反映更改。
狀態 是在構建元件時可以同步讀取的資訊,並且可能在元件的生命週期內發生變化。確保在狀態發生變化時及時通知狀態物件是元件實現者的責任。當元件可以動態更改時,使用 StatefulWidget。例如,元件的狀態透過在表單中鍵入或移動滑塊來更改。或者,它可能會隨著時間的推移而變化——也許資料來源更新了 UI。
Checkbox、Radio、Slider、InkWell、Form 和 TextField 是繼承自 StatefulWidget 的有狀態元件示例。
以下示例聲明瞭一個 StatefulWidget,該元件需要一個 createState() 方法。此方法建立管理元件狀態的狀態物件 _MyStatefulWidgetState。
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key, required this.title});
final String title;
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
以下狀態類 _MyStatefulWidgetState 實現了元件的 build() 方法。當狀態發生變化時,例如,當用戶切換按鈕時,會使用新的切換值呼叫 setState()。這將導致框架在 UI 中重新構建此小部件。
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 的最佳實踐是什麼?
#以下是在設計您的元件時需要考慮的一些事項
- 確定元件是否應為
StatefulWidget或StatelessWidget。
在 Flutter 中,元件要麼是有狀態的,要麼是無狀態的——具體取決於它是否依賴於狀態更改。
- 如果元件發生更改——使用者與之互動或資料來源中斷 UI,那麼它是有狀態的。
- 如果元件是最終的或不可變的,那麼它是無狀態的。
- 確定哪個物件管理元件的狀態(對於
StatefulWidget)。
在 Flutter 中,有三種主要方法來管理狀態
- 元件管理自己的狀態
- 父元件管理元件的狀態
- 混合搭配方法
在決定使用哪種方法時,請考慮以下原則
- 如果相關狀態是使用者資料,例如複選框的選中或未選中模式,或者滑塊的位置,那麼最好由父元件管理該狀態。
- 如果相關狀態是美觀的,例如動畫,那麼元件本身最好管理該狀態。
- 如有疑問,讓父元件管理子元件的狀態。
- 子類化 StatefulWidget 和 State。
MyStatefulWidget 類管理自己的狀態——它擴充套件了 StatefulWidget,它覆蓋了 createState() 方法以建立 State 物件,並且框架呼叫 createState() 來構建小部件。在本示例中,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 新增到小部件樹中。
將自定義 StatefulWidget 新增到應用程式的 build 方法中的小部件樹中。
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'),
);
}
}
Props
#在 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_preferences
import '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 中,新螢幕是新的 ViewControllers。在 Flutter 中,螢幕只是 Widgets!要導航到 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 中指定,或者可以在執行時構建,例如,在 hero 動畫中。以下示例在 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,該 widget 會自動顯示適當的 IconButton 以顯示 Drawer(如果 Scaffold 中有 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 中的勢態系統具有兩個獨立的層。第一層包括原始指標事件,這些事件描述了指標(例如觸控、滑鼠和筆)在螢幕上的位置和移動。第二層包括勢態,這些勢態描述了語義操作,並由一個或多個指標移動組成。
如何向元件新增點選或按下監聽器?
#在 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。或者,透過將 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 http
Flutter 使用 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。
如何使用文字欄位元件?
#在 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 顯示警報訊息來實現的,並且文字欄位中的文字透過 TextEditingController 的 text 屬性訪問。
如何使用表單元件?
#在 Flutter 中,使用 Form widget,其中 TextFormField widget 和提交按鈕作為子項傳遞。TextFormField widget 具有一個名為 onSaved 的引數,該引數接受一個回撥並在儲存表單時執行。使用 FormState 物件來儲存、重置或驗證此 Form 的每個 FormField 後代。要獲取 FormState,可以使用具有 Form 的祖先的上下文的 Form.of(),或者將 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 的 Stateful Hot Reload 功能可幫助您快速輕鬆地進行實驗、構建 UI、新增功能和修復錯誤。每次更改時,您不必重新編譯應用程式,而是可以立即熱過載應用程式。應用程式會更新以反映您的更改,並且應用程式的當前狀態將被保留。
首先,從您喜歡的 IDE,啟用 autosave 和儲存時熱過載。
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 Simulator 的 ⌘R 和 Android 模擬器上雙擊 R。
在 Flutter 中,如果您使用的是 IntelliJ IDE 或 Android Studio,您可以選擇 Save All (⌘s/ctrl-s),或者您可以點選工具欄上的 Hot Reload 按鈕。如果您使用 flutter run 在命令列中執行該應用程式,請在 Terminal 視窗中鍵入 r。您也可以透過在 Terminal 視窗中鍵入 R 來執行完全重啟。
如何訪問應用內開發者選單?
#在 React Native 中,可以透過搖動裝置來訪問開發人員選單:iOS Simulator 的 ⌘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,這些 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 中建立相同的動畫,建立一個 AnimationController 物件,命名為 controller 並指定持續時間。預設情況下,AnimationController 線性生成在給定持續時間內從 0.0 到 1.0 的值。動畫控制器會在您的應用程式正在執行的裝置準備好顯示新幀時生成一個新值。通常,此速率約為每秒 60 個值。
定義 AnimationController 時,必須傳入一個 vsync 物件。vsync 的存在可以防止螢幕外動畫消耗不必要的資源。您可以透過將 TickerProviderStateMixin 新增到類定義中,將您的 stateful 物件用作 vsync。AnimationController 需要一個 TickerProvider,該 TickerProvider 使用建構函式中的 vsync 引數進行配置。
一個 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 元件等效元件
#下表列出了常用的 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 ] 如果不為 null,則強制子項在滾動方向上具有給定的範圍。 | |
| 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 [必需] | [ 回撥函式 ] 在使用者切換開關的開啟或關閉狀態時呼叫。 |