Flutter 实战:flashlight_app 屏幕手电筒的全屏灯光、颜色亮度与鸿蒙适配解析
Flutter 实战:flashlight_app 屏幕手电筒的全屏灯光、颜色亮度与鸿蒙适配解析
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
flashlight_app 是一个基于 Flutter 实现的屏幕手电筒模拟应用。它没有调用设备相机闪光灯,也没有接入原生 torch API,而是通过屏幕背景色、透明度、沉浸式系统 UI、颜色选择、亮度滑块和频闪参数界面,模拟“手电筒”使用体验。
本文基于项目真实源码展开,重点分析 开关状态设计、屏幕灯光渲染、颜色映射、亮度透明度计算、沉浸式系统 UI 切换、频闪模式界面 和 鸿蒙适配关注点。文章内容可直接发布到 CSDN,不包含面向作者的检查说明。
这个项目最值得关注的点,不是“如何控制硬件闪光灯”,而是如何用 Flutter 标准 UI 做出一个可交互、可配置、可跨端运行的屏幕灯光工具。明确边界,才是技术文章可信的第一步。

图示说明:本文围绕 Flutter 屏幕手电筒的开关状态、全屏渲染、颜色亮度配置和跨端适配展开,适合用于鸿蒙、Android、iOS 等多端小工具应用开发复盘。
一、项目定位与功能概览
1.1 应用主题
flashlight_app 的定位是一个 屏幕手电筒模拟工具。用户点击屏幕可以开启或关闭灯光;开启后可以选择灯光颜色、调节亮度;也可以通过 AppBar 的图标切换频闪模式,并选择频率显示。
当前项目主要功能如下:
| 功能 | 页面表现 | 源码实现 |
|---|---|---|
| 开关灯 | 点击屏幕切换亮/暗 | _toggleLight() |
| 全屏灯光 | 开启后背景变为选中颜色 | AnimatedContainer |
| 颜色选择 | 白、红、绿、蓝、黄 | _colors、_getLightColor() |
| 亮度调节 | Slider 百分比 | _brightness |
| 频闪模式 | AppBar 闪电图标切换 | _strobeMode |
| 频率选择 | 1/2/5/10/20 Hz | _frequencies |
| 沉浸式显示 | 开灯后隐藏系统 UI | SystemChrome.setEnabledSystemUIMode |
1.2 源码边界
这篇文章必须先说清楚边界:当前源码是屏幕灯光模拟,不是硬件手电筒控制。
| 能力 | 当前是否实现 | 说明 |
|---|---|---|
| 屏幕变亮 | 是 | 通过背景色和透明度模拟 |
| 颜色切换 | 是 | 通过 Flutter Color 切换 |
| 亮度调节 | 是 | 通过 Alpha 透明度模拟 |
| 沉浸式 UI | 是 | 使用 SystemChrome |
| 硬件闪光灯 | 否 | 没有相机或 torch 插件 |
| 真实频闪计时 | 否 | 当前只是频闪模式和频率选择界面 |
1.3 适合学习的点
这个项目适合学习以下 Flutter 实战能力:
- 如何用布尔状态切换两个完全不同的页面分支。
- 如何用
AnimatedContainer做平滑背景色变化。 - 如何通过 Alpha 值模拟屏幕亮度。
- 如何使用
SystemChrome控制系统 UI 模式。 - 如何用
Wrap做颜色圆点和频率按钮。 - 如何面向鸿蒙检查沉浸式、触摸和屏幕适配。
二、工程结构与运行方式
2.1 工程结构
项目保持标准 Flutter 工程结构,核心代码集中在 lib/main.dart:
| 文件或目录 | 作用 | 说明 |
|---|---|---|
lib/main.dart |
应用入口与页面实现 | 包含开关、颜色、亮度和频闪 UI |
pubspec.yaml |
依赖声明 | 使用 Flutter SDK 与 Material 图标 |
test/widget_test.dart |
Widget 测试入口 | 可扩展为开关和配置测试 |
ohos/ |
鸿蒙平台工程目录 | 用于跨端构建和适配 |
2.2 依赖声明
项目没有引入复杂第三方依赖:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
这意味着当前功能完全依赖 Flutter 标准 UI 与 Dart 状态管理,不需要相机权限、闪光灯权限或平台通道。
2.3 常用命令
开发和验证时可以使用以下命令:
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 作用 | 使用场景 |
|---|---|---|
flutter pub get |
获取依赖 | 首次运行或依赖变化 |
flutter analyze |
静态分析 | 检查语法和 lint |
flutter test |
执行测试 | 验证 Widget 行为 |
flutter run |
启动应用 | 本地调试界面 |
三、应用入口与主题配置
3.1 main 函数
应用入口保持 Flutter 标准写法:
void main() {
runApp(const MyApp());
}
屏幕手电筒不需要启动时初始化硬件能力,因此入口很简单。
3.2 MyApp 根组件
根组件负责创建 MaterialApp:
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flashlight',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
),
home: const MyHomePage(title: 'Flashlight'),
);
}
}
这里有三个关键信息:
- 应用标题是
Flashlight。 - 主题种子色是
Colors.amber。 - 首页是
MyHomePage。
3.3 主题色选择
琥珀色很适合灯光类工具。源码中亮度滑块、频率选中项和主题色都与 Colors.amber 呼应,让界面更统一。
当前源码没有显式设置
useMaterial3: true,因此文章以真实代码为准。如果后续需要统一 Material 3 表现,可以在ThemeData中补充该配置。
四、状态字段设计
4.1 核心状态总览
页面核心状态定义在 _MyHomePageState 中:
bool _isOn = false;
double _brightness = 1.0;
String _selectedColor = 'White';
bool _strobeMode = false;
int _strobeFrequency = 5;
字段含义如下:
| 字段 | 类型 | 默认值 | 作用 |
|---|---|---|---|
_isOn |
bool |
false |
灯光是否开启 |
_brightness |
double |
1.0 |
屏幕灯光亮度比例 |
_selectedColor |
String |
White |
当前灯光颜色 |
_strobeMode |
bool |
false |
是否显示频闪模式 |
_strobeFrequency |
int |
5 |
当前选择频率 |
4.2 选项集合
颜色和频率来自两个列表:
final List<String> _colors = ['White', 'Red', 'Green', 'Blue', 'Yellow'];
final List<int> _frequencies = [1, 2, 5, 10, 20];
4.3 状态分层
这些状态可以分成三类:
| 状态类别 | 字段 | 说明 |
|---|---|---|
| 开关状态 | _isOn |
决定显示暗屏还是亮屏 |
| 灯光配置 | _brightness、_selectedColor |
决定屏幕灯光表现 |
| 模式配置 | _strobeMode、_strobeFrequency |
决定频闪界面展示 |
4.4 状态驱动 UI
这个项目的 UI 分支非常依赖 _isOn:关闭时显示暗色入口页,开启时显示全屏灯光页。这样的状态分支清晰、直接,适合小工具应用。
五、颜色映射实现
5.1 getLightColor 方法
颜色名称映射到 Flutter Color:
Color _getLightColor(String color) {
switch (color) {
case 'White': return Colors.white;
case 'Red': return Colors.red.shade400;
case 'Green': return Colors.green.shade400;
case 'Blue': return Colors.blue.shade400;
case 'Yellow': return Colors.yellow.shade600;
default: return Colors.white;
}
}
5.2 映射表
| 颜色名称 | Flutter 颜色 | 页面效果 |
|---|---|---|
| White | Colors.white |
白色屏幕灯 |
| Red | Colors.red.shade400 |
红色屏幕灯 |
| Green | Colors.green.shade400 |
绿色屏幕灯 |
| Blue | Colors.blue.shade400 |
蓝色屏幕灯 |
| Yellow | Colors.yellow.shade600 |
黄色屏幕灯 |
5.3 默认颜色
默认分支返回白色:
default: return Colors.white;
这保证即使传入未知颜色,页面仍然可以正常显示。
5.4 颜色扩展方式
如果后续要增加紫色或青色,只需要增加列表项和 switch 分支:
case 'Purple': return Colors.purple.shade400;
这种扩展方式对入门项目足够清晰。
六、灯光开关与系统 UI 模式
6.1 toggleLight 方法
开关逻辑集中在 _toggleLight():
void _toggleLight() {
setState(() {
_isOn = !_isOn;
if (_isOn) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
} else {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
});
}
6.2 状态切换
_isOn = !_isOn 表示每次点击都取反:
| 点击前 | 点击后 |
|---|---|
false |
true |
true |
false |
6.3 沉浸式系统 UI
开启灯光时设置:
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
关闭灯光时恢复:
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
6.4 为什么开灯后隐藏系统 UI
屏幕手电筒的目标是尽量让整块屏幕发光。隐藏系统 UI 可以减少顶部和底部区域的干扰,让灯光显示更完整。
七、关闭态页面设计
7.1 关闭态分支
当 _isOn 为 false 时,页面显示暗色背景:
GestureDetector(
onTap: _toggleLight,
child: Container(
color: Colors.grey.shade900,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.flashlight_off),
Text('Tap to turn on'),
Text('Long press for options'),
],
),
),
),
)
7.2 暗色背景
关闭态使用:
color: Colors.grey.shade900
这会让页面看起来像“灯已关闭”的状态。
7.3 关闭态文案
页面显示两个提示:
| 文案 | 含义 |
|---|---|
Tap to turn on |
点击开启 |
Long press for options |
长按选项提示 |
需要注意的是,当前源码没有实现长按回调,所以这句文案只是提示性文本。
7.4 图标设计
关闭态使用:
Icons.flashlight_off
大图标能让用户一眼知道当前处于关闭状态。
八、开启态页面与全屏灯光
8.1 开启态分支
当 _isOn 为 true 时,页面显示灯光界面:
GestureDetector(
onTap: _toggleLight,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
color: _getLightColor(_selectedColor).withAlpha((255 * _brightness).round()),
child: Center(...),
),
)
8.2 AnimatedContainer 的作用
AnimatedContainer 可以在颜色变化时提供过渡动画:
duration: const Duration(milliseconds: 300)
用户切换颜色或亮度时,界面不会显得太突兀。
8.3 亮度模拟
屏幕亮度通过 Alpha 值模拟:
withAlpha((255 * _brightness).round())
这里的 _brightness 范围是 0.1 到 1.0。
8.4 亮度计算示例
_brightness |
Alpha 计算 | 近似 Alpha |
|---|---|---|
| 0.1 | 255 * 0.1 |
26 |
| 0.5 | 255 * 0.5 |
128 |
| 1.0 | 255 * 1.0 |
255 |
这种方式改变的是颜色透明度,不是系统屏幕亮度。
九、颜色选择器设计
9.1 颜色选择区域
非频闪模式下,页面会显示颜色选择器:
Wrap(
spacing: 8,
children: _colors.map((color) {
return GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _getLightColor(color),
shape: BoxShape.circle,
border: Border.all(
color: _selectedColor == color ? Colors.white : Colors.transparent,
width: 3,
),
),
),
);
}).toList(),
)
9.2 Wrap 的价值
Wrap 可以让多个颜色圆点按空间自动排列:
Wrap(spacing: 8, children: ...)
如果后续颜色更多,Wrap 比固定 Row 更灵活。
9.3 选中态边框
选中颜色会显示白色边框:
color: _selectedColor == color ? Colors.white : Colors.transparent
9.4 颜色选择流程
点击颜色圆点
-> 更新 _selectedColor
-> AnimatedContainer 使用新颜色
-> 屏幕灯光颜色变化
十、亮度滑块设计
10.1 Slider 配置
亮度滑块如下:
Slider(
value: _brightness,
min: 0.1,
max: 1.0,
activeColor: Colors.amber,
inactiveColor: Colors.white30,
onChanged: (val) {
setState(() {
_brightness = val;
});
},
)
10.2 亮度范围
| 属性 | 值 |
|---|---|
| 最小值 | 0.1 |
| 最大值 | 1.0 |
| 默认值 | 1.0 |
最小值不是 0,可以避免灯光区域完全不可见。
10.3 百分比显示
当前亮度百分比通过以下代码展示:
Text(
'${(_brightness * 100).round()}%',
style: const TextStyle(color: Colors.white),
)
10.4 亮度调节边界
这只是屏幕颜色透明度调节,不是系统屏幕亮度调节。如果需要真正调整系统亮度,需要引入对应平台插件,并处理权限和适配差异。
十一、频闪模式界面
11.1 频闪模式切换
AppBar 中的按钮负责切换频闪模式:
IconButton(
icon: Icon(_strobeMode ? Icons.flash_on : Icons.flash_off),
onPressed: () {
setState(() {
_strobeMode = !_strobeMode;
});
},
)
11.2 频闪模式展示
频闪模式开启后,页面显示:
Text(
'Strobe Mode: $_strobeFrequency Hz',
style: const TextStyle(
fontSize: 24,
color: Colors.white,
fontWeight: FontWeight.bold,
),
)
11.3 频率选项
频率列表如下:
final List<int> _frequencies = [1, 2, 5, 10, 20];
页面中会显示对应按钮:
Text('$freq Hz')
11.4 当前频闪能力边界
当前源码没有定时器,也没有让屏幕按频率闪烁。它实现的是频闪模式 UI 和频率选择状态。若要实现真正屏幕频闪,需要引入周期性定时器,让背景色在亮/暗之间切换。
十二、频率选择器设计
12.1 频率按钮
频率选择器使用 Wrap:
Wrap(
spacing: 8,
children: _frequencies.map((freq) {
return GestureDetector(
onTap: () => setState(() => _strobeFrequency = freq),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _strobeFrequency == freq ? Colors.amber : Colors.white24,
borderRadius: BorderRadius.circular(20),
),
child: Text('$freq Hz'),
),
);
}).toList(),
)
12.2 频率选中态
| 状态 | 背景色 | 文字色 |
|---|---|---|
| 选中 | 琥珀色 | 黑色 |
| 未选中 | 白色半透明 | 白色 |
12.3 频率状态更新
点击频率按钮只更新 _strobeFrequency:
_strobeFrequency = freq
12.4 可扩展实现
如果要让它真正闪烁,可以增加 Timer.periodic,根据 _strobeFrequency 计算间隔:
final intervalMs = (1000 / _strobeFrequency).round();
然后周期性切换亮暗状态。
十三、页面分支与交互闭环
13.1 关闭态到开启态
用户点击暗屏
-> _toggleLight()
-> _isOn = true
-> 设置 immersiveSticky
-> 渲染 AnimatedContainer 灯光页
13.2 开启态到关闭态
用户点击灯光页
-> _toggleLight()
-> _isOn = false
-> 设置 edgeToEdge
-> 渲染暗屏入口页
13.3 普通灯光配置
选择颜色
-> 更新 _selectedColor
-> 更新背景色
拖动亮度
-> 更新 _brightness
-> 更新 Alpha 值
13.4 频闪模式配置
点击 AppBar 闪电图标
-> 切换 _strobeMode
-> 显示频率选择器
-> 点击频率按钮
-> 更新 _strobeFrequency
十四、鸿蒙适配关注点
14.1 适配风险分析
当前项目主要使用 Flutter 标准组件和系统 UI 模式控制,没有硬件闪光灯调用,因此基础适配风险较低。
| 模块 | 是否依赖平台能力 | 适配关注度 |
|---|---|---|
| 屏幕颜色渲染 | 否 | 低 |
| 触摸开关 | Flutter 标准触摸 | 中 |
| Slider 亮度 | Flutter 标准组件 | 中 |
| 沉浸式系统 UI | 系统 UI 行为 | 中 |
| 硬件闪光灯 | 当前未实现 | 高 |
14.2 沉浸式模式验证
鸿蒙设备上需要重点验证:
- 开灯后系统栏是否按预期隐藏。
- 关灯后系统 UI 是否恢复。
- 手势返回和系统导航是否正常。
- 横竖屏切换后 UI 是否稳定。
14.3 触摸体验验证
页面大面积使用 GestureDetector,需要确认:
- 暗屏点击是否能开启。
- 亮屏点击是否能关闭。
- 颜色圆点是否容易点击。
- 频率按钮是否容易点击。
- Slider 拖动是否顺滑。
14.4 功能边界提醒
如果要做真正手电筒应用,鸿蒙适配会涉及硬件闪光灯能力、权限声明、平台通道和设备兼容性。当前项目主要是屏幕灯光模拟,适合做 UI 和状态样例。
十五、测试设计与默认测试改造
15.1 当前测试入口
项目中的测试文件仍是默认计数器测试。对于屏幕手电筒,更有价值的是验证初始状态、开关切换、频闪模式和亮度控件。
15.2 初始页面测试
可以验证关闭态:
testWidgets('flashlight renders off state', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
expect(find.text('Flashlight'), findsOneWidget);
expect(find.text('Tap to turn on'), findsOneWidget);
expect(find.byIcon(Icons.flashlight_off), findsWidgets);
});
15.3 开灯测试
点击页面后应进入开启态:
testWidgets('tap turns flashlight on', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.text('Tap to turn on'));
await tester.pumpAndSettle();
expect(find.text('Tap anywhere to turn off'), findsOneWidget);
expect(find.byIcon(Icons.flashlight_on), findsOneWidget);
});
15.4 频闪模式测试
点击 AppBar 按钮后应显示频闪文案:
testWidgets('can enter strobe mode', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.byIcon(Icons.flash_off).first);
await tester.pump();
await tester.tap(find.text('Tap to turn on'));
await tester.pumpAndSettle();
expect(find.text('Strobe Mode: 5 Hz'), findsOneWidget);
});
15.5 频率选择测试
可以在频闪模式中选择 10 Hz:
testWidgets('can select strobe frequency', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.byIcon(Icons.flash_off).first);
await tester.pump();
await tester.tap(find.text('Tap to turn on'));
await tester.pumpAndSettle();
await tester.tap(find.text('10 Hz'));
await tester.pump();
expect(find.text('Strobe Mode: 10 Hz'), findsOneWidget);
});
十六、可维护性优化方向
16.1 抽离颜色配置
当前颜色列表和 switch 分开维护,可以抽成配置类:
class LightColorOption {
const LightColorOption({
required this.name,
required this.color,
});
final String name;
final Color color;
}
16.2 抽离灯光状态
可以把灯光状态整理成模型:
class FlashlightState {
const FlashlightState({
required this.isOn,
required this.brightness,
required this.colorName,
required this.strobeMode,
});
final bool isOn;
final double brightness;
final String colorName;
final bool strobeMode;
}
16.3 实现真实频闪
当前频闪模式只展示频率。如果要实现真实屏幕频闪,可以使用定时器周期性切换显示颜色。
16.4 接入硬件闪光灯
如果产品目标是真正手电筒,需要引入硬件闪光灯能力。那会涉及平台插件、相机权限、设备兼容性和鸿蒙侧适配,不再是纯 Flutter UI 范畴。
十七、功能扩展方向
17.1 增加 SOS 模式
可以加入 SOS 闪烁节奏,用于紧急场景的屏幕提示。
17.2 增加预设颜色
除了五种颜色,还可以增加:
- 紫色。
- 青色。
- 暖白。
- 冷白。
- 自定义颜色。
17.3 增加系统亮度控制
如果希望屏幕更像手电筒,可以考虑接入系统亮度插件,让用户调节真实屏幕亮度。
17.4 增加硬件模式
可以把应用分成两个模式:
| 模式 | 说明 |
|---|---|
| 屏幕模式 | 当前实现,使用屏幕颜色发光 |
| 硬件模式 | 调用设备闪光灯 |
十八、常见问题与优化建议
18.1 这个应用会打开相机闪光灯吗
不会。当前源码没有调用相机、闪光灯或平台通道,它是屏幕灯光模拟应用。
18.2 亮度调节是不是系统亮度
不是。源码通过 withAlpha() 改变背景色透明度,模拟亮度变化,并不会修改系统屏幕亮度。
18.3 频闪模式是否真的闪烁
当前不会真正闪烁。源码实现了频闪模式界面和频率选择状态,但没有周期性定时器去切换亮暗。
18.4 为什么开灯后隐藏系统 UI
屏幕灯光需要尽量利用整块屏幕。沉浸式模式可以减少系统栏干扰,让灯光区域更完整。
18.5 鸿蒙适配最应该关注什么
重点关注沉浸式系统 UI、触摸开关、Slider 拖动、颜色显示、横竖屏布局和系统返回行为。若要做硬件闪光灯,则需要额外研究鸿蒙平台能力和权限。
十九、完整流程复盘
19.1 页面启动流程
main()
-> runApp(MyApp)
-> MaterialApp
-> MyHomePage
-> _isOn 默认为 false
-> 显示暗色关闭态页面
19.2 开灯流程
点击暗屏
-> _toggleLight()
-> _isOn = true
-> 设置 immersiveSticky
-> 使用 AnimatedContainer 渲染灯光页
19.3 颜色亮度流程
点击颜色圆点
-> 更新 _selectedColor
-> _getLightColor 返回 Color
-> 背景色变化
拖动 Slider
-> 更新 _brightness
-> withAlpha 重新计算透明度
-> 灯光亮度变化
19.4 关灯流程
点击灯光页
-> _toggleLight()
-> _isOn = false
-> 设置 edgeToEdge
-> 回到暗色关闭态页面
二十、相关资源与继续学习
20.1 Flutter 学习资源
屏幕手电筒涉及动画容器、手势、Slider、系统 UI 和测试,可以结合以下资源学习:
| 资源 | 内容 |
|---|---|
| Flutter Docs | Flutter 官方开发文档 |
| Dart 官方文档 | Dart 语言与核心库 |
| Widget catalog | Flutter 常用组件 |
| Flutter testing | Widget 测试与交互模拟 |
20.2 应用扩展方向
后续可以继续增强:
- 真实屏幕频闪。
- SOS 闪烁。
- 自定义颜色。
- 系统亮度控制。
- 硬件闪光灯模式。
- 亮度和颜色记忆。
- 深色模式设置页。
20.3 跨端实践价值
flashlight_app 很适合作为 Flutter 适配鸿蒙的小型工具样例。它依赖很轻,但覆盖了手势、动画、系统 UI 模式、Slider、Wrap 布局和全屏渲染,能帮助开发者验证很多跨端基础交互。
总结
flashlight_app 用简洁的 Flutter 代码实现了一个屏幕手电筒模拟工具。它通过 _isOn 控制开关分支,通过 _selectedColor 和 _brightness 控制屏幕灯光表现,通过 _strobeMode 和 _strobeFrequency 展示频闪模式配置,并在开灯时切换到沉浸式系统 UI。
从工程角度看,这个项目最重要的价值是清晰展示了“状态驱动全屏 UI”的实现方式。面向鸿蒙适配时,项目依赖较轻,主要需要验证沉浸式系统 UI、触摸响应、颜色显示和布局稳定性。需要特别注意的是,当前实现是屏幕灯光模拟,不是硬件闪光灯控制。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐



所有评论(0)