请添加图片描述
请添加图片描述

一、引言:聊天界面——移动端最经典的交互范式

1.1 聊天界面的布局地位

在移动应用开发的历史上,聊天界面(Chat Interface)始终占据着最核心的交互范式地位。从早期的短信应用到微信、Telegram、WhatsApp 等即时通讯巨头,从客服系统到社交平台私信,消息列表 + 输入框的布局结构几乎没有改变过。

这种布局之所以成为经典,是因为它完美地满足了人类沟通的基本需求:

  • 查看历史消息:上下滚动浏览对话记录
  • 区分消息归属:自己发出的气泡在右、对方在左
  • 实时输入发送:底部固定输入栏,随时键入内容

在鸿蒙 ArkUI 中,实现这种布局只需要三个核心组件——Column、Scroll、Row——以及它们之间的巧妙配合。

1.2 为什么是 Column + Scroll + Row

组件 角色 类比
Column 全屏纵向容器 类似 Android 的 LinearLayout(vertical)
Scroll 可滚动消息区域 类似 iOS 的 UIScrollView
Row 每条消息的横向布局 类似 CSS 的 flex-direction: row

Column 提供全屏的基础架构,Scroll 处理消息列表的滚动行为,Row 控制每条消息中"头像 + 气泡"的对齐方式。三者组合,足以应对从简单聊天到复杂社交信息流的各种场景。

1.3 本文实践内容

本文以一个完整的聊天界面 Demo 为例,深入剖析以下核心技术:

  • Column 纵向布局:标题栏 + 消息列表 + 输入栏三段式
  • Scroll 滚动容器:Scroller 控制器 + 自动滚底
  • Row 横向对齐:FlexAlign 控制消息气泡方向
  • 消息气泡设计:borderRadius 不对称圆角模拟尖角
  • @Builder 组件化:消息气泡统一构建器
  • @State 状态管理:消息列表与输入框的响应式更新
  • 交互流程:发送 + 自动回复 + 滚动到底部

项目基于 HarmonyOS NEXT 6.1.1(API 24),编译链 Hvigor 6.1.1,完整源码共 325 行,所有代码均已在 DevEco Studio 中构建验证通过。

1.4 ArkTS 声明式 UI 的核心思想

在深入聊天界面代码之前,需要先理解 ArkTS 声明式 UI 的几个核心概念:

1. 状态驱动(State-Driven)

传统命令式 UI 开发中,开发者需要手动调用 textView.setText("新内容")button.setEnabled(true) 来更新 UI。而在 ArkTS 中,UI 是状态的函数:

UI = state(状态)

开发者只需要管理状态变量(@State),当状态变化时,框架自动计算出新的 UI 描述并与旧描述对比(diff),然后增量更新实际渲染树。

2. 声明式 DSL

ArkTS 的 UI 代码采用声明式领域特定语言(DSL):

Column() {                    // 创建 Column 容器
  Text('Hello')              // 创建 Text 子组件
    .fontSize(20)            // 设置属性(链式调用)
    .fontColor('#333333')
}

这种嵌套结构直观反映了 UI 的层级关系:Column 包含 Text,Text 有字体大小和颜色属性。

3. 组件化思维

ArkTS 鼓励将 UI 拆分为独立的 @Builder 方法或 @Component 结构体。每个组件只负责自己的那一部分 UI,通过参数接收数据,通过 @State 管理内部状态。这种思维与 React 的函数式组件、Flutter 的 Widget 同出一源。


二、整体布局架构:三段式 Column

2.1 三层纵向结构

聊天界面的 Column 布局分为三个层次:

