HarmonyOS7 弹窗里做选项列表怎么写:CustomDialogOptionList 实战说明
文章目录
前言

很多设置类交互都有一个共性:不是让用户输入,而是让用户选。模式切换、清晰度选择、导出格式、排序规则,这些都更适合列表式选择弹窗,而不是文本输入框。
标准确认框装不下一个完整列表,这时候 CustomDialog 的价值就出来了。这一页案例做的,就是一个很常见的单选弹窗:展示多个模式,用户点中一项,再确认提交。
这个案例核心在“选择态”

页面本身不复杂,但有一个关键状态:
@State selectedIndex: number = -1
这说明整个弹窗的重点不在展示列表,而在维护“当前到底选中了谁”。只要这个状态设计清楚,单选、多选、默认值、回显,全都能往下扩。
完整代码

import { DEMO_CARD_COLOR, DEMO_THEME_COLOR } from './types'
@Builder
function emptyDialogBuilder() {
// placeholder - controller is injected by framework at runtime
}
@CustomDialog
struct ListDialog {
controller: CustomDialogController = new CustomDialogController({
builder: wrapBuilder(emptyDialogBuilder),
autoCancel: false
})
options: string[] = ['选项A - 标准模式', '选项B - 极速模式', '选项C - 省电模式', '选项D - 自定义模式']
@State selectedIndex: number = -1
build() {
Column() {
Text('选择模式')
.fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 16 })
List() {
ForEach(this.options, (item: string, index: number) => {
ListItem() {
Row() {
Text(item)
.fontSize(15)
.fontColor(this.selectedIndex === index ? DEMO_THEME_COLOR : '#333333')
.layoutWeight(1)
if (this.selectedIndex === index) {
Text('✓')
.fontSize(18)
.fontColor(DEMO_THEME_COLOR)
.fontWeight(FontWeight.Bold)
}
}
.width('100%')
.padding(14)
.borderRadius(8)
.backgroundColor(this.selectedIndex === index ? '#E8F4FF' : '#F8F8F8')
}
.margin({ bottom: 8 })
.onClick(() => {
this.selectedIndex = index
})
})
}
.width('100%')
.height(240)
Button('确认选择')
.width('100%').height(44)
.backgroundColor(DEMO_THEME_COLOR).borderRadius(10)
.fontColor('#FFFFFF').fontSize(15)
.margin({ top: 16 })
.onClick(() => {
if (this.selectedIndex >= 0) {
console.info(`选中: ${this.options[this.selectedIndex]}`)
}
this.controller.close()
})
}
.padding(24)
}
}
@Entry
@Component
struct CustomDialogOptionList {
@State isShow: boolean = true
dialogController: CustomDialogController = new CustomDialogController({
builder: ListDialog({}),
autoCancel: true,
alignment: DialogAlignment.Center,
customStyle: true
})
build() {
Column() {
if (this.isShow) {
Column() {
Text('弹窗内嵌列表')
.fontSize(16).fontWeight(FontWeight.Bold).margin({ bottom: 12 })
Text('CustomDialog 中可以嵌入 List 组件,实现单选或多选功能,常用于选项配置场景。')
.fontSize(14).fontColor('#666666').margin({ bottom: 24 })
Button('打开列表弹窗')
.width('100%').height(48)
.backgroundColor('#FF6B9D').borderRadius(12)
.fontColor('#FFFFFF').fontSize(16)
.onClick(() => { this.dialogController.open() })
}
.width('100%').padding(24).backgroundColor(DEMO_CARD_COLOR).borderRadius(12)
}
Text('CustomDialogOptionList - 模式选择弹窗')
.fontSize(12).fontColor('#999999').margin({ top: 12 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F6FA')
.padding(16)
}
}
选项数据为什么直接放数组里
案例先定义了一个选项数组:
options: string[] = ['选项A - 标准模式', '选项B - 极速模式', '选项C - 省电模式', '选项D - 自定义模式']
对于这种简单单选场景,这种方式完全够用。它让渲染结构很清晰,也便于你后面改成接口返回数据或者参数传入。
如果以后每个选项需要图标、描述、副标题,那就升级成对象数组;但入门阶段先用字符串数组是合理的。
selectedIndex 为什么比直接存字符串更适合这个案例
@State selectedIndex: number = -1
用索引做选择态有两个好处。
第一,判断当前项是否选中很方便。
第二,后面取完整选项内容时也直接:
this.options[this.selectedIndex]
对于列表选择场景,索引常常比存一整段文本更顺手。
列表项的选中态做得很典型
每个选项行的样式都绑定到了 selectedIndex:
.fontColor(this.selectedIndex === index ? DEMO_THEME_COLOR : '#333333')
.backgroundColor(this.selectedIndex === index ? '#E8F4FF' : '#F8F8F8')
同时,选中项还会显示一个勾号:
if (this.selectedIndex === index) {
Text('✓')
}
这是非常典型的单选交互设计。用户不需要猜你到底选中了什么,颜色、背景和图标都在帮他确认当前状态。
点击列表项时,真正发生了什么
逻辑非常干净:
.onClick(() => {
this.selectedIndex = index
})
也就是说,单选弹窗的本质不是“点按钮提交”,而是先在列表中建立当前选择,再通过确认按钮完成动作。这个两段式流程比“点一下直接关闭”更适合一些需要用户确认的配置场景。
为什么确认按钮不直接放在每一项里
因为这页案例更偏“配置类选择”,不是“即时执行类选择”。
如果你点一个模式就立刻切换,用户可能会误触;而现在这种结构是:先选,再确认,更稳,也更适合带风险或影响较大的配置项。
List 放在弹窗里有什么现实价值
这个模式非常适合以下场景:
- 模式切换
- 清晰度选择
- 排序规则选择
- 导出格式选择
- 账号切换入口
这类需求如果跳完整页面,往往显得重;如果挤在普通确认框里,又放不下。弹窗列表刚好卡在一个很舒服的位置。
这个案例下一步最值得补什么
真实项目里,通常会继续加这些能力:
- 默认选中某一项
- 打开弹窗时回显当前设置
- 确认后把结果回调给父页面
- 未选中时禁用确认按钮
- 支持多选模式
也就是说,这个案例已经把基础结构搭好,后面主要是沿着选择态和结果回传继续扩。
容易踩的坑
第一个坑,是没有选中反馈,用户点了以后根本不知道当前哪项生效。
第二个坑,是列表太长却不给固定高度,弹窗会被撑得很难看。
第三个坑,是确认按钮在未选中时也可点击,但又没有兜底反馈。
写在最后
列表型 CustomDialog 的核心,不是把 List 塞进弹窗这么简单,而是把选择状态、视觉反馈和最终确认流程组织清楚。只要这三件事站住了,选项型弹窗会非常顺手。
这一页已经把骨架搭出来了,后面你要做模式切换、规则选择,基本都能沿着这个结构继续走。
更多推荐


所有评论(0)