手电筒小应用 - HarmonyOS ArkUI 开发实战-Toggle与Slider组件应用-PC版本

一、应用概述

手电筒应用是移动端最实用的工具类应用之一,它利用设备的闪光灯或屏幕亮度提供照明功能。在HarmonyOS开发中,手电筒应用涉及到Toggle开关组件、Slider亮度控制、Stack堆叠布局、Circle圆形组件等多个核心技术点。本篇博客将深入分析手电筒小应用的设计思路和实现细节,帮助开发者掌握开关控制和亮度调节的构建技巧。

手电筒小应用的核心功能包括:开关状态切换、亮度等级调节、状态视觉反馈、快捷亮度预设。通过这些功能的实现,我们可以学习到Toggle组件的Switch类型使用、Slider组件的亮度控制、状态驱动的样式变化、以及快捷按钮组的交互设计。

从用户体验角度来看,手电筒应用需要提供直观的开关操作、流畅的亮度调节、清晰的状态指示。这些设计要点在HarmonyOS ArkUI中都有对应的实现方案,本篇博客将逐一展开讲解。

二、技术架构分析

2.1 整体架构设计

手电筒应用采用简洁的单页面架构,主要包含展示层和状态层:

复制代码
┌─────────────────────────────────────────────────────────┐
│                     UI展示层                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │ 标题栏   │  │ 状态指示 │  │ 开关按钮 │  │ 亮度调节 │ │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘ │
│  ┌──────────┐  ┌──────────┐                               │
│  │ 亮度显示 │  │ 快捷预设 │                               │
│  └──────────┘  └──────────┘                               │
├─────────────────────────────────────────────────────────┤
│                   状态管理层                             │
│  ┌──────────────────────────────────────────────────┐  │
│  │  @State isOn_1: boolean                         │  │
│  │  @State brightness_1: number                    │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

2.2 数据流向分析

手电筒应用的数据流主要围绕开关状态和亮度值展开:
#mermaid-svg-P97zRC7FNlFVf257{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P97zRC7FNlFVf257 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P97zRC7FNlFVf257 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P97zRC7FNlFVf257 .error-icon{fill:#552222;}#mermaid-svg-P97zRC7FNlFVf257 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P97zRC7FNlFVf257 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P97zRC7FNlFVf257 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P97zRC7FNlFVf257 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P97zRC7FNlFVf257 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P97zRC7FNlFVf257 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P97zRC7FNlFVf257 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P97zRC7FNlFVf257 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P97zRC7FNlFVf257 .marker.cross{stroke:#333333;}#mermaid-svg-P97zRC7FNlFVf257 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P97zRC7FNlFVf257 p{margin:0;}#mermaid-svg-P97zRC7FNlFVf257 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-P97zRC7FNlFVf257 .cluster-label text{fill:#333;}#mermaid-svg-P97zRC7FNlFVf257 .cluster-label span{color:#333;}#mermaid-svg-P97zRC7FNlFVf257 .cluster-label span p{background-color:transparent;}#mermaid-svg-P97zRC7FNlFVf257 .label text,#mermaid-svg-P97zRC7FNlFVf257 span{fill:#333;color:#333;}#mermaid-svg-P97zRC7FNlFVf257 .node rect,#mermaid-svg-P97zRC7FNlFVf257 .node circle,#mermaid-svg-P97zRC7FNlFVf257 .node ellipse,#mermaid-svg-P97zRC7FNlFVf257 .node polygon,#mermaid-svg-P97zRC7FNlFVf257 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P97zRC7FNlFVf257 .rough-node .label text,#mermaid-svg-P97zRC7FNlFVf257 .node .label text,#mermaid-svg-P97zRC7FNlFVf257 .image-shape .label,#mermaid-svg-P97zRC7FNlFVf257 .icon-shape .label{text-anchor:middle;}#mermaid-svg-P97zRC7FNlFVf257 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P97zRC7FNlFVf257 .rough-node .label,#mermaid-svg-P97zRC7FNlFVf257 .node .label,#mermaid-svg-P97zRC7FNlFVf257 .image-shape .label,#mermaid-svg-P97zRC7FNlFVf257 .icon-shape .label{text-align:center;}#mermaid-svg-P97zRC7FNlFVf257 .node.clickable{cursor:pointer;}#mermaid-svg-P97zRC7FNlFVf257 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P97zRC7FNlFVf257 .arrowheadPath{fill:#333333;}#mermaid-svg-P97zRC7FNlFVf257 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P97zRC7FNlFVf257 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P97zRC7FNlFVf257 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P97zRC7FNlFVf257 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P97zRC7FNlFVf257 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P97zRC7FNlFVf257 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P97zRC7FNlFVf257 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P97zRC7FNlFVf257 .cluster text{fill:#333;}#mermaid-svg-P97zRC7FNlFVf257 .cluster span{color:#333;}#mermaid-svg-P97zRC7FNlFVf257 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P97zRC7FNlFVf257 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P97zRC7FNlFVf257 rect.text{fill:none;stroke-width:0;}#mermaid-svg-P97zRC7FNlFVf257 .icon-shape,#mermaid-svg-P97zRC7FNlFVf257 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P97zRC7FNlFVf257 .icon-shape p,#mermaid-svg-P97zRC7FNlFVf257 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P97zRC7FNlFVf257 .icon-shape .label rect,#mermaid-svg-P97zRC7FNlFVf257 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P97zRC7FNlFVf257 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P97zRC7FNlFVf257 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P97zRC7FNlFVf257 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户点击开关按钮
onClick事件触发
isOn_1状态切换
按钮文字更新
按钮颜色更新
状态文字更新
圆形颜色更新
用户拖动亮度滑块
Slider onChange触发
brightness_1状态更新
亮度值显示更新
快捷按钮状态更新
用户点击快捷按钮
onClick事件触发
brightness_1状态更新
快捷按钮样式更新

