給 SwiftUI 開發者的 Flutter
希望使用 Flutter 編寫行動應用程式的 SwiftUI 開發人員,應查看本指南。它說明如何將現有的 SwiftUI 知識應用於 Flutter。
Flutter 是一個使用 Dart 程式語言建立跨平台應用程式的框架。若要了解使用 Dart 程式設計與使用 Swift 程式設計之間的一些差異,請參閱身為 Swift 開發人員的 Dart 學習以及給 Swift 開發者的 Flutter 並行處理。
當您使用 Flutter 建立應用程式時,您的 SwiftUI 知識和經驗非常寶貴。
Flutter 在 iOS 和 macOS 上執行時,也會對應用程式的行為進行一些調整。若要了解如何調整,請參閱平台調整。
您可以跳過閱讀本文件,直接查找與您需求最相關的問題,將其作為一本食譜來使用。本指南嵌入了程式碼範例。透過使用滑鼠懸停或聚焦時出現的「在 DartPad 中開啟」按鈕,您可以在 DartPad 上開啟並執行一些範例。
概觀
#作為介紹,請觀看以下影片。它概述了 Flutter 如何在 iOS 上運作,以及如何使用 Flutter 建立 iOS 應用程式。
Flutter 和 SwiftUI 程式碼描述了 UI 的外觀和運作方式。開發人員將這種程式碼類型稱為宣告式框架。
檢視與 Widget
#SwiftUI 將 UI 元件表示為視圖。您可以使用修飾符來設定視圖。
Text("Hello, World!") // <-- This is a View
.padding(10) // <-- This is a modifier of that View
Flutter 將 UI 元件表示為小工具。
視圖和小工具都只存在到需要變更為止。這些語言將此屬性稱為不可變性。SwiftUI 將 UI 元件屬性表示為視圖修飾符。相比之下,Flutter 使用小工具來表示 UI 元件及其屬性。
Padding( // <-- This is a Widget
padding: EdgeInsets.all(10.0), // <-- So is this
child: Text("Hello, World!"), // <-- This, too
)));
若要組合版面配置,SwiftUI 和 Flutter 都會將 UI 元件互相嵌套。SwiftUI 嵌套視圖,而 Flutter 嵌套小工具。
版面配置程序
#SwiftUI 使用以下流程來佈局視圖
- 父視圖向其子視圖建議一個大小。
- 所有後續的子視圖
- 向其子視圖建議一個大小
- 詢問該子視圖想要的大小
- 每個父視圖都以傳回的大小呈現其子視圖。
Flutter 的流程略有不同
父小工具將約束條件傳遞給其子小工具。約束條件包括高度和寬度的最小值和最大值。
子小工具會嘗試決定其大小。它會對其自己的子小工具列表重複相同的流程
- 它會將子小工具的約束條件告知子小工具。
- 它會詢問子小工具希望的大小。
父小工具會佈局子小工具。
- 如果請求的大小符合約束條件,則父小工具會使用該大小。
- 如果請求的大小不符合約束條件,則父小工具會限制高度、寬度或兩者以符合其約束條件。
Flutter 與 SwiftUI 的不同之處在於,父元件可以覆寫子元件的所需大小。小工具不能有任何它想要的大小。它也無法知道或決定它在螢幕上的位置,因為它的父元件會做出該決定。
若要強制子小工具以特定大小呈現,父小工具必須設定嚴格的約束條件。當約束條件的最小值等於其最大值時,約束條件會變得嚴格。
在 SwiftUI 中,視圖可能會擴展到可用的空間,或將其大小限制為其內容的大小。Flutter 小工具的行為類似。
但是,在 Flutter 中,父小工具可以提供無邊界的約束條件。無邊界的約束條件會將其最大值設定為無限大。
UnboundedBox(
child: Container(
width: double.infinity, height: double.infinity, color: red),
)
如果子元件擴展且具有無邊界的約束條件,Flutter 會傳回溢位警告
UnconstrainedBox(
child: Container(color: red, width: 4000, height: 50),
)
若要了解約束條件如何在 Flutter 中運作,請參閱了解約束條件。
設計系統
#由於 Flutter 的目標是多個平台,因此您的應用程式不需要符合任何設計系統。儘管本指南採用Material 小工具,但您的 Flutter 應用程式可以使用許多不同的設計系統
- 自訂 Material 小工具
- 社群建立的小工具
- 您自己的自訂小工具
- Cupertino 小工具,遵循 Apple 的人機介面指南
適用於 iOS 開發人員的 Flutter Cupertino 程式庫
如果您正在尋找一個以自訂設計系統為特色的絕佳參考應用程式,請查看Wonderous。
UI 基礎
#本節涵蓋 Flutter 中 UI 開發的基礎知識,以及它與 SwiftUI 的比較。這包括如何開始開發應用程式、顯示靜態文字、建立按鈕、回應按下事件、顯示清單、網格等等。
開始使用
#在 SwiftUI 中,您可以使用 App
來啟動應用程式。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
HomePage()
}
}
}
另一個常見的 SwiftUI 實務是將應用程式主體放置在符合 View
協定的 struct
內,如下所示
struct HomePage: View {
var body: some View {
Text("Hello, World!")
}
}
若要啟動您的 Flutter 應用程式,請將應用程式的執行個體傳遞至 runApp
函式。
void main() {
runApp(const MyApp());
}
App
是一個小工具。build 方法描述了它所代表的使用者介面部分。通常以WidgetApp
類別(例如 CupertinoApp
)來開始您的應用程式。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Returns a CupertinoApp that, by default,
// has the look and feel of an iOS app.
return const CupertinoApp(
home: HomePage(),
);
}
}
HomePage
中使用的小工具可能會以 Scaffold
類別開始。Scaffold
會為應用程式實作基本的版面配置結構。
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'Hello, World!',
),
),
);
}
}
請注意 Flutter 如何使用 Center
小工具。SwiftUI 預設會在中心呈現視圖的內容。Flutter 並非總是如此。Scaffold
不會將其 body
小工具呈現於螢幕的中心。若要將文字置中,請將其包裝在 Center
小工具中。若要了解不同的視圖及其預設行為,請查看小工具目錄。
新增按鈕
#在 SwiftUI 中,您可以使用 Button
結構來建立按鈕。
Button("Do something") {
// this closure gets called when your
// button is tapped
}
若要在 Flutter 中達到相同的結果,請使用 CupertinoButton
類別
CupertinoButton(
onPressed: () {
// This closure is called when your button is tapped.
},
const Text('Do something'),
),
Flutter 可讓您存取各種具有預先定義樣式的按鈕。CupertinoButton
類別來自 Cupertino 程式庫。Cupertino 程式庫中的小工具使用 Apple 的設計系統。
水平對齊元件
#在 SwiftUI 中,堆疊視圖在設計您的版面配置中扮演著重要角色。兩個單獨的結構可讓您建立堆疊
適用於水平堆疊視圖的
HStack
適用於垂直堆疊視圖的
VStack
以下 SwiftUI 視圖將地球影像和文字新增至水平堆疊視圖
HStack {
Image(systemName: "globe")
Text("Hello, world!")
}
Flutter 使用 Row
而不是 HStack
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(CupertinoIcons.globe),
Text('Hello, world!'),
],
),
Row
小工具需要 children
參數中的 List<Widget>
。mainAxisAlignment
屬性會告知 Flutter 如何使用額外的空間來定位子小工具。MainAxisAlignment.center
會將子小工具定位在主軸的中心。對於 Row
而言,主軸是水平軸。
垂直對齊元件
#以下範例以上一節中的範例為基礎。
在 SwiftUI 中,您可以使用 VStack
將元件排列成垂直柱狀。
VStack {
Image(systemName: "globe")
Text("Hello, world!")
}
Flutter 使用與上一個範例相同的 Dart 程式碼,只是將 Column
交換為 Row
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(CupertinoIcons.globe),
Text('Hello, world!'),
],
),
顯示清單檢視
#在 SwiftUI 中,您可以使用 List
基本元件來顯示項目序列。若要顯示模型物件的序列,請確保使用者可以識別您的模型物件。若要使物件可識別,請使用 Identifiable
協定。
struct Person: Identifiable {
var name: String
}
var persons = [
Person(name: "Person 1"),
Person(name: "Person 2"),
Person(name: "Person 3"),
]
struct ListWithPersons: View {
let persons: [Person]
var body: some View {
List {
ForEach(persons) { person in
Text(person.name)
}
}
}
}
這類似於 Flutter 偏好建立其清單小工具的方式。Flutter 不需要讓清單項目可識別。您可以設定要顯示的項目數,然後為每個項目建立小工具。
class Person {
String name;
Person(this.name);
}
final List<Person> items = [
Person('Person 1'),
Person('Person 2'),
Person('Person 3'),
];
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].name),
);
},
),
);
}
}
Flutter 對於清單有一些注意事項
ListView
小工具具有 builder 方法。這與 SwiftUI 的List
結構中的ForEach
類似。ListView
的itemCount
參數設定ListView
顯示多少個項目。itemBuilder
具有一個索引參數,該參數介於零和 itemCount 減一之間。
上一個範例為每個項目傳回了 ListTile
小工具。ListTile
小工具包含 height
和 font-size
之類的屬性。這些屬性有助於建立清單。但是,Flutter 允許您傳回幾乎任何代表您的資料的小工具。
顯示網格
#在 SwiftUI 中建構非條件式網格時,您會使用具有 GridRow
的 Grid
。
Grid {
GridRow {
Text("Row 1")
Image(systemName: "square.and.arrow.down")
Image(systemName: "square.and.arrow.up")
}
GridRow {
Text("Row 2")
Image(systemName: "square.and.arrow.down")
Image(systemName: "square.and.arrow.up")
}
}
若要在 Flutter 中顯示網格,請使用 GridView
小工具。此小工具具有各種建構函式。每個建構函式都有類似的目標,但會使用不同的輸入參數。以下範例使用 .builder()
初始化器
const widgets = [
Text('Row 1'),
Icon(CupertinoIcons.arrow_down_square),
Icon(CupertinoIcons.arrow_up_square),
Text('Row 2'),
Icon(CupertinoIcons.arrow_down_square),
Icon(CupertinoIcons.arrow_up_square),
];
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisExtent: 40,
),
itemCount: widgets.length,
itemBuilder: (context, index) => widgets[index],
),
);
}
}
SliverGridDelegateWithFixedCrossAxisCount
委派會決定網格用來佈局其元件的各種參數。這包括 crossAxisCount
,它會決定每一行顯示的項目數。
SwiftUI 的 Grid
和 Flutter 的 GridView
的不同之處在於,Grid
需要 GridRow
。GridView
使用委派來決定網格應該如何佈局其元件。
建立捲動檢視
#在 SwiftUI 中,您可以使用 ScrollView
來建立自訂捲動元件。以下範例以可捲動方式顯示一系列 PersonView
執行個體。
ScrollView {
VStack(alignment: .leading) {
ForEach(persons) { person in
PersonView(person: person)
}
}
}
若要建立捲動視圖,Flutter 會使用 SingleChildScrollView
。在以下範例中,函式 mockPerson
會模擬 Person
類別的執行個體,以建立自訂的 PersonView
小工具。
SingleChildScrollView(
child: Column(
children: mockPersons
.map(
(person) => PersonView(
person: person,
),
)
.toList(),
),
),
彈性與回應式設計
#在 SwiftUI 中,您可以使用 GeometryReader
來建立相對的視圖大小。
例如,您可以
- 將
geometry.size.width
乘以某個係數來設定寬度。 - 使用
GeometryReader
作為中斷點來變更應用程式的設計。
您也可以使用 horizontalSizeClass
來查看大小類別是否具有 .regular
或 .compact
。
若要在 Flutter 中建立相對視圖,您可以使用下列兩種選項之一
- 取得
LayoutBuilder
類別中的BoxConstraints
物件。 - 在您的 build 函式中使用
MediaQuery.of()
來取得目前應用程式的大小和方向。
若要深入了解,請查看建立回應式和適應性應用程式。
管理狀態
#在 SwiftUI 中,您可以使用 @State
屬性包裝器來表示 SwiftUI 視圖的內部狀態。
struct ContentView: View {
@State private var counter = 0;
var body: some View {
VStack{
Button("+") { counter+=1 }
Text(String(counter))
}
}}
SwiftUI 也包含一些用於更複雜的狀態管理選項,例如 ObservableObject
協定。
Flutter 使用 StatefulWidget
來管理本機狀態。使用下列兩個類別來實作狀態小工具
StatefulWidget
的子類別State
的子類別
State
物件儲存了 widget 的狀態。若要變更 widget 的狀態,請從 State
子類別呼叫 setState()
,以告知框架重新繪製 widget。
以下範例展示了一個計數器應用程式的一部分
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_counter'),
TextButton(
onPressed: () => setState(() {
_counter++;
}),
child: const Text('+'),
),
],
),
),
);
}
}
若要了解更多管理狀態的方式,請查看狀態管理。
動畫
#主要有兩種 UI 動畫類型。
- 隱式動畫,從目前值動畫到新的目標值。
- 顯式動畫,在被要求時進行動畫。
隱式動畫
#SwiftUI 和 Flutter 採用類似的動畫方法。在這兩個框架中,您都可以指定像 duration
和 curve
這樣的參數。
在 SwiftUI 中,您可以使用 animate()
修飾符來處理隱式動畫。
Button("Tap me!"){
angle += 45
}
.rotationEffect(.degrees(angle))
.animation(.easeIn(duration: 1))
Flutter 包含用於隱式動畫的 widget。這簡化了常見 widget 的動畫。Flutter 將這些 widget 命名為以下格式:AnimatedFoo
。
例如:若要旋轉一個按鈕,請使用 AnimatedRotation
類別。這會將 Transform.rotate
widget 動畫化。
AnimatedRotation(
duration: const Duration(seconds: 1),
turns: turns,
curve: Curves.easeIn,
TextButton(
onPressed: () {
setState(() {
turns += .125;
});
},
const Text('Tap me!')),
),
Flutter 允許您建立自訂的隱式動畫。若要組合一個新的動畫 widget,請使用 TweenAnimationBuilder
。
顯式動畫
#對於顯式動畫,SwiftUI 使用 withAnimation()
函數。
Flutter 包含名稱格式類似於 FooTransition
的顯式動畫 widget。其中一個範例是 RotationTransition
類別。
Flutter 也允許您使用 AnimatedWidget
或 AnimatedBuilder
建立自訂的顯式動畫。
若要了解更多關於 Flutter 中動畫的資訊,請參閱動畫概觀。
在螢幕上繪圖
#在 SwiftUI 中,您使用 CoreGraphics
在螢幕上繪製線條和形狀。
Flutter 有一個基於 Canvas
類別的 API,並有兩個類別可以協助您繪製
CustomPaint
需要一個繪圖器dartCustomPaint( painter: SignaturePainter(_points), size: Size.infinite, ),
CustomPainter
實現您在畫布上繪圖的演算法。dartclass SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List<Offset?> points; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) { canvas.drawLine(points[i]!, points[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; }
導覽
#本節說明如何在應用程式的頁面之間導航、推入和彈出機制等等。
在頁面之間導覽
#開發人員使用稱為導航路由的不同頁面來建構 iOS 和 macOS 應用程式。
在 SwiftUI 中,NavigationStack
代表此頁面堆疊。
以下範例建立一個顯示人員清單的應用程式。若要在新的導航連結中顯示人員的詳細資訊,請點擊該人員。
NavigationStack(path: $path) {
List {
ForEach(persons) { person in
NavigationLink(
person.name,
value: person
)
}
}
.navigationDestination(for: Person.self) { person in
PersonView(person: person)
}
}
如果您有一個沒有複雜連結的Flutter小型應用程式,請使用具有命名路由的Navigator
。在定義導航路由後,使用其名稱呼叫您的導航路由。
在傳遞給
runApp()
函數的類別中,為每個路由命名。以下範例使用App
dart// Defines the route name as a constant // so that it's reusable. const detailsPageRouteName = '/details'; class App extends StatelessWidget { const App({ super.key, }); @override Widget build(BuildContext context) { return CupertinoApp( home: const HomePage(), // The [routes] property defines the available named routes // and the widgets to build when navigating to those routes. routes: { detailsPageRouteName: (context) => const DetailsPage(), }, ); } }
以下範例使用
mockPersons()
產生人員清單。點擊人員會使用pushNamed()
將人員的詳細資訊頁面推送到Navigator
。dartListView.builder( itemCount: mockPersons.length, itemBuilder: (context, index) { final person = mockPersons.elementAt(index); final age = '${person.age} years old'; return ListTile( title: Text(person.name), subtitle: Text(age), trailing: const Icon( Icons.arrow_forward_ios, ), onTap: () { // When a [ListTile] that represents a person is // tapped, push the detailsPageRouteName route // to the Navigator and pass the person's instance // to the route. Navigator.of(context).pushNamed( detailsPageRouteName, arguments: person, ); }, ); }, ),
定義
DetailsPage
widget,以顯示每個人員的詳細資訊。在 Flutter 中,您可以在導航到新路由時將參數傳遞到 widget 中。使用ModalRoute.of()
提取參數dartclass DetailsPage extends StatelessWidget { const DetailsPage({super.key}); @override Widget build(BuildContext context) { // Read the person instance from the arguments. final Person person = ModalRoute.of( context, )?.settings.arguments as Person; // Extract the age. final age = '${person.age} years old'; return Scaffold( // Display name and age. body: Column(children: [Text(person.name), Text(age)]), ); } }
若要建立更進階的導航和路由需求,請使用像 go_router 這樣的路由套件。
若要了解更多資訊,請查看導航和路由。
手動彈回
#在 SwiftUI 中,您可以使用 dismiss
環境值彈回上一頁。
Button("Pop back") {
dismiss()
}
在 Flutter 中,使用 Navigator
類別的 pop()
函數
TextButton(
onPressed: () {
// This code allows the
// view to pop back to its presenter.
Navigator.of(context).pop();
},
child: const Text('Pop back'),
),
導覽至另一個應用程式
#在 SwiftUI 中,您可以使用 openURL
環境變數開啟另一個應用程式的 URL。
@Environment(\.openURL) private var openUrl
// View code goes here
Button("Open website") {
openUrl(
URL(
string: "https://google.com"
)!
)
}
在 Flutter 中,使用 url_launcher
外掛程式。
CupertinoButton(
onPressed: () async {
await launchUrl(
Uri.parse('https://google.com'),
);
},
const Text(
'Open website',
),
),
主題、樣式和媒體
#您可以輕鬆地為 Flutter 應用程式設定樣式。樣式設定包括在淺色和深色主題之間切換、變更文字和 UI 元件的設計等等。本節涵蓋如何為應用程式設定樣式。
使用深色模式
#在 SwiftUI 中,您可以在 View
上呼叫 preferredColorScheme()
函數以使用深色模式。
在 Flutter 中,您可以在應用程式層級控制淺色和深色模式。若要控制亮度模式,請使用 App
類別的 theme
屬性
const CupertinoApp(
theme: CupertinoThemeData(
brightness: Brightness.dark,
),
home: HomePage(),
);
設定文字樣式
#在 SwiftUI 中,您可以使用修飾符函數來設定文字樣式。例如,若要變更 Text
字串的字型,請使用 font()
修飾符
Text("Hello, world!")
.font(.system(size: 30, weight: .heavy))
.foregroundColor(.yellow)
若要在 Flutter 中設定文字樣式,請將 TextStyle
widget 作為 Text
widget 的 style
參數的值。
Text(
'Hello, world!',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: CupertinoColors.systemYellow,
),
),
設定按鈕樣式
#在 SwiftUI 中,您可以使用修飾符函數來設定按鈕樣式。
Button("Do something") {
// do something when button is tapped
}
.font(.system(size: 30, weight: .bold))
.background(Color.yellow)
.foregroundColor(Color.blue)
}
若要在 Flutter 中設定按鈕 widget 的樣式,請設定其子項的樣式,或修改按鈕本身的屬性。
在以下範例中
CupertinoButton
的color
屬性設定其color
。- 子項
Text
widget 的color
屬性設定按鈕文字顏色。
child: CupertinoButton(
color: CupertinoColors.systemYellow,
onPressed: () {},
padding: const EdgeInsets.all(16),
child: const Text(
'Do something',
style: TextStyle(
color: CupertinoColors.systemBlue,
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
使用自訂字型
#在 SwiftUI 中,您可以透過兩個步驟在應用程式中使用自訂字型。首先,將字型檔案新增至您的 SwiftUI 專案。新增檔案後,使用 .font()
修飾符將其套用至您的 UI 元件。
Text("Hello")
.font(
Font.custom(
"BungeeSpice-Regular",
size: 40
)
)
在 Flutter 中,您可以使用名為 pubspec.yaml
的檔案來控制您的資源。此檔案與平台無關。若要將自訂字型新增至您的專案,請依照下列步驟操作
在專案的根目錄中建立一個名為
fonts
的資料夾。這個選擇性步驟有助於整理您的字型。將您的
.ttf
、.otf
或.ttc
字型檔案新增至fonts
資料夾。開啟專案中的
pubspec.yaml
檔案。找到
flutter
區段。在
fonts
區段下新增您的自訂字型。yamlflutter: fonts: - family: BungeeSpice fonts: - asset: fonts/BungeeSpice-Regular.ttf
在您將字型新增至專案後,您可以像以下範例一樣使用它
Text(
'Cupertino',
style: TextStyle(
fontSize: 40,
fontFamily: 'BungeeSpice',
),
),
在應用程式中捆綁圖片
#在 SwiftUI 中,您首先將影像檔案新增至 Assets.xcassets
,然後使用 Image
視圖顯示影像。
若要在 Flutter 中新增影像,請依照與新增自訂字型類似的方法。
將
images
資料夾新增至根目錄。將此資源新增至
pubspec.yaml
檔案。yamlflutter: assets: - images/Blueberries.jpg
新增影像後,使用 Image
widget 的 .asset()
建構函式來顯示它。此建構函式
- 使用提供的路徑來實例化給定的影像。
- 從與您的應用程式捆綁在一起的資源讀取影像。
- 在螢幕上顯示影像。
若要檢閱完整的範例,請查看 Image
文件。
在應用程式中綁定影片
#在 SwiftUI 中,您可以透過兩個步驟將本機影片檔案與您的應用程式捆綁在一起。首先,您匯入 AVKit
框架,然後實例化 VideoPlayer
視圖。
在 Flutter 中,將 video_player 外掛程式新增至您的專案。此外掛程式可讓您建立在 Android、iOS 和 Web 上使用相同程式碼庫的影片播放器。
- 將外掛程式新增至您的應用程式,並將影片檔案新增至您的專案。
- 將資源新增至您的
pubspec.yaml
檔案。 - 使用
VideoPlayerController
類別來載入和播放您的影片檔案。
若要檢閱完整的逐步解說,請查看 video_player 範例。
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面上次更新於 2024-10-17。 檢視原始碼 或 回報問題。