前言

为一篇 HarmonyOS7 技术教程绘制手绘笔记风信息图,主题是“弹窗里做选项列表”。画面要表现标

很多设置类交互都有一个共性:不是让用户输入,而是让用户选。模式切换、清晰度选择、导出格式、排序规则,这些都更适合列表式选择弹窗,而不是文本输入框。

标准确认框装不下一个完整列表,这时候 CustomDialog 的价值就出来了。这一页案例做的,就是一个很常见的单选弹窗:展示多个模式,用户点中一项,再确认提交。

这个案例核心在“选择态”

为 HarmonyOS7 ArkUI 教程绘制一张手绘框架图,主题是“选中态 selectedInd

页面本身不复杂,但有一个关键状态:

@State selectedIndex: number = -1

这说明整个弹窗的重点不在展示列表,而在维护“当前到底选中了谁”。只要这个状态设计清楚,单选、多选、默认值、回显,全都能往下扩。

完整代码

为一篇讲解 HarmonyOS7 CustomDialog 单选列表实现的技术文章绘制手绘流程图。内

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 塞进弹窗这么简单,而是把选择状态、视觉反馈和最终确认流程组织清楚。只要这三件事站住了,选项型弹窗会非常顺手。

这一页已经把骨架搭出来了,后面你要做模式切换、规则选择,基本都能沿着这个结构继续走。

Logo

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

更多推荐