鸿蒙新特性:Radio 单项选择组件——构建健康评估问卷
单选是表单中最基础的交互。性别选择、支付方式、评分档位、问卷答案——所有这些"多选一"的场景都需要单选按钮。Radio 组件通过互斥的圆形按钮和清晰的选中标记,让用户在多个互斥选项中做出明确的单一选择。
HarmonyOS NEXT ArkUI 提供了 Radio 组件——一个标准的单项选择控件,通常与 group 属性配合使用实现组内互斥。本文将深入讲解 Radio 组件与 Radio 组的 API,并构建一个"健康评估问卷"——支持 5 道单选题、自动评分、健康等级分级和重新评估。
关键词:HarmonyOS、ArkUI、Radio、单项选择、问卷调查、健康评估、自动评分
一、Radio 组件 API
1.1 基本用法
Radio({ value: '0', group: 'question1' })
.checked(this.selectedOption === 0)
.width(24)
.height(24)
.radioStyle({ checkedBackgroundColor: '#1677FF' })
核心参数与属性:
| 参数/属性 | 类型 | 说明 |
|---|---|---|
value |
string | 该选项的值标识 |
group |
string | 组名,同组 Radio 互斥 |
.checked() |
boolean | 是否选中(@State 驱动) |
.radioStyle() |
RadioStyle | 自定义选中状态的样式 |
.width() / .height() |
Length | 按钮尺寸 |
1.2 group 互斥机制
Radio 的互斥通过 group 属性实现——所有 group 值相同的 Radio 自动互斥,用户选择其中一个时,同组其他 Radio 自动取消选中:
// 问题 1 的 4 个选项——所有 group 都是 'question1'
Radio({ value: '0', group: 'question1' }) // 选项 A
Radio({ value: '1', group: 'question1' }) // 选项 B
Radio({ value: '2', group: 'question1' }) // 选项 C
Radio({ value: '3', group: 'question1' }) // 选项 D
// 问题 2 的 4 个选项——所有 group 都是 'question2'
Radio({ value: '0', group: 'question2' }) // 选项 A
Radio({ value: '1', group: 'question2' }) // 选项 B
// ...
问题 1 和问题 2 的 Radio 使用不同的 group 值,因此互不影响。用户可以在问题 1 选 A、在问题 2 选 C——两者互不干扰。
Demo 中通过 'q'.concat(qIdx.toString()) 动态生成组名,例如问题 0 的组名为 'q0',问题 1 的组名为 'q1',以此类推。
1.3 checked 与 @State 联动
Radio 的 checked 属性需要手动绑定 @State 变量来控制选中状态:
Radio({ value: '0', group: 'question1' })
.checked(this.answers[0] === 0)
这里 this.answers[0] 存储问题 1 的选中选项索引(-1 表示未选,0/1/2/3 表示已选)。当 answers[0] === 0 时,第一个 Radio 显示选中状态;当用户点击其他 Radio 时,answers[0] 更新为新索引,之前的 Radio 自动取消选中。
这种"用 @State 数组管理所有问题答案"的模式让 5 道题的答案统一存储、统一管理、统一重置。
1.4 radioStyle 自定义样式
.radioStyle({ checkedBackgroundColor: '#1677FF' })
checkedBackgroundColor 控制选中时内圆点的填充颜色。默认的灰色选中圆点可能不够醒目,自定义为蓝色后与页面主题色一致,选中状态更清晰。
1.5 行级点击区域
Radio 按钮本身只有 24×24 的点击区域,在移动端不够大。Demo 中将整个选项行设置为可点击:
Row() {
Radio({ value: ... }).width(24).height(24)
Text(option.label)
}
.onClick(() => { this.selectAnswer(qIdx, oIdx); })
用户点击"选项行"的任意位置(包括 Radio 和文字)都会触发选择,而不仅仅是点击 Radio 小圆圈。这显著提升了移动端的操作体验。
1.6 选中行的视觉反馈
选中行使用浅蓝背景(#F0F5FF),未选中行使用浅灰背景(#F8F9FA),选中文字更深更粗:
.fontColor(this.answers[qIdx] === oIdx ? '#1a1a2e' : '#888899')
.fontWeight(this.answers[qIdx] === oIdx ? FontWeight.Medium : FontWeight.Normal)
.backgroundColor(this.answers[qIdx] === oIdx ? '#F0F5FF' : '#F8F9FA')
这种整行高亮的设计让用户一眼就能看到每道题的当前选择,无需寻找那个小小的 Radio 按钮。
二、健康评估问卷的整体设计
2.1 页面架构
SurveyPage
├── 标题栏 — "健康评估" + 进度计数(已答/总题数)
├── 答题模式(submitted = false)
│ ├── 问题 1: 😴 睡眠时长 — 4 个 Radio 选项
│ ├── 问题 2: 🏃 运动频率 — 4 个 Radio 选项
│ ├── 问题 3: 💧 饮水量 — 4 个 Radio 选项
│ ├── 问题 4: 🧠 压力感受 — 4 个 Radio 选项
│ ├── 问题 5: 🍽️ 饮食习惯 — 4 个 Radio 选项
│ └── 提交按钮(全部完成时激活)
└── 结果模式(submitted = true)
├── 健康等级 emoji + 标签 + 总分
├── 健康建议文字
└── "重新评估"按钮
2.2 数据结构
class QuestionOption {
label: string; // 选项文字
score: number; // 该选项的分值(1-4)
}
class Question {
id: number;
title: string; // 问题文字
icon: string; // 图标 emoji
options: QuestionOption[]; // 4 个选项
}
5 个问题覆盖了健康评估的核心维度:
| 问题 | 图标 | 4 个选项的分值 |
|---|---|---|
| 睡眠时长 | 😴 | 不足5h(1) / 5-7h(2) / 7-8h(3) / >8h(4) |
| 运动频率 | 🏃 | 几乎不(1) / 1-2次(2) / 3-4次(3) / >5次(4) |
| 饮水量 | 💧 | 不足3杯(1) / 3-5杯(2) / 6-8杯(3) / >8杯(4) |
| 压力感受 | 🧠 | 很大(1) / 较大(2) / 一般(3) / 轻松(4) |
| 饮食习惯 | 🍽️ | 偏油腻(1) / 偏清淡(2) / 均衡(3) / 规划(4) |
每题 4 个选项,分值从 1 到 4,总分范围 5-20。分值越高代表生活习惯越好。
2.3 答案存储
@State answers: number[] = [-1, -1, -1, -1, -1];
5 个元素的数组,-1 表示未选,0/1/2/3 表示选中的选项索引。用户每点击一个选项,selectAnswer 通过 slice() 创建新数组并更新对应索引:
selectAnswer(questionIdx: number, optionIdx: number): void {
let newAnswers = this.answers.slice();
newAnswers[questionIdx] = optionIdx;
this.answers = newAnswers;
}
注意这里使用了 let newAnswers = this.answers.slice()(在方法内部而非 UI 函数中),这符合 ArkTS 的语法规则。
三、评分与结果
3.1 总分计算
getTotalScore(): number {
let score = 0;
for (let i = 0; i < this.answers.length; i++) {
let idx = this.answers[i];
if (idx >= 0) {
score += this.getQuestions()[i].options[idx].score;
}
}
return score;
}
遍历所有已答问题,累加各选项的分值。getQuestions()[i].options[idx].score 通过问题索引和选项索引两级查找获取分值——这种设计让分值始终与选项绑定,修改选项时只需改一处。
3.2 健康等级评定
getHealthLevel(): HealthLevel {
let score = this.getTotalScore();
if (score >= 18) return new HealthLevel('非常健康', '#52C41A', '...', '🎉');
if (score >= 14) return new HealthLevel('比较健康', '#1677FF', '...', '👍');
if (score >= 10) return new HealthLevel('需要关注', '#FAAD14', '...', '⚠️');
return new HealthLevel('急需调整', '#FF4D4F', '...', '❗');
}
HealthLevel 是一个专门的类(而非匿名对象——ArkTS 要求所有对象字面量对应显式声明的类型),包含 4 个字段:
| 字段 | 说明 |
|---|---|
label |
等级名称 |
color |
主题色(绿/蓝/金/红) |
desc |
个性化疗建议 |
emoji |
等级图标 |
四个等级的颜色映射:
- 绿色(18-20 分):非常健康——积极完成
- 蓝色(14-17 分):比较健康——肯定鼓励
- 金色(10-13 分):需要关注——温和警示
- 红色(5-9 分):急需调整——强烈提醒
3.3 结果卡片
提交后,页面从"答题模式"切换为"结果模式"——问题列表隐藏,取而代之的是一个结果卡片:
- 大号 emoji(48px)
- 健康等级名称(24px,等级主题色)
- 总分显示(“总分: 16 / 20”)
- 健康建议文字(13px,灰色)
- "重新评估"按钮(等级主题色圆角)
3.4 重新评估
retakeSurvey(): void {
this.answers = [-1, -1, -1, -1, -1];
this.submitted = false;
}
将答案重置为全 -1(未选状态),submitted 切回 false。页面回到答题模式,所有 Radio 恢复未选中状态,提交按钮恢复禁用状态。
四、提交控制
4.1 完成度检测
isComplete(): boolean {
for (let i = 0; i < this.answers.length; i++) {
if (this.answers[i] === -1) return false;
}
return true;
}
当所有 5 题都已选择选项(answers 中无 -1)时,isComplete() 返回 true。提交按钮根据此状态变换样式和行为:
Text(this.isComplete() ? '提交评估' : '请完成所有题目后提交')
.fontColor(this.isComplete() ? '#FFFFFF' : '#BBBBCC')
// 背景色变化
.backgroundColor(this.isComplete() ? '#1677FF' : '#F8F9FA')
// 点击事件守卫
.onClick(() => { if (this.isComplete()) { this.submitSurvey(); } })
未完成时:灰色文字 + 浅灰背景 + 点击无效。完成后:白色文字 + 蓝色背景 + 可点击提交。
4.2 进度计数
标题栏右侧显示"已答 / 总题数"计数:
Text(this.getAnswerCount().toString().concat('/')
.concat(this.getQuestions().length.toString()))
getAnswerCount(): number {
let count = 0;
for (let i = 0; i < this.answers.length; i++) {
if (this.answers[i] !== -1) count++;
}
return count;
}
随着用户逐题作答,计数从 “0/5” 递增到 “5/5”,给用户清晰的任务完成感。
五、页面状态切换
Demo 包含两种页面状态,由 submitted 控制:
- 答题模式(
submitted = false):显示 5 个问题卡片 + 提交按钮 - 结果模式(
submitted = true):显示结果卡片(问题列表隐藏)
两种状态共用同一个 Scroll 容器,通过 if (this.submitted) 和 if (!this.submitted) 条件渲染切换。切换时页面滚动位置自然回到顶部(因为内容完全替换),用户看到结果卡片位于屏幕上方。
六、交互流程演示
6.1 开始答题
进入页面,5 道题的所有选项均为未选中状态。标题栏显示"0/5"。提交按钮显示"请完成所有题目后提交",灰底灰字不可点击。
6.2 逐题作答
点击第 1 题(睡眠时长)的第 3 个选项"7-8 小时"。该选项行变为浅蓝背景,Radio 内圆点变为蓝色,文字变深加粗。标题栏计数变为"1/5"。
依次完成其余 4 题。标题栏从"2/5"递增到"5/5"。完成第 5 题后,提交按钮变为蓝底白字"提交评估",提示用户可以提交。
6.3 查看结果
点击"提交评估"。Toast 提示"评估完成,查看结果"。页面切换为结果模式——结果卡片显示健康等级、总分和个性化建议。"重新评估"按钮位于卡片底部。
6.4 重新评估
点击"重新评估"。页面回到答题模式,所有选项恢复未选中。标题栏计数归零"0/5",提交按钮恢复灰色禁用状态。用户可以修改选择并重新提交。
七、总结
本文通过"健康评估问卷"这个实战案例,全面讲解了 ArkUI Radio 单项选择组件的使用方法。核心知识点包括:
- Radio 基础 API:
value+group组内互斥 +.checked()状态绑定 - 选项行交互设计:整行点击 + 选中高亮背景 + 文字颜色/粗细变化
- 答案管理:
@State数组统一存储 +slice()不可变更新 - 自动评分:选项分值绑定 + 总分累加 + 四级健康等级判定
- 完成度控制:
isComplete()检测 + 按钮样式/行为条件切换 - 双状态切换:答题模式 vs 结果模式 + 重新评估重置
- 对象字面量约束:返回值类型必须使用显式声明的 class(
HealthLevel)
单选是移动表单的基础组件。一个好的单选体验不仅仅是"能点一个圆"——它需要清晰的选中视觉反馈、足够的点击热区、组间独立互不干扰的互斥逻辑和流畅的答案管理。ArkUI 的 Radio 组件提供了简洁的 value + group API,让开发者将注意力集中在问题的组织、答案的存储和评分的计算上。
更多推荐



所有评论(0)