前言

在鸿蒙 ArkTS 应用开发中,UI 的构建高度依赖于声明式范式。无论是静态展示还是动态交互,都离不开基础组件的支撑。ArkUI 框架提供了 Text、Image、Button、TextInput 四大核心基础组件。虽然它们在日常开发中使用频率极高,但如果缺乏对底层属性的深入理解,很容易写出冗余代码或导致性能瓶颈。

一、 核心定位与适用场景对比

  1. Text(文本组件):采用“纯展示”策略。主要用于渲染标题、正文、标签等静态或动态字符串。它支持富文本排版、文本截断和阴影装饰,是 UI 中最基础的视觉元素。

    • 工作机制:通过链式调用设置字体样式与布局约束。
    • 适用场景:所有需要文字呈现的场景,如新闻列表标题、个人中心昵称。
  2. Image(图片组件):采用“资源驱动”策略。用于渲染本地图标、网络照片或 Base64 图像。它不仅负责展示,还内置了图片解码与缓存机制。

    • 工作机制:根据容器尺寸与 objectFit 属性进行自适应缩放裁剪。
    • 适用场景:商品封面、用户头像、Banner 轮播图。
  3. Button(按钮组件):采用“事件响应”策略。作为人机交互的核心枢纽,它封装了点击态反馈与状态切换逻辑,同时支持图文混排的自定义子组件形态。

    • 工作机制:监听 .onClick() 回调,结合 @State 变量实现视图联动。
    • 适用场景:表单提交、页面跳转、功能触发开关。
  4. TextInput(单行输入框组件):采用“数据捕获”策略。专门针对用户的键盘输入行为设计,内置了光标控制、正则过滤与软键盘类型适配能力。

    • 工作机制:双向绑定状态变量,实时拦截并校验非法字符。
    • 适用场景:登录注册表单、全局搜索栏、验证码输入。

二、 核心属性配置详解

1. Text 文本组件
  • 字体样式:通过 .fontSize(size) 设定字号,.fontColor(color) 设定颜色,.fontWeight(weight) 控制字重(如 FontWeight.Bold)。
  • 排版与截断:使用 .textAlign(align) 控制对齐方式;配合 .maxLines(number) 限制最大行数,并通过 .textOverflow({overflow: TextOverflow.Ellipsis}) 实现超长文本的尾部省略号截断。
  • 高级装饰:支持通过 .shadow() 添加文字投影,以及 .lineSpacing() 调整多行文本的行间距以提升阅读体验。
2. Image 图片组件
  • 填充模式 (objectFit):这是图片组件最核心的属性。ImageFit.Cover 保持比例覆盖容器(常用于头像);ImageFit.Contain 保持比例完整显示(常用于商品图);ImageFit.Fill 则强制拉伸填满边界。
  • 容错与占位:通过 .alt(src) 设置网络图片加载失败时的兜底占位图,避免界面出现空白裂图。
  • 性能优化:对于大图,务必使用 .sourceSize({width, height}) 指定解码分辨率,防止内存溢出;在网络请求前需确保已申请 ohos.permission.INTERNET 权限。
3. Button 按钮组件
  • 形态类型 (type):通过 ButtonType 枚举快速生成标准外观。Capsule 为胶囊型(圆角自动匹配高度一半),Circle 为正圆形(需保证宽高一致),Normal 为矩形(支持自定义 .borderRadius())。
  • 交互与状态:设置 stateEffect: true 可开启默认的按压态视觉反馈;通过 .enabled(boolean) 动态控制按钮是否置灰禁用。
  • 自定义内容:除了传入字符串,还支持在 Button() {} 内部嵌套 RowImageText 等组件,实现复杂的图文组合按钮。
4. TextInput 输入框组件
  • 输入类型 (type):通过 InputType 枚举调起不同的系统键盘。Password 会自动掩码显示并提供明文切换图标;Number 调起纯数字键盘;Email 会限制仅允许输入一个 @ 符号。
  • 内容校验:使用 .maxLength(number) 硬性限制最大字符数;通过 .inputFilter(regex) 传入正则表达式,从底层过滤非法输入(如仅允许输入中文或数字)。
  • 样式定制:支持通过 .placeholder() 设置灰色提示文案,.caretColor() 自定义光标颜色,以及 .enterKeyType() 将软键盘的回车键替换为“搜索”、“发送”等业务文案。

三、 代码实战与综合应用

1. Text:从单行截断到富文本排版

