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 组件怎么写而是一个页面如何从数据走到状态再走到布局和交互。你可以按这个顺序改造先替换数据模型再确认状态更新是否正确接着调整布局容器最后打磨样式细节。只要这条主线不乱页面规模变大也不会失控。如果后续继续扩展我会优先把可复用的卡片、列表行、筛选栏或控制区拆成独立组件。这样主页面只保留数据和流程代码会更接近真实项目里的写法。

相关新闻

密评——从合规基线到实战密码应用的技术路径解析

密评——从合规基线到实战密码应用的技术路径解析

1. 密评的本质:从合规检查到实战验证的范式升级商用密码应用安全评估(简称"密评")正在经历从"合规基线检查"到"有效性验证"的思维转变。过去我们常把GB/T 39786-2021标准当作一份"检查清单"&#xf…

2026/7/15 20:08:55 阅读更多 →
模电面试问题汇总七

模电面试问题汇总七

31.光耦作用1.输入输出间互相隔离,主要用于通信和控制时传输信号的隔离。2.光耦在传输信号的能有效地抑制尖脉冲和各种杂波干扰。3.光耦的响应速度极快,其响应延迟时间在 us 级,更快的有 ns级。4.光耦起到很好的安全保护作用,即使当外部设备出现故障,或者输入信号线…

2026/7/15 20:08:55 阅读更多 →
【实战指南】如何用Python绘制带置信椭圆的PCA双标图?附完整代码

【实战指南】如何用Python绘制带置信椭圆的PCA双标图?附完整代码

1. PCA双标图与置信椭圆的核心价值主成分分析(PCA)是数据降维的经典方法,但在生物信息学和多组学数据分析中,单纯展示样本点在二维空间的分布往往不够。我第一次处理基因表达数据时就踩过坑——虽然能看到样本聚类,但完…

2026/7/15 20:06:54 阅读更多 →

最新新闻

P8661日志统计题解复盘

P8661日志统计题解复盘

P8647 [蓝桥杯 2018 省 A] 日志统计 题解复盘 基本信息项目内容题目编号、来源P8647 洛谷 / 蓝桥杯 2018 年省赛 A/B/C 组训练层级B变式题 双指针 / 滑动窗口 排序知识版块滑动窗口、双指针、排序、哈希表/桶排序 解题前・关键信号识别维度分析目标、约束、底层结构目标&#…

2026/7/15 21:01:13 阅读更多 →
YOLOv11n模型压缩到2MB以下:通道剪枝、量化训练与结构重参数化的组合拳

YOLOv11n模型压缩到2MB以下:通道剪枝、量化训练与结构重参数化的组合拳

一、前言:2MB,一个让人又爱又恨的数字 兄弟们,今天聊一个让无数边缘端开发者夜不能寐的话题——如何把YOLOv11n塞进2MB的存储空间。 先看一组硬核数据。根据Ultralytics官方发布的信息,YOLO11n是YOLO11系列中最小巧的目标检测模型,拥有260万个参数,在COCO数据集上达到了…

2026/7/15 21:01:13 阅读更多 →
AI因果推断:从相关性到因果性的跨越

AI因果推断:从相关性到因果性的跨越

AI因果推断:从相关性到因果性的跨越"相关性不等于因果性"是统计学第一课,但在实际决策中,我们真正需要的是因果知识。AI因果推断致力于从数据中发现因果关系,回答"如果...会怎样"(What if&#xf…

2026/7/15 20:59:12 阅读更多 →
C++函数全解析:从基础概念到高级应用与实战技巧

C++函数全解析:从基础概念到高级应用与实战技巧

1. 函数基础:从“黑盒子”到程序基石如果你刚开始接触C,或者已经写过一些代码,那么“函数”这个概念你一定不陌生。它就像你厨房里的一个多功能料理机——你把食材(参数)放进去,按下开关(调用&a…

2026/7/15 20:55:12 阅读更多 →
API网关、微服务、Service Mesh——ChatGPT一句话讲清本质,92%工程师看完直呼“早该这么教!”

API网关、微服务、Service Mesh——ChatGPT一句话讲清本质,92%工程师看完直呼“早该这么教!”

更多请点击: https://codechina.net 第一章:API网关、微服务、Service Mesh——ChatGPT一句话讲清本质,92%工程师看完直呼“早该这么教!” API网关是系统的“统一门面”,负责流量入口的认证、限流、路由与协议转换&a…

2026/7/15 20:53:11 阅读更多 →
DeepSeek    LeetCode 3559. 给边赋权值的方案数 II Python3实现

DeepSeek LeetCode 3559. 给边赋权值的方案数 II Python3实现

根据题目要求,对于树中任意两个节点 u 和 v,路径长度为 d(边数),使路径总代价为奇数的赋值方案数为 2^(d-1)。因此核心是快速求树上两点距离。Python3 实现python class Solution:MOD 10**9 7def assignEdgeWeights(…

2026/7/15 20:53:11 阅读更多 →

日新闻

YOLO11 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

YOLO11 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

前言 本文介绍了面向红外小目标检测的时空特征融合模块——STFFM,用于增强复杂背景下目标与噪声、杂波的区分能力。该方法通过拼接空间特征与时间/运动特征,并结合通道注意力、空间注意力和残差增强机制,实现对关键语义通道与疑似目标区域的…

2026/7/15 0:01:00 阅读更多 →
YOLO26 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

YOLO26 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点

前言 本文介绍了面向复杂背景小目标检测的时空特征融合模块——STFFM。该模块通过空间分支与时间/运动分支的特征拼接,引入通道注意力和空间注意力对融合特征进行自适应筛选,并结合残差增强与通道压缩,突出目标区域、抑制背景噪声。我们将 S…

2026/7/15 0:01:00 阅读更多 →
行星减速机为什么能提高扭矩?从功率守恒到输出扭矩校核

行星减速机为什么能提高扭矩?从功率守恒到输出扭矩校核

一、为什么减速以后扭矩会增大 旋转机械的功率、转速和扭矩之间存在以下关系: T 9550 P n 其中: T为扭矩,单位Nm; P为功率,单位kW; n为转速,单位r/min。 在功率基本不变的情况下:…

2026/7/15 0:03:00 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/14 16:53:23 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/15 19:42:20 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/15 17:52:08 阅读更多 →

月新闻