HarmonyOS ArkUI Button 组件深度解析:类型、状态与自定义样式

系列:鸿蒙 HarmonyOS 6.1 新特性实战 · 第 33 篇

Button 是用户交互的核心入口。ArkUI 的 Button 组件提供了三种内置形状、完整的状态反馈机制、加载状态嵌入和丰富的自定义能力。本篇通过可运行代码系统演示这些特性。

运行效果

初始状态,展示各种 Button 类型:

点击触发 Loading 加载状态:

一、ButtonType 三种形状

typescript 复制代码
// Normal:默认直角矩形
Button('确认').type(ButtonType.Normal)

// Capsule:圆角胶囊(推荐,视觉最友好)
Button('提交').type(ButtonType.Capsule)

// Circle:正圆形,常用于浮动按钮
Button('+').type(ButtonType.Circle).width(56).height(56)

注意 :ButtonType 不设置时默认为 Capsule。

二、状态反馈:stateEffect

stateEffect 控制按下时是否有高亮反馈(默认开启):

typescript 复制代码
// 开启状态反馈(默认)
Button('有反馈').stateEffect(true)

// 关闭状态反馈
Button('无反馈').stateEffect(false)

// 禁用状态
Button('禁用按钮').enabled(false).opacity(0.4)

禁用按钮要同时设置 enabled(false) 和 opacity() 来提供视觉反馈。

三、内嵌 LoadingProgress 实现加载状态

Button 支持在内部嵌套任意子组件,实现加载按钮效果:

typescript 复制代码
@State isLoading: boolean = false

Button() {
  if (this.isLoading) {
    Row({ space: 8 }) {
      LoadingProgress().width(20).height(20).color('#fff')
      Text('提交中...').fontSize(14).fontColor('#fff')
    }
  } else {
    Text('提交').fontSize(14).fontColor('#fff')
  }
}
.width('100%').height(44)
.backgroundColor(this.isLoading ? '#aaa' : '#0066ff')
.onClick(() => {
  if (!this.isLoading) {
    this.isLoading = true
    setTimeout(() => { this.isLoading = false }, 2000)
  }
})

四、透明/描边自定义样式

通过 .backgroundColor('transparent') 和 .border() 实现描边按钮:

typescript 复制代码
// 描边按钮
Button('查看详情')
  .type(ButtonType.Normal)
  .backgroundColor('transparent')
  .border({ width: 1, color: '#0066ff' })
  .fontColor('#0066ff')
  .borderRadius(8)

// 危险操作按钮
Button('删除').backgroundColor('#e74c3c').fontColor('#fff').borderRadius(8)

// 次要操作按钮(灰色)
Button('取消').backgroundColor('#f5f5f5').fontColor('#666').borderRadius(8)

五、完整代码

typescript 复制代码
@Entry
@Component
struct Index {
  @State isLoading: boolean = false

