发布时间:2026/7/15 20:12:38
HarmonyOS7 ArkUI 实战:Button 类型、主题色、Loading 与禁用状态全解析 文章目录前言代码讲解页面入口怎么安排数据模型先稳住状态变量控制页面反馈布局代码不要从样式开始读Builder 和方法承担复用使用方式完整代码关键代码解析interface 不是摆设State 是交互的开关列表和卡片要靠数据驱动事件回调要短一点样式参数别急着抽常量总结前言按钮组件很多项目都会随手写但 loading、禁用和主题切换这些细节决定体验。我建议把这篇当成一个可以直接改造的 ArkUI 页面模板来看。先跑起来确认页面展示和交互都正常再把里面的模拟数据替换成接口数据最后再调整颜色、间距和业务字段。这样改起来比较稳不容易一边改样式一边把状态逻辑弄乱。这篇文章会按「页面意图、数据结构、状态流转、布局组织、交互细节」这条线来读代码。你不用从第一行样式硬看到最后先抓住主线后面的细节会轻松很多。代码讲解页面入口怎么安排代码里的入口组件是ButtonStatesShowcasePage。如果你把它当成单独页面预览保留Entry就可以如果要塞进现有工程的路由体系通常会去掉Entry再由外层页面或路由模块统一管理。这个习惯很重要。一个项目里入口页面太多预览时容易混后期拆组件也会麻烦。我的做法是案例阶段保留入口接业务时只保留真正需要被路由访问的页面。数据模型先稳住本案例涉及的数据模型主要有ButtonDemo、Theme_kolk。这些interface的作用不是为了“看起来规范”而是给 UI 层划边界。比如列表项需要标题、描述、颜色、选中态就应该在模型里说清楚。后续从接口拿数据时可以在请求层做字段映射别让后端字段名直接污染页面代码。如果你准备接真实业务建议把模拟数组替换成接口返回后的 ViewModel。页面只关心自己要渲染什么不关心接口原始字段长什么样。状态变量控制页面反馈本案例里的关键状态包括isLoading、loadingText、clickCount、currentTheme。State的职责是让 UI 对用户操作有反馈。比如选中某个卡片、切换分类、展开内容、修改输入值、控制加载中状态这些都应该由状态驱动。状态一变相关 UI 自动刷新这也是 ArkUI 声明式开发最核心的体验。这里有个容易踩的坑不要把所有数据都塞进State。静态配置用普通数组就够了只有用户会改、接口会更新、页面需要重新渲染的数据才适合放进状态里。布局代码不要从样式开始读这个页面里比较关键的组件有Row横向排列、左右分栏、按钮组或卡片行都依赖它来控制水平方向的节奏。Column纵向组织标题、内容、操作区适合搭页面主骨架。Scroll用于长内容容器锚点跳转和返回顶部都离不开滚动控制。Button按钮案例要关注 loading、disabled、主题色和点击反馈。读布局时我更建议先找大容器再看内部如何拆块。比如一个页面通常会先用Column纵向放标题区、内容区、操作区内容区里再用Row、Grid、List或其他容器细分。这样看代码层级会比逐行看.padding()、.fontSize()清楚得多。如果页面显示错位优先检查容器关系而不是马上改像素。很多问题不是某个.margin()不对而是外层容器的宽高、滚动方向、权重分配或对齐方式没想清楚。Builder 和方法承担复用这篇没有额外拆出 Builder页面结构主要集中在build()中。辅助方法主要有getCurrentThemeColor、doLoadingAction。Builder更适合复用 UI 片段比如卡片、列表行、头部区域、按钮组。普通方法更适合放计算逻辑比如筛选列表、统计数量、切换状态、生成颜色。两者分清楚后页面会干净很多。我不建议把所有东西都塞进build()。刚开始代码少还行需求一多build()会变成几百行后面改一个按钮都要找半天。使用方式把下面的完整代码放到 ArkTS 页面文件里即可运行。文章中的组件名已经是语义化命名不包含编号式案例名称接路由时直接使用ButtonStatesShowcasePage就行。如果你要改成业务页面优先改三处数据模型、状态变量、事件方法。样式可以后调先保证数据流和交互是通的。完整代码// ButtonStatesShowcasePage: 按钮组件全解析 - Button 各种类型与状态 // 知识点: Button、ButtonType、stateEffect、loading状态、图文混排按钮 interface ButtonDemo { demoId: number demoTitle: string demoDesc: string } interface Theme_kolk { key: string, label: string, color: string, textColor: string } Entry Component struct ButtonStatesShowcasePage { State isLoading: boolean false State loadingText: string 提交 State clickCount: number 0 State currentTheme: string primary private themes:Theme_kolk[] [ { key: primary, label: 主要, color: #007DFF, textColor: #FFFFFF }, { key: success, label: 成功, color: #10B981, textColor: #FFFFFF }, { key: warning, label: 警告, color: #F59E0B, textColor: #FFFFFF }, { key: danger, label: 危险, color: #EF4444, textColor: #FFFFFF }, { key: info, label: 信息, color: #6366F1, textColor: #FFFFFF }, ] getCurrentThemeColor(): string { return this.themes.find(t t.key this.currentTheme)?.color ?? #007DFF } doLoadingAction() { if (this.isLoading) return this.isLoading true this.loadingText 提交中... setTimeout(() { this.isLoading false this.loadingText 提交成功 ✓ setTimeout(() { this.loadingText 提交 }, 2000) }, 2000) } build() { Column({ space: 0 }) { // 顶部 Row({ space: 0 }) { Text(Button 按钮组件) .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) } .width(100%) .height(52) .padding({ left: 16, right: 16 }) .backgroundColor(#FFFFFF) .border({ width: { bottom: 1 }, color: #F0F0F0 }) Scroll() { Column({ space: 12 }) { // 一、ButtonType 类型 Column({ space: 10 }) { Text(一、ButtonType 按钮形状) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) Row({ space: 12 }) { Button(Normal) .type(ButtonType.Normal) .fontSize(14) .backgroundColor(#007DFF) .borderRadius(8) .layoutWeight(1) .height(44) Button(Capsule) .type(ButtonType.Capsule) .fontSize(14) .backgroundColor(#007DFF) .layoutWeight(1) .height(44) Button(Circle) .type(ButtonType.Circle) .fontSize(14) .backgroundColor(#007DFF) .width(44) .height(44) } .width(100%) } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) // 二、按钮尺寸 Column({ space: 10 }) { Text(二、按钮尺寸与填充样式) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) // 大号 Button(大号按钮) .type(ButtonType.Capsule) .width(100%) .height(54) .fontSize(17) .fontWeight(FontWeight.Bold) .backgroundColor(#007DFF) // 中号 Row({ space: 12 }) { Button(中号按钮) .type(ButtonType.Capsule) .layoutWeight(1) .height(44) .fontSize(15) .backgroundColor(#007DFF) Button(中号按钮) .type(ButtonType.Capsule) .layoutWeight(1) .height(44) .fontSize(15) .backgroundColor(#FFFFFF) .fontColor(#007DFF) .border({ width: 1, color: #007DFF }) } .width(100%) // 小号 Row({ space: 8 }) { Button(小号) .type(ButtonType.Capsule) .height(32) .fontSize(13) .padding({ left: 14, right: 14 }) .backgroundColor(#007DFF) Button(小号) .type(ButtonType.Capsule) .height(32) .fontSize(13) .padding({ left: 14, right: 14 }) .backgroundColor(#FFFFFF) .fontColor(#007DFF) .border({ width: 1, color: #007DFF }) Button(小号) .type(ButtonType.Capsule) .height(32) .fontSize(13) .padding({ left: 14, right: 14 }) .backgroundColor(#F0F0F0) .fontColor(#666666) } } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) // 三、主题色按钮 Column({ space: 10 }) { Text(三、主题色切换) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) Row({ space: 8 }) { ForEach(this.themes, (theme: Theme_kolk) { Text(theme.label) .fontSize(13) .fontColor(this.currentTheme theme.key ? #FFFFFF : theme.color) .fontWeight(FontWeight.Medium) .padding({ left: 12, right: 12, top: 6, bottom: 6 }) .backgroundColor(this.currentTheme theme.key ? theme.color : transparent) .border({ width: 1, color: theme.color }) .borderRadius(16) .onClick(() { this.currentTheme theme.key }) }, (theme: Theme_kolk) theme.key) } Button(当前主题按钮) .type(ButtonType.Capsule) .width(100%) .height(50) .fontSize(15) .fontWeight(FontWeight.Bold) .backgroundColor(this.getCurrentThemeColor()) .animation({ duration: 300, curve: Curve.EaseOut }) } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) // 四、图标按钮 Column({ space: 10 }) { Text(四、图标 文字按钮) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) Row({ space: 12 }) { Button() { Row({ space: 6 }) { Text().fontSize(16) Text(搜索).fontSize(14).fontColor(#FFFFFF) } } .type(ButtonType.Capsule) .backgroundColor(#007DFF) .height(44) .layoutWeight(1) Button() { Row({ space: 6 }) { Text().fontSize(16) Text(分享).fontSize(14).fontColor(#10B981) } } .type(ButtonType.Capsule) .backgroundColor(#FFFFFF) .height(44) .border({ width: 1, color: #10B981 }) .layoutWeight(1) } .width(100%) Row({ space: 10 }) { ForEach([❤️, ⭐, , ↗️, ], (emoji: string) { Button() { Column({ space: 2 }) { Text(emoji).fontSize(20) } } .type(ButtonType.Circle) .width(48) .height(48) .backgroundColor(#F5F5F5) }, (emoji: string) emoji) } .width(100%) } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) // 五、Loading 加载状态 Column({ space: 10 }) { Text(五、Loading 加载状态) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) Button(this.loadingText) .type(ButtonType.Capsule) .width(100%) .height(50) .fontSize(16) .backgroundColor(this.isLoading ? #BBBBBB : (this.loadingText.includes(✓) ? #10B981 : #007DFF)) .stateEffect(!this.isLoading) .onClick(() { this.doLoadingAction() }) Text(点击按钮模拟2秒加载状态加载期间禁止重复点击) .fontSize(12) .fontColor(#AAAAAA) .width(100%) } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) // 六、禁用状态 Column({ space: 10 }) { Text(六、禁用状态对比) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(#1A1A1A) .width(100%) Row({ space: 12 }) { Button(正常状态) .type(ButtonType.Capsule) .layoutWeight(1) .height(44) .fontSize(14) .backgroundColor(#007DFF) .enabled(true) Button(禁用状态) .type(ButtonType.Capsule) .layoutWeight(1) .height(44) .fontSize(14) .backgroundColor(#007DFF) .enabled(false) .opacity(0.4) } .width(100%) Text(已点击次数${this.clickCount}) .fontSize(13) .fontColor(#888888) .width(100%) Button() { Row({ space: 6 }) { Text(➕).fontSize(16) Text(计数 1).fontSize(14).fontColor(#FFFFFF) } } .type(ButtonType.Capsule) .width(100%) .height(44) .backgroundColor(#007DFF) .onClick(() { this.clickCount }) } .width(100%) .padding(16) .backgroundColor(#FFFFFF) .borderRadius(12) } .padding({ left: 12, right: 12, top: 12, bottom: 24 }) } .scrollBar(BarState.Off) .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#F5F6FA) } }关键代码解析interface不是摆设很多新手写 ArkUI 页面时会直接在ForEach里使用一堆临时对象。页面小的时候没问题但字段一多就很难知道每个字段到底用于哪里。本案例把数据结构提前声明出来读代码时能很快判断每个 UI 区块依赖哪些字段。后续如果接接口也建议保留这层结构。接口可以变页面模型尽量稳定。这样页面不会因为后端多返回一个字段、少返回一个字段就跟着大改。State是交互的开关本案例的交互反馈都围绕状态展开。点击、切换、输入、刷新这类行为本质上都是修改某个状态值然后让 UI 重新计算显示结果。写这类代码时有一个简单判断如果某个值变化后页面应该立刻变那它大概率应该是状态如果它只是配置项、静态文案、固定颜色就不要放进状态。列表和卡片要靠数据驱动只要页面里出现重复结构就应该优先想到数据数组加ForEach。这样新增一项、删除一项、调整顺序都只动数据不用复制粘贴一段 UI。这个案例的完整代码里已经把主要内容抽成数组或方法。你可以试着多加一条数据看页面是否能自然渲染出来。如果能说明结构是健康的如果加一条就要改很多 UI说明组件拆分还不够。事件回调要短一点.onClick()、onChange()这类回调里可以直接改状态但不建议堆太多逻辑。简单切换可以写在回调里复杂逻辑最好拆成方法。这样做有两个好处一是读页面结构时不会被业务逻辑打断二是后面要加埋点、请求接口、异常处理时有明确的位置可以改。样式参数别急着抽常量这个案例里有不少颜色、间距、圆角、字号。学习阶段直接写在组件链上反而更容易看出效果。等你确定页面风格稳定后再把主题色、通用间距、卡片圆角提到统一常量里。过早抽象会让示例代码变绕。先让页面清楚再谈工程化这是我比较推荐的顺序。总结这个案例真正值得学的不只是某个 ArkUI 组件怎么写而是一个页面如何从数据走到状态再走到布局和交互。你可以按这个顺序改造先替换数据模型再确认状态更新是否正确接着调整布局容器最后打磨样式细节。只要这条主线不乱页面规模变大也不会失控。如果后续继续扩展我会优先把可复用的卡片、列表行、筛选栏或控制区拆成独立组件。这样主页面只保留数据和流程代码会更接近真实项目里的写法。