2.3 组件结构关系

组件名称 父组件 子组件 主要职责
Column - Row, Column 根容器,垂直布局
Row (标题栏) Column Button, Text 显示标题和返回按钮
Stack (状态指示) Column Circle, Text 堆叠圆形状态指示
Text (状态文字) Column - 显示开关状态
Button (开关) Column - 手电筒开关按钮
Text (亮度标签) Column - 亮度调节标题
Slider Column - 亮度调节滑块
Text (亮度值) Column - 显示当前亮度
Row (快捷预设) Column Button, Button, Button 快捷亮度按钮组

三、Toggle开关组件详解

3.1 Toggle组件基础

Toggle组件是HarmonyOS ArkUI中用于实现开关选择的组件,支持Checkbox、Button、Switch三种类型。在手电筒应用中,我们使用Button类型来实现开关按钮。

虽然本应用使用普通Button组件实现开关功能,但Toggle组件的Switch类型在类似场景中也非常常用:

typescript 复制代码
Toggle({ type: ToggleType.Switch, isOn: this.isOn_1 })
  .onChange((isOn: boolean) => {
    this.isOn_1 = isOn;
  })
ToggleType枚举说明
枚举值 说明 适用场景
Checkbox 复选框样式,支持多选 商品选择、任务完成标记
Button 按钮样式,按下时高亮 单选场景、模式切换
Switch 开关样式,滑动切换 设置开关、功能启用

3.2 Switch类型属性

Toggle的Switch类型具有以下常用属性:

typescript 复制代码
Toggle({ type: ToggleType.Switch, isOn: false })
  .width(50)                    // 设置宽度
  .height(26)                   // 设置高度
  .selectedColor('#0A59F7')     // 开启状态颜色
  .switchPointColor('#FFFFFF')  // 开关圆点颜色
  .onChange((isOn: boolean) => {
    // 状态变化回调
  })
Switch组件属性说明
属性名 类型 说明
selectedColor ResourceColor 开启状态背景色
switchPointColor ResourceColor 开关圆点颜色
width Length 开关宽度
height Length 开关高度

3.3 开关按钮实现

本应用使用Button组件实现开关功能,通过状态变量控制样式:

typescript 复制代码
Button(this.isOn_1 ? '关闭手电筒' : '打开手电筒')
  .width('60%')
  .height(56)
  .fontSize(18)
  .margin({ top: 30 })
  .backgroundColor(this.isOn_1 ? '#FF3B30' : '#0A59F7')
  .onClick(() => {
    this.isOn_1 = !this.isOn_1;
  })
开关状态样式对比
状态 文字 背景色 说明
isOn_1 = true '关闭手电筒' #FF3B30(红色) 已开启,显示关闭按钮
isOn_1 = false '打开手电筒' #0A59F7(蓝色) 已关闭,显示打开按钮

3.4 状态联动设计

开关状态变化会影响多个UI元素的样式:

typescript 复制代码
// 圆形状态指示颜色
Circle()
  .fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')

// 状态文字
Text(this.isOn_1 ? '已开启' : '已关闭')
  .fontColor(this.isOn_1 ? '#FFEB3B' : '#666666')

// 状态图标
Text(this.isOn_1 ? '💡' : '🔦')

四、Slider亮度控制详解

4.1 Slider组件基础

Slider组件在手电筒应用中用于调节亮度值,范围设置为20-100:

typescript 复制代码
Slider({
  value: this.brightness_1,
  min: 20,
  max: 100
})
  .width('80%')
  .margin({ top: 16 })
  .onChange((value_1: number) => {
    this.brightness_1 = value_1;
  })