  build() {
    Scroll() {
      Column({ space: 20 }) {
        Text('Button 组件深度解析')
          .fontSize(22).fontWeight(FontWeight.Bold).fontColor('#1a1a1a')

        // 1. ButtonType 三种形状
        Column({ space: 10 }) {
          Text('一、ButtonType 形状').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#333')
          Row({ space: 12 }) {
            Button('Normal').type(ButtonType.Normal)
              .backgroundColor('#0066ff').fontColor('#fff').borderRadius(4)
            Button('Capsule').type(ButtonType.Capsule)
              .backgroundColor('#0066ff').fontColor('#fff')
            Button('+').type(ButtonType.Circle)
              .width(48).height(48).fontSize(24)
              .backgroundColor('#0066ff').fontColor('#fff')
          }
          .width('100%').justifyContent(FlexAlign.SpaceEvenly)
        }
        .padding(12).backgroundColor('#fff').borderRadius(8).width('100%')
        .border({ width: 1, color: '#e0e0e0' }).alignItems(HorizontalAlign.Start)

        // 2. Loading 状态
        Column({ space: 10 }) {
          Text('二、加载状态').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#333')
          Button() {
            if (this.isLoading) {
              Row({ space: 8 }) {
                LoadingProgress().width(20).height(20).color('#fff')
                Text('提交中...').fontSize(14).fontColor('#fff')
              }
            } else {
              Text('提交').fontSize(14).fontColor('#fff')
            }
          }
          .width('100%').height(44)
          .backgroundColor(this.isLoading ? '#999' : '#0066ff')
          .onClick(() => {
            if (!this.isLoading) {
              this.isLoading = true
              setTimeout(() => { this.isLoading = false }, 2000)
            }
          })
        }
        .padding(12).backgroundColor('#fff').borderRadius(8).width('100%')
        .border({ width: 1, color: '#e0e0e0' }).alignItems(HorizontalAlign.Start)

        // 3. 自定义样式
        Column({ space: 10 }) {
          Text('三、自定义样式').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#333')
          Row({ space: 8 }) {
            Button('描边')
              .type(ButtonType.Normal).borderRadius(8)
              .backgroundColor('transparent')
              .border({ width: 1, color: '#0066ff' })
              .fontColor('#0066ff').layoutWeight(1)
            Button('危险')
              .type(ButtonType.Normal).borderRadius(8)
              .backgroundColor('#e74c3c').fontColor('#fff').layoutWeight(1)
            Button('禁用')
              .type(ButtonType.Normal).borderRadius(8)
              .backgroundColor('#f0f0f0').fontColor('#aaa')
              .enabled(false).layoutWeight(1)
          }
          .width('100%')
        }
        .padding(12).backgroundColor('#fff').borderRadius(8).width('100%')
        .border({ width: 1, color: '#e0e0e0' }).alignItems(HorizontalAlign.Start)

        // 4. stateEffect
        Column({ space: 10 }) {
          Text('四、stateEffect 按压反馈').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#333')
          Row({ space: 12 }) {
            Button('有反馈').stateEffect(true)
              .backgroundColor('#0066ff').fontColor('#fff').layoutWeight(1)
            Button('无反馈').stateEffect(false)
              .backgroundColor('#0066ff').fontColor('#fff').layoutWeight(1)
          }
          .width('100%')
        }
        .padding(12).backgroundColor('#fff').borderRadius(8).width('100%')
        .border({ width: 1, color: '#e0e0e0' }).alignItems(HorizontalAlign.Start)
      }
      .padding(20).width('100%')
    }
    .width('100%').height('100%').backgroundColor('#f5f5f5')
  }
}

API 速查

属性/方法 说明
.type(ButtonType.Capsule) 形状:Normal / Capsule / Circle
.stateEffect(true) 是否显示按压高亮
.enabled(false) 禁用点击
Button(){ ... } 内嵌子组件(图标、Loading等)
.backgroundColor('transparent') 透明背景(做描边按钮)
.border({width, color}) 描边

小结

  • Capsule 是默认首选:视觉效果最符合现代 App 规范
  • 加载状态靠嵌套:Button 内嵌 LoadingProgress + Text Row 实现,不需要三方库
  • 禁用要双重处理 :enabled(false) + 视觉上降低对比度(opacity 或灰色背景)
  • Circle 用于 FAB :浮动操作按钮场景,配合 position 绝对定位

上一篇:Image 组件完全指南 | 下一篇:Checkbox、Radio、Toggle 选择组件实战

相关推荐
code2cat1 小时前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
掘金者阿豪2 小时前
SQL Server数据迁移完成后,凌晨的数据库告警该怎么查?
前端·后端
鶴哥只手遮天2 小时前
AFSIM 2.9.0 深度解析·进阶卷第 1 章 插件源码精读:16 个 WSF 插件的架构与实现
后端
愚农搬码2 小时前
低代码表单与在线问卷不是一回事:产品定位和技术边界
后端
武子康2 小时前
固定时间步已经开了,为什么游戏回放还会走出不同位置?
人工智能·后端·agent
70后写java的老码农2 小时前
换模型只改三行配置:LangChain4j + Spring Boot 3.5 实战,一次分别接入 9 家模型服务
后端·ai编程
愚农搬码2 小时前
2026 年开源表单设计器选型:哪个更好用?
后端
vipxieliang3 小时前
ValidX 正则表达式预编译优化:Pattern 复用策略
java·spring boot·后端
vipxieliang3 小时前
OpenTelemetry 生产实践:Skyscanner 24 集群管理经验与 Mastodon 小团队方案
后端
明月_清风3 小时前
为什么 Skill 很难商业化?Bot 才是 AI 时代真正的商业机会
人工智能·后端·agent