👨🏻‍💻's 博客

慢品人间烟火色,闲观万事岁月长

0%

Flutter - UIKit开发相关指南 - 控制器,主题,表单

环境

Flutter 3.29
macOS Sequoia 15.4.1
Xcode 16.3

控制器(ViewControllers)

在UIKit中,通过ViewController控制数据在视图上展现,多个ViewController组合在一起构建复杂的用户界面。在Flutter中,因为所有都是Widget,所以ViewController相关的功能也由Widget来承担。

生命周期事件

在UIKit中可以重写自定义控制器的生命周期的方法,或注册AppDelegate的回调。在Flutter3.13前,没有这个概念,但是可以通过监听WidgetsBinding观察者和didChangeAppLifecycleState()改变事件来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import 'package:flutter/material.dart';

void main() {
runApp(const MainApp());
}

class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: Center(child: BindingObserver())),
);
}
}

class BindingObserver extends StatefulWidget {
const BindingObserver({super.key});

@override
State<BindingObserver> createState() => _BindingObserverState();
}

class _BindingObserverState extends State<BindingObserver>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
// 1.添加App事件变化的观察者
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
// 2.监听app生命周期变化的事件
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.detached:
_onDetached();
/// On all platforms, this state indicates that the application is in the default running mode for a running application that has input focus and is visible.
/// 应用可见且能响应用户的输入,切回前台会触发
case AppLifecycleState.resumed:
_onResumed();
/// At least one view of the application is visible, but none have input focus. The application is otherwise running normally.
/// 应用程序处于非活跃状态,并且未接收用户输入。此事件仅适用于 iOS,因为 Android 上没有对应的事件。
/// 切到后台先触发这个方法
case AppLifecycleState.inactive:
_onInactive();
/// All views of an application are hidden, either because the application is about to be paused (on iOS and Android), or because it has been minimized or placed on a desktop that is no longer visible (on non-web desktop), or is running in a window or tab that is no longer visible (on the web).
/// 所有的应用视图被隐藏,或者应用被暂停
case AppLifecycleState.hidden:
_onHidden();
/// The application is not currently visible to the user, and not responding to user input.
/// When the application is in this state, the engine will not call the [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame] callbacks.
/// This state is only entered on iOS and Android.
/// 应用当前不可见,不响应用户的输入,但依然在后台运行,引擎不会回调PlatformDispatcher.onBeginFrame 和 PlatformDispatcher.onDrawFrame
case AppLifecycleState.paused:
_onPaused();
}
}

void _onDetached() => print('detached');
void _onResumed() => print('resumed');
void _onInactive() => print('inactive');
void _onHidden() => print('hidden');
void _onPaused() => print('paused');

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("监听事件")),
body: Center(child: Text("生命周期")),
);
}
}

2025-05-11 16.53.51.png

Flutter 3.13后通过设置AppLifecycleListener 来实现响应生命周期变更的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import 'package:flutter/material.dart';

void main() {
runApp(const MainApp());
}

class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: Center(child: BindingObserver())),
);
}
}

class BindingObserver extends StatefulWidget {
const BindingObserver({super.key});

@override
State<BindingObserver> createState() => _BindingObserverState();
}

class _BindingObserverState extends State<BindingObserver> {
/// 1. 定义观察者属性
late final AppLifecycleListener _listener;

@override
void initState() {
super.initState();
/// 2. 添加App事件变化的观察者
_listener = AppLifecycleListener(onStateChange: _onStateChanged);
}

/// 3. 回调方法
void _onStateChanged(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.detached:
_onDetached();
case AppLifecycleState.resumed:
_onResumed();
case AppLifecycleState.inactive:
_onInactive();
case AppLifecycleState.hidden:
_onHidden();
case AppLifecycleState.paused:
_onPaused();
}
}

void _onDetached() => print('detached');
void _onResumed() => print('resumed');
void _onInactive() => print('inactive');
void _onHidden() => print('hidden');
void _onPaused() => print('paused');

@override
void dispose() {
// Do not forget to dispose the listener
_listener.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("监听事件")),
body: Center(child: Text("生命周期")),
);
}
}

主题,样式和媒体

使用一个主题

Flutter 设置了一些主题,可以实现统一更改文本和 UI 组件的样式等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import 'package:flutter/material.dart';

void main() {
runApp(const ThemePage());
}

