[鸿蒙从零到一] ArkUI 基础组件实战:Text、Image、Button 与 TextInput

简介: 本文详解鸿蒙ArkUI四大基础组件:Text(支持多语言、样式拼接与长文本处理)、Image(资源加载、裁剪适配与网络权限)、Button(状态防重、自定义内容与交互反馈)、TextInput(状态绑定、输入限制与校验)。通过实战资料编辑页,演示声明式开发与状态驱动最佳实践

[鸿蒙从零到一] ArkUI 基础组件实战:Text、Image、Button 与 TextInput

前言

页面布局解决的是组件如何排列,基础组件解决的则是用户看到什么、输入什么以及如何触发操作。在 ArkUI 中,TextImageButtonTextInput 几乎会出现在每一个业务页面里。它们的基础用法并不复杂,但要写出稳定、易维护且体验完整的界面,还需要理解资源引用、状态驱动、输入约束和事件处理之间的关系。

本文从组件的常用属性讲起,逐步实现一个资料编辑页。示例采用 ArkTS 和 ArkUI 声明式语法,可直接放入 Stage 模型工程的页面目录中进行调整。

一、Text:不仅是显示一段文字

Text 用于展示标题、正文、提示、状态等文本内容。最简单的写法如下:

Text('欢迎使用 HarmonyOS')
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  .fontColor('#182230')

ArkUI 通过链式属性设置字号、字重、颜色和布局信息。样式写在组件之后,阅读代码时可以直接看到组件最终呈现的状态。

处理长文本

列表项、工具栏和用户信息区域通常无法容纳任意长度的文本。应主动限制行数并设置溢出策略:

Text('这是一段长度可能发生变化的用户简介')
  .fontSize(14)
  .fontColor('#667085')
  .maxLines(2)
  .textOverflow({ overflow: TextOverflow.Ellipsis })
  .lineHeight(22)

maxLines 限制最多显示的行数,TextOverflow.Ellipsis 在空间不足时显示省略号。固定高度的小区域尤其需要这组配置,否则动态内容可能挤压相邻组件。

使用资源代替硬编码文案

需要多语言适配的业务文案应放到资源文件中。例如在 resources/base/element/string.json 中声明:

{
   
  "string": [
    {
   
      "name": "profile_title",
      "value": "编辑资料"
    },
    {
   
      "name": "save",
      "value": "保存"
    }
  ]
}

页面中使用 $r 引用资源:

Text($r('app.string.profile_title'))
Button($r('app.string.save'))

这样既便于统一修改,也为后续增加其他语言资源做好准备。

拼接不同样式的文本

一段文本中存在多种样式时,可以在 Text 内使用 Span

Text() {
  Span('昵称')
    .fontColor('#344054')
  Span(' *')
    .fontColor('#D92D20')
}
.fontSize(14)

Span 适合协议链接、关键词强调和必填标记。若每一段内容都需要独立点击、复杂布局或无障碍语义,拆成多个组件会更清晰。

二、Image:正确加载并裁剪图片

Image 可以加载应用资源、像素图以及网络地址。应用内图标优先使用资源引用:

Image($r('app.media.avatar_default'))
  .width(88)
  .height(88)
  .borderRadius(44)
  .objectFit(ImageFit.Cover)

当容器宽高比例与原图不一致时,objectFit 决定图片如何适配:

  • ImageFit.Cover 保持比例并填满容器,边缘可能被裁剪,适合头像和封面。
  • ImageFit.Contain 保持比例并完整显示,容器中可能留白,适合商品图和示意图。
  • ImageFit.Fill 直接拉伸到容器尺寸,原图比例可能改变,通常不适合照片。

网络图片与权限

加载网络图片时传入 HTTPS 地址:

Image('https://example.com/images/avatar.png')
  .width(88)
  .height(88)
  .objectFit(ImageFit.Cover)
  .borderRadius(44)

同时需要在模块的 module.json5 中声明网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

生产环境还应准备占位图和加载失败状态,避免图片请求失败后留下突兀的空白。头像等固定格式媒体需要明确设置宽高,防止资源加载完成前后引发布局跳动。

三、Button:把点击事件变成明确的业务动作

文字按钮适合提交、确认等清晰命令:

Button('保存资料')
  .width('100%')
  .height(44)
  .fontSize(16)
  .backgroundColor('#0A7A5A')
  .onClick(() => {
    console.info('save profile')
  })

