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

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 本文详解鸿蒙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 页面最常见的内容与交互基础。掌握它们不仅是记住属性,更重要的是建立几个稳定习惯:文本资源化、媒体尺寸明确、输入状态可追踪、提交操作可防重、错误反馈贴近字段。

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

相关文章
|
1天前
|
人工智能 安全 测试技术
|
3天前
|
云安全 人工智能 安全
阿里云 Agentic SOC 位居 IDC MarketScape安全运营智能体2026领导者类别
以 Agentic AI 重构安全运营闭环,阿里云云安全在产品能力与市场份额
1151 3
|
2天前
|
数据采集 机器学习/深度学习 人工智能
田间杂草定位与检测4200张YOLO智慧农业数据集分享
本数据集含4200张真实农田图像,YOLO格式,单类别(杂草)高质量标注,覆盖多作物、多光照、多生长阶段等复杂场景,专为智慧农业杂草检测与智能除草设备研发设计,支持YOLOv5/v8/v10等主流模型训练。
344 93
|
4天前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
552 11
|
7天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)
|
7天前
|
人工智能 缓存 JSON
刚刚 GPT-5.6 发布,吊打 Claude 5 和 Grok 4.5?一手实测来啦!
GPT-5.6 刚发布,跑分号称超越 Fable 5,真的假的?附一手实测,让 3 个最强 AI 编程模型 GPT-5.6 Sol、Claude Fable 5、Grok 4.5 在 Cursor 里同时一把梭开发网页游戏,看看最新模型到底谁更能打。
360 0

热门文章

最新文章