除了基础的字体大小和颜色,Text 还支持多段落拼接、渐变文字以及复杂的对齐方式。

@Entry
@Component
struct AdvancedTextExample {
  build() {
    Column({ space: 20 }) {
      // 1. 多段落拼接与独立样式控制
      Text() {
        Span('鸿蒙 ')
          .fontSize(24)
          .fontColor('#007DFF')
          .fontWeight(FontWeight.Bold)
        Span('ArkUI ')
          .fontSize(18)
          .fontColor('#666666')
        Span('声明式开发范式')
          .fontSize(16)
          .fontColor(Color.Red)
          .backgroundStyle({ color: '#FFF0F0' }) // 为特定文字添加背景色
      }
      .textAlign(TextAlign.Center) // 整体居中对齐

      // 2. 长文本省略号 + 尾部图标(常用于新闻列表)
      Row() {
        Text('这是一段非常长的鸿蒙开发技术文档描述文本,用于演示超出容器宽度后的省略号截断效果及右侧图标的完美贴合。')
          .fontSize(16)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .layoutWeight(1) // 占据剩余空间
        Image($r('app.media.startIcon'))
          .width(16).height(16)
          .margin({ left: 5 })
      }
      .width('100%')
      .alignItems(VerticalAlign.Top) // 确保文字顶部对齐小图标

      // 3. 线性渐变文字(高级视觉效果)
      Text('HarmonyOS NEXT')
        .fontSize(36)
        .fontWeight(FontWeight.Bolder)
        .linearGradient({
          angle: 90, // 渐变角度
          colors: [['#FF0000', 0.0], ['#00FF00', 0.5], ['#0000FF', 1.0]]
        })
    }
    .width('100%')
    .padding(20)
  }
}

2. Image:圆角裁剪、遮罩与占位策略

图片组件不仅是展示,还需要处理各种极端情况(如加载失败、形状裁剪)。

@Entry
@Component
struct AdvancedImageExample {
  @State isLoading: boolean = true;

  build() {
    Column({ space: 20 }) {
      // 1. 头像级圆形裁剪 + 边框 + 阴影
      Image($r('app.media.background'))
        .width(100)
        .height(100)
        .borderRadius(50) // 宽高一半即为正圆
        .objectFit(ImageFit.Cover) // 保持比例覆盖,防止变形
        .border({ width: 3, color: '#FFFFFF' })
        .shadow({ radius: 8, color: '#33000000', offsetX: 0, offsetY: 4 })

      // 2. 自定义加载状态与失败兜底 (Alt)
      Stack() {
        Image(this.isLoading ? '' : $r('app.media.startIcon'))
          .width('100%')
          .height(200)
          .objectFit(ImageFit.Cover)
          .onComplete(() => { this.isLoading = false; }) // 监听加载完成
          .alt($r('app.media.background')) // 核心:加载失败或src为空时的占位图
        
        if (this.isLoading) {
          LoadingProgress().width(40).height(40) // 加载中显示菊花图
        }
      }
      .width('100%')
      .borderRadius(12)

      // 3. 异形裁剪(例如:商品卡片顶部的半圆弧)
      Image($r('app.media.background'))
        .width('100%')
        .height(150)
        .objectFit(ImageFit.Cover)
        .borderRadius({ topLeft: 0, topRight: 0, bottomLeft: 20, bottomRight: 20 })
    }
    .padding(20)
  }
}

3. Button:图文混排、禁用态与防抖封装

原生 Button 支持嵌套任意子组件,结合 @State 可以轻松实现复杂的交互反馈。

@Entry
@Component
struct AdvancedButtonExample {
  @State isSubmitting: boolean = false;
  @State likeCount: number = 128;
  @State isLiked: boolean = false;