按钮样式应与动作的重要程度一致。一个页面通常只有一个主要操作,次要操作可以使用普通文本按钮或浅色背景,避免所有按钮都争夺注意力。

防止提交状态下重复点击

保存操作可能需要访问网络。在请求完成前禁用按钮,并给出正在处理的反馈:

@State isSaving: boolean = false

Button(this.isSaving ? '保存中...' : '保存资料')
  .enabled(!this.isSaving)
  .opacity(this.isSaving ? 0.6 : 1)
  .onClick(async () => {
    if (this.isSaving) {
      return
    }
    this.isSaving = true
    try {
      await this.saveProfile()
    } finally {
      this.isSaving = false
    }
  })

这里的 enabled 不只是视觉变化,它还阻止组件继续响应点击。finally 能确保成功或失败后都恢复状态。

自定义按钮内容

按钮需要同时展示图标与文字时,可以使用构建器形式:

Button({ type: ButtonType.Normal }) {
  Row({ space: 8 }) {
    Image($r('app.media.ic_camera'))
      .width(18)
      .height(18)
    Text('更换头像')
      .fontSize(14)
  }
}
.height(40)
.borderRadius(6)
.backgroundColor('#EEF2F6')
.fontColor('#344054')

这种形式适合需要组合内容的按钮,但点击事件仍应绑定在 Button 上,保证整个区域都可点击。

四、TextInput:让输入值进入状态系统

TextInput 用于单行输入。它通常通过 text 接收当前值,并在 onChange 中更新状态:

@State nickname: string = ''

TextInput({
  text: this.nickname,
  placeholder: '请输入昵称'
})
  .width('100%')
  .height(44)
  .fontSize(16)
  .padding({ left: 12, right: 12 })
  .backgroundColor(Color.White)
  .border({ width: 1, color: '#D0D5DD' })
  .borderRadius(6)
  .onChange((value: string) => {
    this.nickname = value
  })

@State 发生变化后,依赖它的界面会自动刷新。这就是状态驱动 UI 的基本闭环:组件展示状态,用户事件修改状态,框架根据新状态更新组件。

输入类型与长度限制

不同字段应使用匹配的输入类型和限制:

TextInput({ placeholder: '请输入手机号' })
  .type(InputType.PhoneNumber)
  .maxLength(11)

TextInput({ placeholder: '请输入邮箱' })
  .type(InputType.Email)
  .maxLength(64)

合适的输入类型会让系统提供更匹配的软键盘,但它不能代替业务校验。提交前仍需检查格式、空值和长度,并把错误信息展示在对应字段附近。

密码输入与清除按钮

密码字段可以使用 Password 类型,普通输入框则可按需显示清除按钮:

TextInput({ placeholder: '请输入密码' })
  .type(InputType.Password)
  .showPasswordIcon(true)

TextInput({ text: this.nickname, placeholder: '请输入昵称' })
  .showCancelButton({ style: CancelButtonStyle.CONSTANT })
  .onChange((value: string) => {
    this.nickname = value
  })

是否持续显示清除按钮要结合页面密度决定。表单较紧凑时,可以使用随输入状态出现的默认策略。

五、组合实现资料编辑页

下面把四类组件组合成完整页面。页面支持编辑昵称和邮箱、显示剩余字数、进行基础校验,并在保存过程中禁用按钮。

@Entry
@Component
struct ProfileEditPage {
  @State nickname: string = 'Lulu'
  @State email: string = ''
  @State errorMessage: string = ''
  @State isSaving: boolean = false

  private validate(): boolean {
    const name: string = this.nickname.trim()
    const mail: string = this.email.trim()

    if (name.length === 0) {
      this.errorMessage = '昵称不能为空'
      return false
    }
    if (mail.length > 0 && (!mail.includes('@') || !mail.includes('.'))) {
      this.errorMessage = '请输入有效的邮箱地址'
      return false
    }
    this.errorMessage = ''
    return true
  }

  private async saveProfile(): Promise<void> {
    // 实际项目中在这里调用仓储层或网络服务。
    await new Promise<void>((resolve: () => void) => {
      setTimeout(resolve, 600)
    })
  }

