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 实战能力:

  1. 如何用布尔状态切换两个完全不同的页面分支。
  2. 如何用 AnimatedContainer 做平滑背景色变化。
  3. 如何通过 Alpha 值模拟屏幕亮度。
  4. 如何使用 SystemChrome 控制系统 UI 模式。
  5. 如何用 Wrap 做颜色圆点和频率按钮。
  6. 如何面向鸿蒙检查沉浸式、触摸和屏幕适配。

二、工程结构与运行方式

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 关闭态分支

_isOnfalse 时,页面显示暗色背景:

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 开启态分支

_isOntrue 时,页面显示灯光界面:

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.11.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、触摸响应、颜色显示和布局稳定性。需要特别注意的是,当前实现是屏幕灯光模拟,不是硬件闪光灯控制。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