  build() {
    Column({ space: 20 }) {
      // 1. 图文混排按钮(常用于支付、分享)
      Button({ type: ButtonType.Normal }) {
        Row({ space: 8 }) {
          Image($r('app.media.startIcon')).width(20).height(20)
          Text('微信支付').fontSize(16).fontColor(Color.White)
        }
      }
      .width('100%')
      .height(48)
      .backgroundColor('#07C160')
      .borderRadius(8)

      // 2. 动态点赞按钮(状态联动)
      Button() {
        Row({ space: 6 }) {
          Image(this.isLiked ? $r('app.media.startIcon') : $r('app.media.background'))
            .width(18).height(18)
          Text(`${this.likeCount}`).fontSize(14)
        }
      }
      .type(ButtonType.Capsule)
      .backgroundColor(this.isLiked ? '#FFEDED' : '#F5F5F5')
      .fontColor(this.isLiked ? '#FF4D4F' : '#666666')
      .stateEffect(true) // 开启按压态缩放效果
      .onClick(() => {
        this.isLiked = !this.isLiked;
        this.likeCount += this.isLiked ? 1 : -1;
      })

      // 3. 提交按钮(带 Loading 状态与防抖保护)
      Button(this.isSubmitting ? '正在保存...' : '确认提交')
        .type(ButtonType.Capsule)
        .width('100%')
        .height(48)
        .enabled(!this.isSubmitting) // 关键:禁用状态下自动屏蔽点击
        .opacity(this.isSubmitting ? 0.7 : 1.0) // 视觉上的置灰效果
        .onClick(() => {
          this.isSubmitting = true;
          setTimeout(() => { this.isSubmitting = false; }, 2000); // 模拟网络请求
        })
    }
    .padding(20)
  }
}

4. TextInput:密码可见性切换、正则过滤与焦点控制

输入框是交互的重灾区,需要精细控制键盘类型、内容校验和光标行为。

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct AdvancedInputExample {
  @State username: string = '';
  @State password: string = '';
  @State searchKey: string = '';

  build() {
    Column({ space: 20 }) {
      // 1. 账号输入框(限制长度 + 字符过滤)
      TextInput({ placeholder: '请输入账号(仅限字母数字)', text: this.username })
        .type(InputType.Normal)
        .maxLength(20)
        .inputFilter('[a-zA-Z0-9]') // 修复:使用字符串而非 RegExp
        .onChange((value: string) => { // 修复:显式声明 value 类型为 string
          this.username = value;
        })
        .width('100%')
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
        .padding({ left: 15, right: 15 } as EdgeWidths) // 修复:添加类型断言避免 untyped-obj-literals

      // 2. 密码输入框
      TextInput({ placeholder: '请输入密码', text: this.password })
        .type(InputType.Password)
        .maxLength(16) // 修复:直接使用 maxLength 替代错误的 passwordRules 对象
        .caretColor('#007DFF')
        .placeholderColor('#CCCCCC')
        .onChange((value: string) => {
          this.password = value;
        })
        .width('100%')
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
        .padding({ left: 15, right: 15 } as EdgeWidths)

      // 3. 搜索框
      TextInput({ placeholder: '搜索商品或文章', text: this.searchKey })
        .enterKeyType(EnterKeyType.Search)
        .onSubmit(() => {
          promptAction.showToast({ message: `开始搜索: ${this.searchKey}` });
        })
        .onBlur(() => {
          console.log('Search input lost focus');
        })
        .width('100%')
        .height(40)
        .backgroundColor('#EEEEEE')
        .borderRadius(20)
        .padding({ left: 15, right: 15 } as EdgeWidths)
        .fontSize(14)
    }
    .padding(20)
    .width('100%')
  }
}

四、 避坑指南与最佳实践

  1. Text 的省略号陷阱

    • 错误做法:只设置了 .textOverflow({overflow: TextOverflow.Ellipsis}) 却没有设置 .maxLines()。此时文本不会生效截断,而是直接撑开容器。
    • 正确做法:必须将 textOverflow 与 maxLines 搭配使用,且建议明确指定 .width() 约束,否则在某些弹性布局下可能无法准确计算截断位置。
  2. Image 的网络加载白屏

    • 问题现象:在弱网环境下,图片加载缓慢导致界面出现大面积空白,用户体验极差。
    • 解决方案:始终配置 .alt() 占位图;对于列表中的大量图片,务必结合 LazyForEach 使用,并利用 .sourceSize() 限制解码尺寸,避免滑动时引发严重的内存抖动与掉帧。
  3. TextInput 的安全校验缺失

    • 错误做法:仅在 .onChange() 回调中进行业务层的正则校验。这会导致非法字符先被输入到框内再被清除,造成光标闪烁和视觉卡顿。
    • 正确做法:优先使用 .inputFilter(regex) 在组件底层直接拦截非法按键输入,将 .onChange() 纯粹用于状态同步与业务逻辑处理。
  4. Button 的防抖与状态保护

    • 最佳实践:涉及网络请求或耗时操作的按钮,必须在点击后立即将绑定的 @State 变量设为 true,并同步更新 .enabled(false) 或显示 Loading 状态。这能有效防止用户在等待期间连续点击导致的重复提交问题。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