Column (全屏容器, #EDEDED 灰色聊天背景)
├── Row (height:56vp)        ① 顶部标题栏 (蓝色 #409EFF)
│   ‹ 返回  [小]小李  ⋮ 更多
│            在线
│
├── Scroll (layoutWeight:1)  ② 消息列表 (弹性占满剩余空间)
│   └── Column
│       ├── Row (End)        自己消息:绿色头像 + 白色气泡(右)
│       ├── Row (Start)      对方消息:蓝色头像 + 白色气泡(左)
│       └── ...              循环渲染(ForEach)
│
└── Row (height:56vp)        ③ 底部输入栏 (灰色 #F5F5F5)
    😊  [ 输入消息...     ]  [发送]

采用"固定 + 弹性 + 固定"的经典三段式:

  1. 顶部标题栏:固定 56vp,显示联系人信息和操作按钮
  2. 消息列表layoutWeight(1) 弹性占满屏幕中间区域
  3. 底部输入栏:固定 56vp,输入框 + 发送按钮

2.2 数据结构设计:消息的完整定义

在编写 UI 之前,需要先定义消息的数据结构。ArkTS 中使用 interfaceenum 来声明类型:

/** 消息角色枚举 */
enum MsgRole {
  SELF,     // 自己发送,气泡在右侧
  OTHER     // 对方发送,气泡在左侧
}

/** 单条消息数据结构 */
interface MsgData {
  id: number;        // 消息唯一标识
  role: MsgRole;     // 发送者角色(决定气泡方向)
  content: string;   // 消息文本内容
  time: string;      // 发送时间(格式 "HH:mm")
  avatar: string;    // 头像文字(单个汉字或字母)
  name: string;      // 发送者昵称
}

为什么字段这样设计?

  • id 使用 number 类型:作为列表渲染的 key,用于帮助框架在增删消息时准确定位变化的项
  • role 使用枚举而非 booleanMsgRole.SELF / MsgRole.OTHERisSelf: true/false 可读性更好,且为将来扩展(如"系统消息")留有余地
  • time 使用 string 而非 Date:展示层只关心格式化的时间字符串,格式化逻辑在数据创建时完成
  • avatar 使用 string:用单个字符(“小”、“我”)渲染在圆形背景上模拟头像,避免了图片资源的依赖

2.3 初始消息数据的设计

初始的 8 条模拟消息不仅仅是占位数据,它们本身就是一个"引导教程"——对话内容解释了聊天界面自身的布局原理:

@State messages: MsgData[] = [
  // 消息 1:对方先打招呼
  { id: 1, role: MsgRole.OTHER, content: '你好!欢迎体验鸿蒙聊天界面布局 🎉', ... },
  // 消息 2:对方介绍使用的技术
  { id: 2, role: MsgRole.OTHER, content: '这个界面采用 Column + Scroll + Row 组合实现...', ... },
  // 消息 3:自己提问
  { id: 3, role: MsgRole.SELF, content: '了解了!消息气泡是怎么做到左对齐和右对齐的呢?', ... },
  // 消息 4:对方解答对齐方式
  { id: 4, role: MsgRole.OTHER, content: '消息气泡的核心是 Row 容器配合 FlexAlign 对齐...', ... },
  // ...后续消息继续以问答形式解释布局
];

这种"自说明"的数据设计是不需要额外引导页的优雅方案——用户在浏览对话的过程中自然理解了界面的布局原理。

2.2 为什么 Scroll 要用 layoutWeight

在 Column 布局中,如果不使用 layoutWeight,子组件的高度分配需要精确计算:

// ❌ 不推荐:手动计算高度
Column() {
  buildHeader()                       // 56vp
  buildMessageList()                  // 剩余高度不易计算
  buildInputBar()                     // 56vp
}

而使用 layoutWeight 后,框架自动完成高度分配:

// ✅ 推荐:Scroll 弹性占满
Column() {
  this.buildHeader()                  // 固定高度
  Scroll(this.scroller) { /* ... */ }
    .layoutWeight(1)                  // 占满顶部和底部之间的所有空间
  this.buildInputBar()                // 固定高度
}

layoutWeight 的计算机制(以聊天界面为例):

Column 总高度 = 设备屏幕高度 = 800vp(假设)

步骤 1:测量固定高度的子组件
  顶部栏 = 56vp
  底部栏 = 56vp

步骤 2:计算剩余空间
  剩余空间 = 800 - 56 - 56 = 688vp

步骤 3:按权重分配
  Scroll.layoutWeight(1) = 688vp × (1/1) = 688vp

无论屏幕尺寸如何变化,Scroll 总能恰好占满标题栏和输入栏之间的空间。


三、消息气泡的布局奥秘

3.1 气泡对齐:Blank() 弹性占位

消息气泡的核心布局挑战是:如何让一些消息靠左、另一些靠右?

在 ArkUI 中,解决方案是使用 Blank() 组件作为弹性占位符:

自己消息(右对齐)

Row
├── Blank()          ← 弹性占位,占据所有空闲宽度
├── Column           气泡内容
│   └── Text         消息文本
└── Text             头像(绿色)

Blank() 放在所有内容前面,Row 的 FlexAlign 默认为 Start,但 Blank() 会"撑满"前面的空间,将后面的内容推到右侧。

对方消息(左对齐)

Row
├── Text             头像(蓝色)
├── Column           气泡内容
│   ├── Text         昵称
│   └── Text         消息文本
└── Blank()          ← 弹性占位,占据所有空闲宽度

Blank() 放在所有内容后面,将前面的内容"吸"在左侧。

3.2 气泡尖角:不对称 borderRadius

聊天气泡的一个关键视觉特征是"尖角"——指向发送者一侧的小尖角。在 ArkUI 中,这通过不对称的 borderRadius 实现:

// 自己消息:右上角小圆角 → 尖角指向右上方(自己方向)
.borderRadius({
  topLeft: 12,        // 大圆角
  topRight: 4,        // ★ 小圆角 = 尖角
  bottomLeft: 12,
  bottomRight: 12
})

// 对方消息:左上角小圆角 → 尖角指向左上方(对方方向)
.borderRadius({
  topLeft: 4,         // ★ 小圆角 = 尖角
  topRight: 12,
  bottomLeft: 12,
  bottomRight: 12
})

圆角值 4 和 12 的视觉差异恰到好处——12 是标准的卡片圆角,4 则几乎接近直角,模拟了气泡"从边缘伸出来"的尖角效果。如果使用 0(直角)会显得过于生硬,使用更大的值则会失去尖角的视觉效果。

3.3 气泡宽度限制:constraintSize

.constraintSize({ maxWidth: '70%' })

constraintSize 设置了气泡的最大宽度为父容器宽度的 70%。为什么是 70%?因为:

  1. 留出头像空间:右侧(或左侧)有 40vp 的头像 + 8vp 间距
  2. 视觉平衡:气泡不应占据全部宽度,否则无法区分消息归属
  3. 长文本处理:超长内容会自动换行,不会撑出屏幕边界

这个 70% 的限制是聊天界面设计的最佳实践,被微信、Telegram 等主流应用广泛采用。

3.4 时间戳的放置

时间戳放在气泡下方,通过 margin({ top: 4 }) 与气泡保持 4vp 间距。时间戳的对齐方式与气泡一致(自己消息右对齐、对方消息左对齐),形成清晰的视觉归属。


四、Scroll 组件的配置详解

4.1 Scroller 控制器

private scroller: Scroller = new Scroller();

Scroll(this.scroller) {
  // 消息内容
}

Scroller 实例传入 Scroll 组件,可以在外部代码中控制 Scroll 的滚动行为:

/** 滚动到底部 */
scrollToBottom(): void {
  this.scroller.scrollEdge(Edge.End);
}

scrollEdge(Edge.End) 是滚动到 Scroll 末尾的最直接方法。与之对应的还有 scrollEdge(Edge.Start) 滚动到顶部。

4.2 延迟滚动:等待渲染完成

aboutToAppear(): void {
  setTimeout((): void => {
    this.scrollToBottom();
  }, 200);
}

// 发送消息后
setTimeout(() => this.scrollToBottom(), 100);

为什么需要 setTimeout 延迟?因为 ArkUI 的 UI 渲染是异步的。当 @State messages 更新后,框架需要经历以下流程:

messages = [...messages, newMsg]
  → 框架检测到状态变化
  → 触发 build() 增量更新(异步调度)
  → 新消息的 UI 组件被创建 → 布局引擎测量并计算位置
  → setTimeout 回调执行 → scrollEdge(Edge.End) 滚动到最新位置

100ms 的延迟足以让框架完成一次渲染-布局周期。过短的延迟(如 0ms)可能在新消息尚未渲染完成时就执行滚动,导致滚动位置不正确。

4.3 隐藏滚动条

.scrollBarState(BarState.Off)

聊天界面中通常隐藏滚动条,用户通过手势直觉完成滚动操作。BarState.Off 彻底隐藏滚动条,BarState.On 始终显示,BarState.Auto 在滚动时自动浮现。对于聊天界面,BarState.Off 是最符合用户期望的选择。

4.4 Scroll 性能考量

当消息数量增多时,Scroll 组件内部包含的子组件数量会线性增长。ArkUI 的 Scroll 组件对子组件的渲染做了优化——只会渲染可见区域附近的组件。但对于包含大量复杂子组件的场景,以下优化策略可以帮助提升性能:

1. 限制消息列表长度

sendMessage() 方法中添加上限控制:

sendMessage(): void {
  // ... 发送逻辑
  this.messages = [...this.messages, newMsg];

  // 上限控制:最多保留 150 条
  if (this.messages.length > 150) {
    this.messages = this.messages.slice(-100);
  }
}

slice(-100) 保留最后 100 条消息,删除最早的消息。这既保证了聊天记录的连续性,又避免了无限增长导致的性能问题。

2. 使用 LazyForEach 替代 ForEach

对于超过 100 条的大列表,LazyForEach 只在需要时创建和渲染子组件:

// 需要实现一个 DataSource 类
class MessageDataSource extends BasicDataSource {
  // ... 实现数据源方法
}

// 在 UI 中使用
LazyForEach(this.dataSource, (msg: MsgData) => {
  this.buildBubble(msg)
}, (msg: MsgData) => msg.id.toString())

3. 避免在 buildBubble 中执行复杂计算

消息气泡的构建方法应该只做 UI 布局,不做数据处理。所有文本格式化、时间计算等操作都应该在数据层面完成,传入 MsgData 时已经是格式化好的字符串。


五、@State 状态管理与消息交互

5.1 消息列表的响应式更新

@State messages: MsgData[] = [
  // 初始消息(8 条模拟对话)
];

@State 装饰器使 messages 数组成为响应式数据。当数组内容变化时,所有引用 messages 的 UI 部分自动重渲染。

关键规则:必须创建新的数组引用才能触发 UI 更新。

// ✅ 正确:展开运算符创建新数组
this.messages = [...this.messages, newMsg];

// ❌ 错误:push 不改变数组引用,不会触发 UI 更新
this.messages.push(newMsg);

这是 ArkTS 响应式系统的约定——框架通过引用比较(===)检测数组和对象的变化。展开运算符 [...arr, newItem] 创建了一个全新的数组实例,引用变化了,框架就知道需要更新 UI。

5.2 发送消息的完整流程

sendMessage(): void {
  const text: string = this.inputText.trim();
  if (text.length === 0) return;                  // ① 空内容不发送

  const now: Date = new Date();
  const time: string =
    `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;

  this.messages = [...this.messages, {             // ② 追加新消息
    id: this.nextId++,
    role: MsgRole.SELF,
    content: text,
    time: time,
    avatar: '我',
    name: '我'
  }];
  this.inputText = '';                             // ③ 清空输入框

  setTimeout(() => this.scrollToBottom(), 100);    // ④ 滚到底部
  setTimeout(() => this.autoReply(), 1200);        // ⑤ 1.2s 后自动回复
}

步骤拆解

步骤 操作 目的
trim().length === 0 判断 防止发送纯空格消息
[...messages, newMsg] 追加 + 触发 UI 更新
inputText = '' 清空输入框,按钮自动变灰
scrollToBottom() × 100ms 自动滚到最新消息
autoReply() × 1200ms 模拟对方回复

5.3 自动回复的逻辑

autoReply(): void {
  const replies: string[] = [
    '收到收到 👍',
    '继续聊!布局还可以配合 List 组件实现。',
    '对,Column + Scroll 是最基础的聊天布局方案。',
    '你可以试着多发几条看看滚动效果 😄',
    '这是个好问题!让我想想...',
    '完全支持!这种布局方式非常灵活。'
  ];

  const time: string = /* 当前时间 */;

  this.messages = [...this.messages, {
    id: this.nextId++,
    role: MsgRole.OTHER,
    content: replies[Math.floor(Math.random() * replies.length)],  // 随机选
    time: time,
    avatar: '小',
    name: '小李'
  }];

  setTimeout(() => this.scrollToBottom(), 100);
}

Math.floor(Math.random() * replies.length) 从 6 条预定义回复中随机选取一条。每次发送消息后 1.2 秒触发自动回复,模拟真实的聊天延迟感。

5.4 输入框与按钮的状态联动

@State inputText: string = '';

inputText 连接着 TextInput 和发送按钮:

TextInput({ text: this.inputText, placeholder: '输入消息...' })
  .onChange((v: string) => { this.inputText = v; })

Button() { Text('发送') }
  .backgroundColor(this.inputText.trim().length > 0 ? '#409EFF' : '#C0C0C0')
  .enabled(this.inputText.trim().length > 0)

状态变化链路

用户在输入框中键入文字
  → TextInput.onChange 回调被触发
  → this.inputText = 刚输入的内容
  → @State 检测到 inputText 变化
  → Button 的 backgroundColor 重新计算
  → 输入框有内容时:蓝色 (#409EFF) + 可点击
  → 输入框为空时:灰色 (#C0C0C0) + 禁用

整个过程中,开发者只需要管理 @State inputText 这一个变量,UI 的联动完全由框架自动处理。这就是声明式 UI 的核心优势。


六、@Builder 组件化:拆解 UI 的逻辑单元

6.1 三个 @Builder 方法

聊天界面将 UI 拆分为三个 @Builder 方法:

@Builder 职责 参数 行数
buildHeader() 顶部标题栏 27
buildMessageList() 消息列表容器 14
buildBubble(msg) 单条消息气泡 MsgData 70
buildInputBar() 底部输入栏 28

6.2 @Builder 的消息气泡逻辑

buildBubble(msg) 是聊天界面中最复杂的 @Builder 方法。它根据 msg.role 的值渲染不同的布局分支:

@Builder
buildBubble(msg: MsgData): void {
  if (msg.role === MsgRole.SELF) {
    // ====== 自己:右对齐 ======
    Row() {
      Blank()                              // 弹性占位
      Column() { /* 气泡 + 时间 */ }       // 内容
        .alignItems(FlexAlign.End)
      Text(msg.avatar)                     // 绿色头像
        .backgroundColor('#87D068')
    }

  } else {
    // ====== 对方:左对齐 ======
    Row() {
      Text(msg.avatar)                     // 蓝色头像
        .backgroundColor('#5B8FF9')
      Column() { /* 昵称 + 气泡 + 时间 */ } // 内容
        .alignItems(FlexAlign.Start)
      Blank()                              // 弹性占位
    }
  }
}

@Builder 设计的关键考量

  • 单一数据源:只接受 MsgData 一个参数,所有渲染信息都来自数据对象
  • 纯 UI 逻辑:不包含业务逻辑(发送、回复等),只有布局和样式
  • 职责清晰:不负责遍历(遍历在父组件中由 ForEach 完成),只负责渲染单条消息

七、顶部标题栏与底部输入栏的细节

7.1 顶部标题栏设计

@Builder
buildHeader(): void {
  Row() {
    Text('‹')           // 返回按钮
    Text('小')           // 圆形头像
      .width(36).height(36).borderRadius(18)
      .backgroundColor('#5B8FF9')

    Column() {
      Text('小李')       // 联系人名称
      Text('在线')       // 在线状态
        .fontSize(11).fontColor('#B0D0FF')
    }

    Blank()             // 弹性占位

    Text('⋮')           // 更多操作
  }
  .height(56)
  .backgroundColor('#409EFF')
}

几个关键的细节

  • 圆形头像width: 36, height: 36, borderRadius: 18(宽/2 = 18)
  • 双层文字:Column 容纳"小李"和"在线"两行文字,alignItems(HorizontalAlign.Start) 左对齐
  • 更多按钮(Unicode 字符 U+22EE)表示更多操作

7.2 底部输入栏的弹性布局

@Builder
buildInputBar(): void {
  Row() {
    Text('😊')           // 表情按钮
      .fontSize(22)

    TextInput({ text: this.inputText, placeholder: '输入消息...' })
      .layoutWeight(1)   // ★★★ 弹性占满
      .height(40).borderRadius(20)
      .onChange((v) => { this.inputText = v; })
      .onSubmit(() => { this.sendMessage(); })

    Button() { Text('发送') }
      .type(ButtonType.Capsule).height(40)
      .backgroundColor(this.inputText.length > 0 ? '#409EFF' : '#C0C0C0')
      .enabled(this.inputText.length > 0)
      .onClick(() => { this.sendMessage(); })
  }
  .height(56)
}

layoutWeight(1) 的效果

Row 总宽度 = 设备宽度
  → 左侧 😊 占用固定宽度(约 34vp)
  → 右侧 [发送] 按钮占用固定宽度(约 60vp)
  → 中间 TextInput 通过 layoutWeight(1) 获得:
    剩余宽度 = 设备宽 - 34 - 60 - 左右间距(20)
    TextInput.width = 剩余宽度 × 1 = 完整剩余宽度

TextInputborderRadius(20) 让输入框呈现胶囊形状(高度 = 40,半径 = 20 恰好是半高),与整体圆润的 UI 风格统一。


八、项目工程配置

8.1 编译版本配置

// build-profile.json5
{
  "app": {
    "products": [
      {
        "name": "default",
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.1.1(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}
配置项 说明
targetSdkVersion 6.1.1(24) 目标 SDK 版本,API 24
compatibleSdkVersion 6.1.1(24) 向后兼容版本
runtimeOS HarmonyOS 运行操作系统
caseSensitiveCheck true 文件名大小写严格检查

8.2 页面路由配置

// main_pages.json
{
  "src": [
    "pages/ChatLayoutDemo"
  ]
}

8.3 EntryAbility 入口

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/ChatLayoutDemo', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'App', 'Failed: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(0x0000, 'App', 'Succeeded.');
    });
  }
}

九、常见问题与解决方案

9.1 消息列表不滚动

现象:Scroll 包含多条消息但无法滚动。

原因:Scroll 没有有效的高度。如果 Scroll 的父容器高度未正确设置,Scroll 的子内容总高度可能小于 Scroll 自身高度。

解决:确保 Scroll 设置了 layoutWeight(1),且父 Column 设置了 height('100%')

// 正确配置
Column()
  .height('100%')              // 父容器必须有明确高度
{
  Scroll()
    .layoutWeight(1)           // Scroll 弹性占满
  // ...
}

9.2 新消息不显示

现象:调用 sendMessage() 后消息数组长度增加,但 UI 上看不到新消息。

原因:使用了 push() 而不是展开运算符。

// ❌ 错误:不会触发 UI 更新
this.messages.push(newMsg);

// ✅ 正确:创建新数组引用
this.messages = [...this.messages, newMsg];

9.3 滚动不到最底部

现象:新消息出现后,底部总是有少量空白或最新消息被遮挡。

原因:滚动的时机过早,新消息的 UI 尚未渲染完成。

解决:使用 setTimeout 延迟 100ms 再滚动:

// ❌ 可能不生效
this.scroller.scrollEdge(Edge.End);

// ✅ 等待渲染完成
setTimeout(() => this.scroller.scrollEdge(Edge.End), 100);

9.4 输入框内容不更新

现象:在 TextInput 中打字,文本不显示或按钮状态不联动。

原因@State inputText 没有被正确绑定。

解决:确保 TextInput 的 text 属性和 onChange 回调都正确关联:

TextInput({ text: this.inputText, placeholder: '输入消息...' })
  .onChange((v: string) => { this.inputText = v; })

十、从 Demo 到生产:升级路径

10.1 功能升级对照

功能 Demo 实现 生产级方案
消息数据 内存数组初始化的模拟数据 WebSocket + 后端 API
消息存储 无持久化 本地 SQLite / 云端同步
自动回复 本地预置回复 真实聊天对方/机器人 API
图片消息 纯文本消息 Image + 图片选择器
语音消息 录音组件 + 音频播放
表情面板 一个 😊 按钮 自定义表情面板 Grid
消息状态 已发送/已读/发送失败
用户头像 字符 Text 圆形 Image 网络图片 + 缓存

10.2 性能优化建议

消息数量增多时,需要考虑以下优化:

  1. LazyForEach 替代 ForEach:当消息超过 50 条时,LazyForEach 可以按需加载和销毁不可见的消息项
LazyForEach(this.messageDataSource, (msg: MsgData) => {
  this.buildBubble(msg)
}, (msg: MsgData) => msg.id.toString())
  1. 消息数量限制:在 sendMessage() 中限制消息数组最大长度,超过时删除最早的消息
if (this.messages.length > 200) {
  this.messages = this.messages.slice(-150);
}
  1. 图片懒加载:使用 Imageload() 方法控制加载时机,避免一次性加载大量图片

十一、布局 API 速查

11.1 核心组件

组件 用途 本项目使用位置
Column 纵向排列 全屏容器、消息内容、昵称+状态
Row 横向排列 标题栏、每条消息气泡、输入栏
Scroll 可滚动容器 消息列表区域
TextInput 文本输入 底部输入框
Button 按钮 发送按钮
Blank 弹性占位 消息气泡左右对齐
Text 文本显示 所有文字内容

11.2 关键属性与 API

属性/API 用途 示例
layoutWeight(n) 弹性权重 .layoutWeight(1) 占满剩余空间
borderRadius() 圆角 .borderRadius({ topLeft:12, topRight:4 })
constraintSize() 尺寸约束 .constraintSize({ maxWidth:'70%' })
scrollEdge(Edge.End) 滚到底部 this.scroller.scrollEdge(Edge.End)
scrollBarState() 滚动条状态 .scrollBarState(BarState.Off)
TextDecorationType 文字装饰 .decoration({ type: LineThrough })
FlexAlign 对齐方式 .alignItems(FlexAlign.End)

11.3 构建命令

# 快速验证项目配置
hvigorw PreBuildApp --no-daemon

# Debug 构建
hvigorw assembleApp --mode debug --no-daemon

# Release 构建
hvigorw assembleApp --mode release --no-daemon

十二、总结

12.1 技术要点回顾

本文通过一个 325 行的完整聊天界面 Demo,系统讲解了 ArkUI 在聊天场景下的核心布局技术:

  1. 三段式 Column 架构:固定标题 + 弹性消息列表 + 固定输入栏
  2. 消息气泡对齐:Blank() + Row 实现左右切换
  3. 气泡尖角效果:borderRadius 不对称值模拟
  4. 宽度限制:constraintSize({ maxWidth: ‘70%’ })
  5. 响应式状态:@State 管理消息列表和输入框
  6. 数组更新:展开运算符 [...arr, newItem] 触发 UI 刷新
  7. 自动滚底:Scroller + setTimeout 延迟滚动
  8. 组件化:@Builder 拆分标题栏、气泡、输入栏

这些技术的组合构成了 ArkUI 聊天界面的标准布局模式。无论是简单的消息应用,还是复杂的社交信息流,都可以基于这个架构进行扩展和定制。

12.2 ArkUI 布局的核心理念

通过本文的实战,可以总结出 ArkUI 布局的几个核心理念:

  1. 声明式优于命令式:开发者描述"UI 应该是什么样",而不是"如何一步步构建 UI"
  2. 状态驱动 UI:@State 变量变化 → UI 自动更新,无需手动操作 DOM
  3. 组合优于继承:Column/Row/Scroll 通过组合满足所有布局需求
  4. 组件化拆分:@Builder 将复杂 UI 拆分为可独立维护的逻辑单元

这些理念与 React、Flutter、SwiftUI 等现代 UI 框架一脉相承。掌握了 ArkUI,也就掌握了现代声明式 UI 开发的通用方法论。

Logo

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

更多推荐