发布时间:2026/9/5 14:54:46
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/9/5 11:16:43

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

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

2026/8/29 23:17:49

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

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

2026/9/5 14:50:56

如何辨别不可靠的论文降重和文本改写服务

在毕业论文的撰写过程中,我们常常会面临一个困惑:如何选择一个可靠的论文降重或文本改写服务?我在这个过程中踩过不少坑,今天想和大家分享一些经验,帮助大家避免不必要的麻烦。 为什么降重服务如此重要 毕业论文的查…

2026/9/5 14:50:56

STM32省IO读取旋转开关与Modbus浮点传输实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 14:50:56

视觉语言模型VLM学习路线:从架构解析到微调部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 14:50:56

MODBUS协议调试实战:从帧格式到CRC校验的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 14:45:56

旅游网站前端开发实战:HTML语义化、CSS Grid/Flex布局与JS模块化

简介:这是一套面向网页设计初学者与前端开发者的旅游行业网站HTML源码,聚焦HTML结构搭建、CSS响应式布局及JavaScript交互实现三大核心技能,适用于课程实训、毕业设计或二次开发实践。资源共84个文件,包含14个完整HTML页面、9个JS…

2026/9/5 2:46:54

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/5 2:46:52

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/5 2:44:34

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/5 0:04:47

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流在大模型流式输出(Streaming)与智能体实时推流的架构中,生产环境中经常出现一种“上下游生产消费速率严重失衡”的极端情况: 生产端极速产出:大模型…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…