  build() {
    Scroll() {
      Column({ space: 20 }) {
        Text('编辑资料')
          .width('100%')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#182230')

        Column({ space: 10 }) {
          Image($r('app.media.avatar_default'))
            .width(88)
            .height(88)
            .borderRadius(44)
            .objectFit(ImageFit.Cover)

          Button('更换头像')
            .height(36)
            .fontSize(14)
            .backgroundColor('#EEF2F6')
            .fontColor('#344054')
        }
        .width('100%')

        Column({ space: 8 }) {
          Row() {
            Text('昵称 *')
              .fontSize(14)
              .fontColor('#344054')
            Blank()
            Text(`${this.nickname.length}/20`)
              .fontSize(12)
              .fontColor('#667085')
          }
          .width('100%')

          TextInput({ text: this.nickname, placeholder: '请输入昵称' })
            .width('100%')
            .height(44)
            .maxLength(20)
            .showCancelButton({ style: CancelButtonStyle.CONSTANT })
            .border({ width: 1, color: '#D0D5DD' })
            .borderRadius(6)
            .backgroundColor(Color.White)
            .onChange((value: string) => {
              this.nickname = value
              this.errorMessage = ''
            })
        }
        .alignItems(HorizontalAlign.Start)

        Column({ space: 8 }) {
          Text('邮箱')
            .fontSize(14)
            .fontColor('#344054')

          TextInput({ text: this.email, placeholder: '用于接收账户通知' })
            .width('100%')
            .height(44)
            .type(InputType.Email)
            .maxLength(64)
            .border({ width: 1, color: '#D0D5DD' })
            .borderRadius(6)
            .backgroundColor(Color.White)
            .onChange((value: string) => {
              this.email = value
              this.errorMessage = ''
            })
        }
        .alignItems(HorizontalAlign.Start)

        if (this.errorMessage.length > 0) {
          Text(this.errorMessage)
            .width('100%')
            .fontSize(13)
            .fontColor('#D92D20')
        }

        Button(this.isSaving ? '保存中...' : '保存资料')
          .width('100%')
          .height(44)
          .enabled(!this.isSaving)
          .opacity(this.isSaving ? 0.6 : 1)
          .backgroundColor('#0A7A5A')
          .onClick(async () => {
            if (!this.validate() || this.isSaving) {
              return
            }
            this.isSaving = true
            try {
              await this.saveProfile()
            } finally {
              this.isSaving = false
            }
          })
      }
      .width('100%')
      .padding(20)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F7F8FA')
  }
}

示例中的输入值、错误信息和保存状态全部由 @State 管理。界面没有直接查找或手动修改某个控件,而是根据状态自然呈现,这也是 ArkUI 声明式开发的核心方式。

六、常见问题与改进建议

把所有文案都写死在页面中

硬编码适合快速验证,但正式项目应尽早使用字符串资源。除了多语言需求,资源化还能统一产品用词,减少跨页面修改成本。

只限制输入,不校验提交数据

maxLengthInputType 改善的是输入过程,不能保证数据一定有效。校验逻辑应靠近表单状态或业务层,并明确告诉用户哪个字段存在问题。

图片没有稳定尺寸

网络图片未设置宽高时,加载过程可能影响布局。头像、封面、缩略图都应根据设计比例设置稳定尺寸,并提供占位和失败兜底。

异步操作仍允许重复点击

用户可能在网络响应前多次点击提交按钮。使用独立的加载状态控制 enabled,同时在事件入口再次判断,可以减少重复请求和数据冲突。

视觉变化代替了真实交互状态

仅把按钮颜色变灰并不会自动阻止点击。不可用状态应通过 enabled(false) 表达,透明度或颜色只作为视觉反馈。

总结

TextImageButtonTextInput 构成了 ArkUI 页面最常见的内容与交互基础。掌握它们不仅是记住属性,更重要的是建立几个稳定习惯:文本资源化、媒体尺寸明确、输入状态可追踪、提交操作可防重、错误反馈贴近字段。

当组件展示由状态决定、用户事件只负责更新状态时,页面逻辑会更容易理解和测试。后续无论构建登录、搜索、资料编辑还是业务表单,都可以沿用同一套状态驱动思路。

相关文章
|
24天前
|
人工智能 IDE API
阿里云百炼Coding Plan详细介绍:是什么、收费价格、支持模型和AI工具
阿里云百炼Coding Plan是面向开发者的AI编码专属订阅套餐,Lite基础版已全面停售,当前仅Pro高级版支持新购与续费。新用户可享首月200元限时特惠,采用固定月费模式,整合通义千问、Kimi、GLM等多款大模型,兼容Cursor、Qwen Code等数十款主流AI编程工具。产品采用5小时滚动、周度、月度多层级额度恢复机制,月度最高9万次请求,单位调用成本远低于普通按量计费。该套餐仅限交互式编码场景使用,严禁后端自动化调用,适合个人日常写代码;若涉及团队协作或生产级API集成,更推荐搭配Token Plan团队版使用。
|
24天前
|
人工智能
WAIC 2026 阿里云主题论坛倒计时!
在阿里云主题论坛,让我们聊聊 Agent Native Cloud 是怎么让智能体成为企业“自己人”的。
|
24天前
|
机器学习/深度学习 测试技术 PyTorch
英伟达三代旗舰显卡性能测试:5090、4090、3090
本文通过ResNet-50模型在CIFAR-10数据集上的PyTorch训练实测,对比RTX 3090/4090/5090三代旗舰显卡性能。结果显示:5090单精/混精吞吐达1076/1822 samples/s,较4090提升约50%,4090较3090提升约45%,为深度学习选卡提供实证参考。
英伟达三代旗舰显卡性能测试:5090、4090、3090
|
24天前
|
人工智能 监控 API
凌晨2点的PR谁来回?AgentTeams 给 LoongSuite 配了个 7×24h 社区管理员
基于多智能体治理与协作平台 AgentTeams,我们为 LoongSuite 开源项目搭建了一个 7×24 运转的 AI 数字员工,三周自动审查 108 个 PR、处理 48 个 Issue,本文记录了从架构设计到踩坑经验的全过程。
|
24天前
|
人工智能 数据处理 开发工具
阿里云大模型Night Plan活动:夜间专属折扣,Qoder/Meoo客户使用Qwen3.7-Max享2折
阿里云推出大模型Night Plan夜间专属优惠活动,面向Qoder和Meoo全量用户,北京时间每晚22:00至次日8:00错峰时段内,调用Qwen3.7-Max享2折、Qwen3.7-Plus享4折,最高节省80%调用成本。用户无需额外操作,切换指定模型提交任务即可自动适配折扣,覆盖Qoder全系产品与秒悟Meoo平台。活动配套QoderWork CN首月0元福利、多档位灵活订阅方案,以及AI Coding入门/专业版组合套餐,一站式配齐编程工具与算力资源,大幅降低复杂代码项目、长文本推理、创意开发等高算力任务的运行成本,助力个人开发者与小团队零门槛接入旗舰大模型生产力。
|
24天前
|
人工智能
2026 GOAI 世界人工智能开源大赛—新智基座 Agent Infra 赛道正式启动! ¥190万总奖池等你挑战!
2026 GOAI 世界人工智能开源大赛—新智基座 Agent Infra 赛道正式启动!¥190万总奖池等你挑战!
1506 10
|
24天前
|
存储 人工智能 Kubernetes
阿里云 AgentTeams 解读:当 Agent 开始真正在企业里干活
多 Agent 协作不只是任务并行,更是组织运转。从产品主创团队视角,聊聊 AgentTeams 在安全、协作、弹性、进化四个方向的设计思考。
|
24天前
|
JSON 缓存 网络协议
WorkManager 可靠性实战:唯一任务、重试与幂等设计
本文以“离线订单同步”为例,详解 WorkManager 可靠性实战:通过唯一任务防重复、约束控制执行时机、合理重试策略区分临时/永久错误、服务端幂等设计保障最终一致,并强调 Worker 仅负责调度、状态以数据库为准。
109 0
|
10天前
|
缓存 监控 数据挖掘
Android ANR 定位与治理:从主线程阻塞到线上证据闭环
本文系统解析Android ANR成因与治理:厘清“未响应”非崩溃本质,聚焦主线程阻塞根因(锁竞争、I/O、Binder等),强调通过堆栈+Trace+指标构建线上证据闭环,并提供典型问题修复方案与工程化治理实践。
75 0
|
24天前
|
缓存 Java 应用服务中间件
保姆级实战!用宝塔部署企业级Java开源电商商城
基于SpringBoot3+Vue3+Nuxt3+UniApp全栈架构提供宝塔面板可视化部署方案,零命令、高稳定、全流程覆盖环境安装、双Java服务部署、Nginx反向代理与HTTPS配置,大幅降低中小企业及开发者运维门槛。(239字)

热门文章

最新文章