亮度范围设计
参数 说明
min 20 最小亮度,避免完全黑暗
max 100 最大亮度,满亮度
value 100 默认亮度,满亮度

4.2 Slider样式定制

typescript 复制代码
Slider({ value: this.brightness_1, min: 20, max: 100 })
  .width('80%')
  .height(40)
  .blockColor('#0A59F7')       // 滑块颜色
  .trackColor('#E8E8E8')       // 滑轨颜色
  .selectedColor('#0A59F7')    // 已选部分颜色
  .showSteps(false)            // 不显示步长刻度
  .onChange((value: number) => {
    this.brightness_1 = value;
  })

4.3 亮度值显示

亮度值实时显示在滑块下方:

typescript 复制代码
Text('亮度: ' + String(this.brightness_1) + '%')
  .fontSize(14)
  .fontColor('#666666')
  .margin({ top: 8 })

4.4 亮度调节交互流程

快捷按钮 亮度显示 brightness_1 onChange Slider 用户 快捷按钮 亮度显示 brightness_1 onChange Slider 用户 #mermaid-svg-f2mhUw1YtkyH1nrU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f2mhUw1YtkyH1nrU .error-icon{fill:#552222;}#mermaid-svg-f2mhUw1YtkyH1nrU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f2mhUw1YtkyH1nrU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f2mhUw1YtkyH1nrU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f2mhUw1YtkyH1nrU .marker.cross{stroke:#333333;}#mermaid-svg-f2mhUw1YtkyH1nrU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f2mhUw1YtkyH1nrU p{margin:0;}#mermaid-svg-f2mhUw1YtkyH1nrU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-f2mhUw1YtkyH1nrU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-f2mhUw1YtkyH1nrU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-f2mhUw1YtkyH1nrU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-f2mhUw1YtkyH1nrU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-f2mhUw1YtkyH1nrU .sequenceNumber{fill:white;}#mermaid-svg-f2mhUw1YtkyH1nrU #sequencenumber{fill:#333;}#mermaid-svg-f2mhUw1YtkyH1nrU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-f2mhUw1YtkyH1nrU .messageText{fill:#333;stroke:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-f2mhUw1YtkyH1nrU .labelText,#mermaid-svg-f2mhUw1YtkyH1nrU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .loopText,#mermaid-svg-f2mhUw1YtkyH1nrU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-f2mhUw1YtkyH1nrU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-f2mhUw1YtkyH1nrU .noteText,#mermaid-svg-f2mhUw1YtkyH1nrU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-f2mhUw1YtkyH1nrU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-f2mhUw1YtkyH1nrU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-f2mhUw1YtkyH1nrU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-f2mhUw1YtkyH1nrU .actorPopupMenu{position:absolute;}#mermaid-svg-f2mhUw1YtkyH1nrU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-f2mhUw1YtkyH1nrU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-f2mhUw1YtkyH1nrU .actor-man circle,#mermaid-svg-f2mhUw1YtkyH1nrU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-f2mhUw1YtkyH1nrU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 开始拖动触发onChange更新brightness_1更新亮度显示更新快捷按钮状态继续拖动连续触发onChange结束拖动最终onChange

五、快捷亮度预设设计

5.1 快捷按钮组布局

快捷亮度预设使用Row容器水平排列三个按钮:

typescript 复制代码
Row() {
  Button('低')
    .width(60)
    .height(36)
    .fontSize(12)
    .backgroundColor(this.brightness_1 <= 40 ? '#0A59F7' : '#F1F3F5')
    .fontColor(this.brightness_1 <= 40 ? '#FFFFFF' : '#333333')
    .onClick(() => {
      this.brightness_1 = 40;
    })
  Button('中')
    .width(60)
    .height(36)
    .fontSize(12)
    .margin({ left: 8 })
    .backgroundColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#0A59F7' : '#F1F3F5')
    .fontColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#FFFFFF' : '#333333')
    .onClick(() => {
      this.brightness_1 = 70;
    })
  Button('高')
    .width(60)
    .height(36)
    .fontSize(12)
    .margin({ left: 8 })
    .backgroundColor(this.brightness_1 > 70 ? '#0A59F7' : '#F1F3F5')
    .fontColor(this.brightness_1 > 70 ? '#FFFFFF' : '#333333')
    .onClick(() => {
      this.brightness_1 = 100;
    })
}
.margin({ top: 16 })

5.2 亮度等级划分

等级 亮度值 条件 说明
40 brightness_1 <= 40 低亮度模式
70 40 < brightness_1 <= 70 中亮度模式
100 brightness_1 > 70 高亮度模式

5.3 按钮激活状态

快捷按钮根据当前亮度值显示激活状态:

typescript 复制代码
// 低亮度按钮激活条件
.backgroundColor(this.brightness_1 <= 40 ? '#0A59F7' : '#F1F3F5')
.fontColor(this.brightness_1 <= 40 ? '#FFFFFF' : '#333333')