class ThemePage extends StatefulWidget {
const ThemePage({super.key});

@override
State<ThemePage> createState() => _ThemePageState();
}

class _ThemePageState extends State<ThemePage> {
Brightness brightness = Brightness.light;

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Theme 主题修改",
// 1. 创建主题
theme: ThemeData(brightness: brightness, primarySwatch: Colors.blue),

home: Scaffold(
appBar: AppBar(title: Text("Theme 主题修改")),
body: Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
brightness = Brightness.light;
});
},
child: Text("切换到日间主题"),
),
ElevatedButton(
// 2. 点击按钮触发
onPressed: () {
// 3. 通知Flutter
setState(() {
// 4. 设置亮度,更新主题
brightness = Brightness.dark;
});
},
child: Text("切换到夜间主题"),
),
],
),
),
);
}
}

202505121010.gif

使用自定义字体

Flutter要使用自定义的字体可以在pubspec.yaml文件中添加

1
2
3
4
fonts:
- family: google_kavivanar
fonts:
- asset: static/font/google_kavivanar.ttf
1
2
3
4
Text(
"Theme 123",
style: TextStyle(fontFamily: 'google_kavivanar'),
),

字体样式

Text widget有TextStyle对象,可以通过这个属性来设置样式

  • color – 字体颜色
  • decoration – 修饰
  • decorationColor – 修饰颜色
  • decorationStyle – 装饰的样式
  • fontFamily – 字体
  • fontSize – 字体大小
  • fontStyle – 字体风格(斜体,正常)
  • fontWeight – 用于绘制文本的字形的厚度
  • wordSpacing – 词间距
  • letterSpacing – 字间距
  • height – 文本区域的高度

应用资源包

在Flutter中使用assets表示资源

pubspec.yaml声明资源

1
2
assets:
- static/data.json

iOS中图片的资源是1.0x,2.0x,3.0x格式的,在Flutter中比如图片是在static/images下的,那不同倍数图片的存放方式。

1
2
3
static/images/my_icon.png       
static/images/2.0x/my_icon.png // 2.0x image
static/images/3.0x/my_icon.png // 3.0x image

pubspec.yaml声明图片资源

1
2
assets:
- static/images/my_icon.png

然后通过AssetImage或Image.asset来访问

1
2
3
4
5
6
image: AssetImage('static/images/my_image.png'),

@override
Widget build(BuildContext context) {
return Image.asset('static/images/my_image.png');
}

表单输入

在UIKit中,通常是在提交时查询对应的输入框的当前值,因为Flutter的Widgets是不可变的,如何对用户的输入操作进行处理,

获取用户输入

针对TextFileTextFormField,可以通过提供一个TextEditingController来获取用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(title: "TextFile", home: MyForm()));
}

class MyForm extends StatefulWidget {
const MyForm({super.key});

@override
State<MyForm> createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
// 1. 创建text控制器来获取textFiled的值
final myController = TextEditingController();

@override
void dispose() {
// 4. 清理生成的myController
myController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Retrieve Text Input')),
body: Padding(
padding: const EdgeInsets.all(16),
// 2.TextField绑定控制器
child: TextField(controller: myController),
),
floatingActionButton: FloatingActionButton(
/// 3. 当用户点击按钮时,弹出dialog,显示textField的值
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(content: Text(myController.text));
},
);
},
tooltip: 'Show me the value!',
child: const Icon(Icons.text_fields),
),
);
}
}

202505121435.gif

设置TextField的占位信息

1
2
3
Center(
child: TextField(decoration: InputDecoration(hintText: 'textField占位信息')),
)

显示校验错误

在TextField的onSubmitted方法中,判断textField的输入是否合法,若不合法,可以在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
child: TextField(
controller: myController,
// 1. 拦截事件
onSubmitted:
(text) => {
// 2. 通知Flutter状态变更
setState(() {
// 3. 判断textField的内容是否合法,不合法则在build方法刷新时显示
if (!isEmail(text)) {
_errorText = '错误: 邮箱地址不合法';
} else {
_errorText = null;
}
}),
},
decoration: InputDecoration(
hintText: "邮箱",
errorText: _errorText,
),
),


202505121502.gif

参考

  1. 给 UIKit 开发者的 Flutter 指南
  2. 【Flutter】Flutter 应用主题 ( ThemeData | 动态修改主题 )