单选是表单中最基础的交互。性别选择、支付方式、评分档位、问卷答案——所有这些"多选一"的场景都需要单选按钮。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 单项选择组件的使用方法。核心知识点包括:

  1. Radio 基础 APIvalue + group 组内互斥 + .checked() 状态绑定
  2. 选项行交互设计:整行点击 + 选中高亮背景 + 文字颜色/粗细变化
  3. 答案管理@State 数组统一存储 + slice() 不可变更新
  4. 自动评分:选项分值绑定 + 总分累加 + 四级健康等级判定
  5. 完成度控制isComplete() 检测 + 按钮样式/行为条件切换
  6. 双状态切换:答题模式 vs 结果模式 + 重新评估重置
  7. 对象字面量约束:返回值类型必须使用显式声明的 class(HealthLevel

单选是移动表单的基础组件。一个好的单选体验不仅仅是"能点一个圆"——它需要清晰的选中视觉反馈、足够的点击热区、组间独立互不干扰的互斥逻辑和流畅的答案管理。ArkUI 的 Radio 组件提供了简洁的 value + group API,让开发者将注意力集中在问题的组织、答案的存储和评分的计算上。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