// 中亮度按钮激活条件
.backgroundColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#0A59F7' : '#F1F3F5')
.fontColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#FFFFFF' : '#333333')

// 高亮度按钮激活条件
.backgroundColor(this.brightness_1 > 70 ? '#0A59F7' : '#F1F3F5')
.fontColor(this.brightness_1 > 70 ? '#FFFFFF' : '#333333')

5.4 快捷按钮交互流程

#mermaid-svg-FXAhvf6Q9f9GVMkL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FXAhvf6Q9f9GVMkL .error-icon{fill:#552222;}#mermaid-svg-FXAhvf6Q9f9GVMkL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FXAhvf6Q9f9GVMkL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .marker.cross{stroke:#333333;}#mermaid-svg-FXAhvf6Q9f9GVMkL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FXAhvf6Q9f9GVMkL p{margin:0;}#mermaid-svg-FXAhvf6Q9f9GVMkL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster-label text{fill:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster-label span{color:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster-label span p{background-color:transparent;}#mermaid-svg-FXAhvf6Q9f9GVMkL .label text,#mermaid-svg-FXAhvf6Q9f9GVMkL span{fill:#333;color:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .node rect,#mermaid-svg-FXAhvf6Q9f9GVMkL .node circle,#mermaid-svg-FXAhvf6Q9f9GVMkL .node ellipse,#mermaid-svg-FXAhvf6Q9f9GVMkL .node polygon,#mermaid-svg-FXAhvf6Q9f9GVMkL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .rough-node .label text,#mermaid-svg-FXAhvf6Q9f9GVMkL .node .label text,#mermaid-svg-FXAhvf6Q9f9GVMkL .image-shape .label,#mermaid-svg-FXAhvf6Q9f9GVMkL .icon-shape .label{text-anchor:middle;}#mermaid-svg-FXAhvf6Q9f9GVMkL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .rough-node .label,#mermaid-svg-FXAhvf6Q9f9GVMkL .node .label,#mermaid-svg-FXAhvf6Q9f9GVMkL .image-shape .label,#mermaid-svg-FXAhvf6Q9f9GVMkL .icon-shape .label{text-align:center;}#mermaid-svg-FXAhvf6Q9f9GVMkL .node.clickable{cursor:pointer;}#mermaid-svg-FXAhvf6Q9f9GVMkL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .arrowheadPath{fill:#333333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FXAhvf6Q9f9GVMkL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FXAhvf6Q9f9GVMkL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FXAhvf6Q9f9GVMkL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster text{fill:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL .cluster span{color:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FXAhvf6Q9f9GVMkL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FXAhvf6Q9f9GVMkL rect.text{fill:none;stroke-width:0;}#mermaid-svg-FXAhvf6Q9f9GVMkL .icon-shape,#mermaid-svg-FXAhvf6Q9f9GVMkL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FXAhvf6Q9f9GVMkL .icon-shape p,#mermaid-svg-FXAhvf6Q9f9GVMkL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FXAhvf6Q9f9GVMkL .icon-shape .label rect,#mermaid-svg-FXAhvf6Q9f9GVMkL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FXAhvf6Q9f9GVMkL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FXAhvf6Q9f9GVMkL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FXAhvf6Q9f9GVMkL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 点击低按钮
brightness_1 = 40
低按钮激活
中按钮未激活
高按钮未激活
点击中按钮
brightness_1 = 70
低按钮未激活
中按钮激活
高按钮未激活
点击高按钮
brightness_1 = 100
低按钮未激活
中按钮未激活
高按钮激活

六、状态指示设计

6.1 圆形状态指示

手电筒状态通过圆形组件和图标组合展示:

typescript 复制代码
Stack() {
  Circle()
    .width(200)
    .height(200)
    .fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')
    .margin({ top: 60 })
  Text(this.isOn_1 ? '💡' : '🔦')
    .fontSize(80)
}

6.2 状态视觉对比

状态 圆形颜色 图标 说明
已开启 #FFEB3B(黄色) 💡 模拟灯光亮起
已关闭 #F1F3F5(灰色) 🔦 模拟手电筒关闭

6.3 状态文字显示

typescript 复制代码
Text(this.isOn_1 ? '已开启' : '已关闭')
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  .fontColor(this.isOn_1 ? '#FFEB3B' : '#666666')
  .margin({ top: 30 })

6.4 状态变化动画效果(扩展)

typescript 复制代码
// 可以添加动画效果
Stack() {
  Circle()
    .width(200)
    .height(200)
    .fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')
    .opacity(this.isOn_1 ? 1 : 0.5)  // 开启时更亮
}
.animation({
  duration: 300,
  curve: Curve.EaseInOut
})