相关新闻

2026/7/15 20:12:38

GEO内容优化策略:从语义结构到知识图谱的技术路径

GEO内容优化策略:从语义结构到知识图谱的技术路径 传统SEO关注关键词布局和外链建设,而GEO内容优化策略的技术重心在于语义结构化和知识图谱关联。这种转变源于生成式引擎对内容的理解方式发生了根本性变化。内容优化不再面向排名算法,而是面…

2026/7/15 20:07:38

TVA具身智能范式研究进展(18)

前沿技术探索:AI智能体视觉(TVA,Transformer-based Vision Agent)是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术,是集深度强化学习(DRL)、卷积神经网络(CNN…

2026/7/15 21:17:44

HEAD请求实战指南:从原理到性能优化的深度探索

1. HEAD请求的本质与核心价值第一次听说HEAD请求时,你可能会有这样的疑问:既然已经有了GET请求,为什么还需要一个不返回响应体的请求方法?这就像去餐厅吃饭时,服务员先给你看菜单(HEAD请求)&…

2026/7/15 21:17:44

python 连接拓竹打印机

目录 怎么查看序列号: 连接代码:conn_tool.py 怎么查看序列号: 机器屏幕中:设置,设备和序列号,序列号,里面有序列号 连接代码: conn_tool.py 可以获取打印进度。 import time import bambulabs_api as blclass BambuPrinter:def __init__(self, ip, access_code, se…

2026/7/15 21:17:44

电子元器件实战检测:从基础工具到进阶技巧

1. 电子元器件检测入门:工具准备与基础操作 刚入行那会儿,我最常被问到的就是"这块电路板不工作了,到底哪个元件坏了?"说实话,第一次面对密密麻麻的元器件时,我也是一头雾水。但经过这些年的摸爬…

2026/7/15 21:17:44

2026陶瓷行业观察:国内十大瓷砖品牌概况一览

瓷砖是家装硬装里使用率较高的基础建材,广泛用于客厅、厨房、卫生间、阳台等空间。随着家装审美升级,岩板、哑光砖、大板等品类逐步普及,陶瓷行业产品款式持续更新。国内十大主流瓷砖品牌基础信息一览1、鹰牌陶瓷品牌始创于1974年&#xff0c…

2026/7/15 21:12:42

5.1V稳压管输出电压偏差的七大原因分析与解决方案

为什么你精心挑选的5.1V稳压管,实际测量时电压却总是达不到5.1V?这个问题困扰着无数电子工程师和电子爱好者。很多人第一反应是“买到假货了”,但真相往往比这复杂得多。稳压二极管的工作原理看似简单——利用反向击穿特性来稳定电压&#xf…

2026/7/15 17:04:06

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾在网易云音乐下载了心爱的歌曲&#…

2026/7/14 19:23:19

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境随着新能源汽车行业的快速发展,充电通信协议的标准化和测试验证变得尤为重要。GB/T 27930-2023作为中国智能充电协议的最新版本,对充电机与电动汽车之间的通信提出了更严格…

2026/7/14 18:30:06

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡 【免费下载链接】rtl8852be Realtek Linux WLAN Driver for RTL8852BE 项目地址: https://gitcode.com/gh_mirrors/rt/rtl8852be 还在为Linux系统无法识别RTL8852BE Wi-Fi 6网卡而烦恼吗?&#x1f…

2026/7/15 0:08:29

【LINUX】驱动

【LINUX驱动】【字符设备】【中断】【Platform】【网课 设备树】【GPIO】【PINCTRL】【INPUT】【IIC】【SPI】【网络驱动】【屏幕驱动】【一 设备树】【二 内核模块编译】【三 基本驱动框架】【四 Platform总线设备驱动框架】【五 驱动子系统】【六 综合】

2026/7/15 0:08:29

【1982-2026】全国高精度建筑轮廓|村级精度|SHP矢量

🔍 数据简介 本次分享1982-2026年全国村级精度建筑轮廓矢量数据,覆盖全国各省市区县,到村级别精细,为2026年最新实时采集成果,非网传仅60/77个城市的老旧数据。 数据含带高度/不带高度双版本,单体建筑边界精…

2026/7/15 0:08:29

【1975-2026】全国水系水路数据|河流/水库/运河|SHP矢量

🔍 数据简介 本次分享1975-2026年全国高精度水系水路矢量数据,覆盖全国全域,包含河流、水系、水库、运河、湿地、冰川、沟渠等全类别水文要素。 数据集包含双层矢量图层,字段分类清晰、要素齐全,支持2013-2026逐年完整…

2026/7/15 13:36:32

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…