七、状态管理详解

7.1 状态变量定义

手电筒应用使用两个状态变量管理核心数据:

typescript 复制代码
@State isOn_1: boolean = false;
@State brightness_1: number = 100;

状态变量说明:

变量名 类型 初始值 说明
isOn_1 boolean false 手电筒开关状态
brightness_1 number 100 亮度值(20-100)

7.2 状态更新触发UI变化

typescript 复制代码
// 开关状态变化触发多个UI更新
.onClick(() => {
  this.isOn_1 = !this.isOn_1;
})

// 亮度变化触发多个UI更新
.onChange((value: number) => {
  this.brightness_1 = value;
})

7.3 状态联动关系

开关状态和亮度值之间存在联动关系:
#mermaid-svg-Vh5LRnKi1rLkndyw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Vh5LRnKi1rLkndyw .error-icon{fill:#552222;}#mermaid-svg-Vh5LRnKi1rLkndyw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Vh5LRnKi1rLkndyw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Vh5LRnKi1rLkndyw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Vh5LRnKi1rLkndyw .marker.cross{stroke:#333333;}#mermaid-svg-Vh5LRnKi1rLkndyw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Vh5LRnKi1rLkndyw p{margin:0;}#mermaid-svg-Vh5LRnKi1rLkndyw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster-label text{fill:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster-label span{color:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster-label span p{background-color:transparent;}#mermaid-svg-Vh5LRnKi1rLkndyw .label text,#mermaid-svg-Vh5LRnKi1rLkndyw span{fill:#333;color:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw .node rect,#mermaid-svg-Vh5LRnKi1rLkndyw .node circle,#mermaid-svg-Vh5LRnKi1rLkndyw .node ellipse,#mermaid-svg-Vh5LRnKi1rLkndyw .node polygon,#mermaid-svg-Vh5LRnKi1rLkndyw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Vh5LRnKi1rLkndyw .rough-node .label text,#mermaid-svg-Vh5LRnKi1rLkndyw .node .label text,#mermaid-svg-Vh5LRnKi1rLkndyw .image-shape .label,#mermaid-svg-Vh5LRnKi1rLkndyw .icon-shape .label{text-anchor:middle;}#mermaid-svg-Vh5LRnKi1rLkndyw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Vh5LRnKi1rLkndyw .rough-node .label,#mermaid-svg-Vh5LRnKi1rLkndyw .node .label,#mermaid-svg-Vh5LRnKi1rLkndyw .image-shape .label,#mermaid-svg-Vh5LRnKi1rLkndyw .icon-shape .label{text-align:center;}#mermaid-svg-Vh5LRnKi1rLkndyw .node.clickable{cursor:pointer;}#mermaid-svg-Vh5LRnKi1rLkndyw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Vh5LRnKi1rLkndyw .arrowheadPath{fill:#333333;}#mermaid-svg-Vh5LRnKi1rLkndyw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Vh5LRnKi1rLkndyw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Vh5LRnKi1rLkndyw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vh5LRnKi1rLkndyw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Vh5LRnKi1rLkndyw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vh5LRnKi1rLkndyw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster text{fill:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw .cluster span{color:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Vh5LRnKi1rLkndyw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Vh5LRnKi1rLkndyw rect.text{fill:none;stroke-width:0;}#mermaid-svg-Vh5LRnKi1rLkndyw .icon-shape,#mermaid-svg-Vh5LRnKi1rLkndyw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vh5LRnKi1rLkndyw .icon-shape p,#mermaid-svg-Vh5LRnKi1rLkndyw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Vh5LRnKi1rLkndyw .icon-shape .label rect,#mermaid-svg-Vh5LRnKi1rLkndyw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vh5LRnKi1rLkndyw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Vh5LRnKi1rLkndyw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Vh5LRnKi1rLkndyw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} isOn_1状态
圆形颜色
状态文字
开关按钮样式
状态图标
brightness_1状态
亮度显示
快捷按钮状态
Slider位置

八、UI布局实现

8.1 整体布局结构

手电筒应用采用Column作为根容器,内部嵌套Stack、Button、Slider、Row等组件:

复制代码
Column (根容器)
│
├── Row (标题栏)
│   ├── Button (返回)
│   └── Text (标题)
│
└── Column (内容区域)
    ├── Stack (状态指示)
    │   ├── Circle (圆形背景)
    │   └── Text (状态图标)
    │
    ├── Text (状态文字)
    │
    ├── Button (开关按钮)
    │
    ├── Text (亮度标签)
    │
    ├── Slider (亮度滑块)
    │
    ├── Text (亮度值)
    │
    └── Row (快捷预设)
        ├── Button (低)
        ├── Button (中)
        └── Button (高)

8.2 标题栏实现

typescript 复制代码
Row() {
  Button('返回')
    .onClick(() => router.back())
  Text('手电筒小应用')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .layoutWeight(1)
    .textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')

8.3 状态指示区域

typescript 复制代码
Stack() {
  Circle()
    .width(200)
    .height(200)
    .fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')
    .margin({ top: 60 })
  Text(this.isOn_1 ? '💡' : '🔦')
    .fontSize(80)
}

8.4 亮度调节区域

typescript 复制代码
Text('亮度调节')
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .margin({ top: 40 })

Slider({
  value: this.brightness_1,
  min: 20,
  max: 100
})
  .width('80%')
  .margin({ top: 16 })
  .onChange((value_1: number) => {
    this.brightness_1 = value_1;
  })

Text('亮度: ' + String(this.brightness_1) + '%')
  .fontSize(14)
  .fontColor('#666666')
  .margin({ top: 8 })

九、样式定制技巧

9.1 颜色规范

用途 颜色值 说明
主色调 #0A59F7 蓝色,打开按钮、激活按钮
关闭色调 #FF3B30 红色,关闭按钮
灯光色 #FFEB3B 黄色,开启状态指示
背景色 #FFFFFF 白色,主背景
次级背景 #F1F3F5 浅灰色,标题栏、关闭状态
主文字 #333333 深灰色,正常文字
次级文字 #666666 中灰色,状态文字、亮度值
激活文字 #FFFFFF 白色,激活按钮文字

9.2 尺寸规范

元素 尺寸 说明
标题文字 20fp 标题栏文字
状态文字 20fp 开关状态文字
亮度标签 16fp 亮度调节标题
亮度值 14fp 亮度百分比文字
快捷按钮文字 12fp 低/中/高按钮文字
开关按钮 56vp 开关按钮高度
快捷按钮 36vp 快捷预设按钮高度
圆形指示 200vp 状态圆形直径
状态图标 80fp 状态图标字体大小

9.3 圆角设计

typescript 复制代码
// 开关按钮圆角(默认)
Button('打开手电筒')
  .height(56)
  // 默认圆角

// 快捷按钮圆角(默认)
Button('低')
  .height(36)
  // 默认圆角

十、完整代码展示

typescript 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct FlashlightApp {
  // 手电筒开关状态
  @State isOn_1: boolean = false;
  // 亮度值(20-100)
  @State brightness_1: number = 100;

  build() {
    Column() {
      // 标题栏
      Row() {
        Button('返回')
          .onClick(() => router.back())
        Text('手电筒小应用')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#F1F3F5')

      // 内容区域
      Column() {
        // 状态指示
        Stack() {
          Circle()
            .width(200)
            .height(200)
            .fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')
            .margin({ top: 60 })
          Text(this.isOn_1 ? '💡' : '🔦')
            .fontSize(80)
        }

        // 状态文字
        Text(this.isOn_1 ? '已开启' : '已关闭')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.isOn_1 ? '#FFEB3B' : '#666666')
          .margin({ top: 30 })

        // 开关按钮
        Button(this.isOn_1 ? '关闭手电筒' : '打开手电筒')
          .width('60%')
          .height(56)
          .fontSize(18)
          .margin({ top: 30 })
          .backgroundColor(this.isOn_1 ? '#FF3B30' : '#0A59F7')
          .onClick(() => {
            this.isOn_1 = !this.isOn_1;
          })

        // 亮度调节标题
        Text('亮度调节')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .margin({ top: 40 })

        // 亮度滑块
        Slider({
          value: this.brightness_1,
          min: 20,
          max: 100
        })
          .width('80%')
          .margin({ top: 16 })
          .onChange((value_1: number) => {
            this.brightness_1 = value_1;
          })

        // 亮度值显示
        Text('亮度: ' + String(this.brightness_1) + '%')
          .fontSize(14)
          .fontColor('#666666')
          .margin({ top: 8 })

        // 快捷亮度预设
        Row() {
          Button('低')
            .width(60)
            .height(36)
            .fontSize(12)
            .backgroundColor(this.brightness_1 <= 40 ? '#0A59F7' : '#F1F3F5')
            .fontColor(this.brightness_1 <= 40 ? '#FFFFFF' : '#333333')
            .onClick(() => {
              this.brightness_1 = 40;
            })
          Button('中')
            .width(60)
            .height(36)
            .fontSize(12)
            .margin({ left: 8 })
            .backgroundColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#0A59F7' : '#F1F3F5')
            .fontColor(this.brightness_1 > 40 && this.brightness_1 <= 70 ? '#FFFFFF' : '#333333')
            .onClick(() => {
              this.brightness_1 = 70;
            })
          Button('高')
            .width(60)
            .height(36)
            .fontSize(12)
            .margin({ left: 8 })
            .backgroundColor(this.brightness_1 > 70 ? '#0A59F7' : '#F1F3F5')
            .fontColor(this.brightness_1 > 70 ? '#FFFFFF' : '#333333')
            .onClick(() => {
              this.brightness_1 = 100;
            })
        }
        .margin({ top: 16 })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

十一、组件对比分析

11.1 Toggle(Switch) vs Button

Toggle的Switch类型和Button都可以实现开关功能:

特性 Toggle(Switch) Button
外观 滑动开关样式 按钮样式
交互 滑动切换 点击切换
状态指示 内置开关位置 需自定义样式
适用场景 设置开关 功能开关按钮

11.2 Slider vs TextInput

Slider和TextInput都可以用于数值输入:

特性 Slider TextInput
输入方式 滑动选择 键盘输入
数值范围 可设置min/max 需手动验证
交互体验 直观 精确
适用场景 亮度调节、音量 精确数值输入

11.3 Circle vs Image

Circle和Image都可以用于状态指示:

特性 Circle Image
图形类型 矢量圆形 图片资源
定制能力 颜色、大小 图片内容
性能 高(原生绘制) 中(图片加载)
适用场景 简单圆形指示 复杂图片图标

十二、最佳实践总结

12.1 开关状态管理

typescript 复制代码
// ✅ 推荐:使用布尔状态变量
@State isOn_1: boolean = false;

.onClick(() => {
  this.isOn_1 = !this.isOn_1;
})

// ❌ 避免:使用字符串状态
@State status_1: string = 'off';

.onClick(() => {
  this.status_1 = this.status_1 === 'off' ? 'on' : 'off';
})

12.2 亮度范围设置

typescript 复制代码
// ✅ 推荐:设置合理的最小值
Slider({ value: 100, min: 20, max: 100 })
// 避免亮度为0的情况

// ❌ 避免:最小值为0
Slider({ value: 100, min: 0, max: 100 })
// 可能导致完全黑暗,用户体验差

12.3 快捷按钮状态同步

typescript 复制代码
// ✅ 推荐:使用条件表达式同步状态
.backgroundColor(this.brightness_1 <= 40 ? '#0A59F7' : '#F1F3F5')

// ❌ 避免:硬编码激活状态
.backgroundColor('#0A59F7')
// 状态不随亮度变化

12.4 状态视觉反馈

typescript 复制代码
// ✅ 推荐:多元素联动反馈
Circle().fill(this.isOn_1 ? '#FFEB3B' : '#F1F3F5')
Text(this.isOn_1 ? '已开启' : '已关闭')
Button(this.isOn_1 ? '关闭' : '打开')

// ❌ 避免:单一元素反馈
// 只有按钮文字变化,其他元素不变

十三、扩展功能建议

13.1 真实手电筒控制

typescript 复制代码
import { camera } from '@kit.CameraKit';

// 控制闪光灯
async toggleFlashlight(isOn: boolean) {
  let cameraManager = camera.getCameraManager(getContext(this));
  let cameras = cameraManager.getSupportedCameras();
  
  for (let cam of cameras) {
    if (cam.cameraType === camera.CameraType.CAMERA_TYPE_BACK) {
      let cameraInput = cameraManager.createCameraInput(cam);
      let flashMode = isOn ? camera.FlashMode.FLASH_MODE_ALWAYS_ON : camera.FlashMode.FLASH_MODE_OFF;
      cameraInput.setFlashMode(flashMode);
    }
  }
}

13.2 频闪模式

typescript 复制代码
@State strobeMode_1: boolean = false;
@State strobeInterval_1: number = 500;  // 闪烁间隔(毫秒)

// 频闪控制
startStrobe() {
  setInterval(() => {
    if (this.strobeMode_1) {
      this.isOn_1 = !this.isOn_1;
    }
  }, this.strobeInterval_1);
}

// 频闪间隔调节
Slider({ value: this.strobeInterval_1, min: 100, max: 1000 })
  .onChange((value: number) => {
    this.strobeInterval_1 = value;
  })

13.3 SOS模式

typescript 复制代码
@State sosMode_1: boolean = false;

// SOS闪烁序列
// S: 3短闪
// O: 3长闪
// S: 3短闪
async playSOS() {
  const shortDuration = 200;
  const longDuration = 600;
  const pauseDuration = 200;
  
  // S
  for (let i = 0; i < 3; i++) {
    this.isOn_1 = true;
    await this.delay(shortDuration);
    this.isOn_1 = false;
    await this.delay(pauseDuration);
  }
  
  // O
  for (let i = 0; i < 3; i++) {
    this.isOn_1 = true;
    await this.delay(longDuration);
    this.isOn_1 = false;
    await this.delay(pauseDuration);
  }
  
  // S
  for (let i = 0; i < 3; i++) {
    this.isOn_1 = true;
    await this.delay(shortDuration);
    this.isOn_1 = false;
    await this.delay(pauseDuration);
  }
}

13.4 颜色滤镜

typescript 复制代码
@State filterColor_1: string = 'white';

// 颜色选择
Row() {
  Button('白')
    .onClick(() => this.filterColor_1 = 'white')
  Button('红')
    .onClick(() => this.filterColor_1 = 'red')
  Button('蓝')
    .onClick(() => this.filterColor_1 = 'blue')
  Button('绿')
    .onClick(() => this.filterColor_1 = 'green')
}

// 应用颜色滤镜
Circle()
  .fill(this.getFilterColor())

getFilterColor(): string {
  const colorMap: Record<string, string> = {
    'white': '#FFEB3B',
    'red': '#FF5722',
    'blue': '#2196F3',
    'green': '#4CAF50'
  };
  return colorMap[this.filterColor_1] || '#FFEB3B';
}

13.5 屏幕常亮

typescript 复制代码
import { window } from '@kit.ArkUI';

// 设置屏幕常亮
async setKeepScreenOn(isOn: boolean) {
  let windowStage = window.getLastWindow(getContext(this));
  await windowStage.setWindowKeepScreenOn(isOn);
}

// 开启手电筒时保持屏幕常亮
.onClick(() => {
  this.isOn_1 = !this.isOn_1;
  this.setKeepScreenOn(this.isOn_1);
})

13.6 定时关闭

typescript 复制代码
@State timerMinutes_1: number = 0;
@State timerActive_1: boolean = false;

// 设置定时关闭
startTimer(minutes: number) {
  this.timerMinutes_1 = minutes;
  this.timerActive_1 = true;
  
  setTimeout(() => {
    this.isOn_1 = false;
    this.timerActive_1 = false;
  }, minutes * 60 * 1000);
}

// 定时选项
Row() {
  Button('5分钟')
    .onClick(() => this.startTimer(5))
  Button('10分钟')
    .onClick(() => this.startTimer(10))
  Button('30分钟')
    .onClick(() => this.startTimer(30))
}

十四、性能优化策略

14.1 状态更新优化

typescript 复制代码
// ✅ 推荐:批量更新状态
.onClick(() => {
  this.isOn_1 = !this.isOn_1;
  // 其他相关状态一起更新
})

// ❌ 避免:分散更新
.onClick(() => {
  this.isOn_1 = !this.isOn_1;
})
// 其他状态在其他地方更新

14.2 Slider拖动优化

typescript 复制代码
// ✅ 推荐:节流更新
let lastUpdateTime: number = 0;

.onChange((value: number) => {
  let now = Date.now();
  if (now - lastUpdateTime > 50) {  // 50ms间隔
    this.brightness_1 = value;
    lastUpdateTime = now;
  }
})

// ❌ 避免:频繁更新
.onChange((value: number) => {
  this.brightness_1 = value;  // 每次拖动都触发渲染
})

14.3 圆形组件优化

typescript 复制代码
// ✅ 推荐:使用固定尺寸
Circle()
  .width(200)
  .height(200)

// ❌ 避免:使用百分比尺寸
Circle()
  .width('50%')
  .height('50%')
// 可能导致圆形变形

14.4 快捷按钮优化

typescript 复制代码
// ✅ 推荐:使用条件表达式
.backgroundColor(this.brightness_1 <= 40 ? '#0A59F7' : '#F1F3F5')

// ❌ 避免:使用函数计算
.backgroundColor(this.getButtonColor('low'))
// 每次渲染都调用函数

十五、总结

手电筒小应用展示了HarmonyOS ArkUI中开关控制和亮度调节的核心技术。通过本篇博客的学习,我们掌握了以下知识点:

  1. Toggle组件:了解了Switch类型的使用方法和属性配置,以及与Button组件的对比
  2. Slider组件:掌握了亮度调节的实现,包括范围设置、样式定制、值显示等技巧
  3. 状态指示:学会了圆形组件和图标组合的状态展示方式,以及颜色联动设计
  4. 快捷预设:掌握了快捷按钮组的布局和激活状态同步的实现方法
  5. 状态管理:理解了开关状态和亮度值的联动关系,以及多元素协同更新
  6. UI布局:掌握了Stack、Column、Row的组合使用,实现了完整的界面布局

手电筒应用虽然功能简单,但涉及到的技术点非常实用。在实际开发中,还需要集成真实的闪光灯控制API、实现频闪模式、添加颜色滤镜等扩展功能。这些功能的实现都建立在本篇博客介绍的基础技术之上。


源代码路径FlashlightApp.ets(file:///d:/HarmonyOSProject/newdemo10616/entry/src/main/ets/pages/miniApps/FlashlightApp.ets)