定义消息行(MessageLine)组件定义于 entry/…/src/ui/ui.cj 文件中。其工作机制与前述 Switch 自定义组件相似。
在entry/…/src/ui.cj文件内,包ohos_app_cangjie_entry.ui里,可见自定义MessageLine组件的相关代码。在聊天机器人应用中,MessageLine用于显示一条对话消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Component public class MessageLine { @Prop var content: String @Prop var reason: String @Prop var self: Bool @State var showReason: Bool = true
func build() { if (self) { userMessageLine() } else { aiMessageLine() } } }
|
▶第1行:Component宏装饰类型MessageLine,使其成为可被复用的自定义UI组件。其他程序文件从ohos_…_entry.ui包导入MessageLine类型以后,便可使用MessageLine创建消息行组件。
▶第2行:为确保类型能在包外访问,将其声明为公有(public)。
▶第3 ~ 5行:content为消息内容,reason为消息中的思维链内容; self为真是说明消息行为用户消息行,否则为AI消息行(由AI生成的消息)。如第3行注释所述,content, reason, self均使用Prop宏修饰,语义与@State类似,但必须使用父组件提供的变量进行初始化。即,使用MessageLine组件的组件并需为MessageLine提供上述三个“构造参数”。
▶第6行:使用@State宏修饰的showReason成员表示是否显示思维链内容。当showReason发生变化时,相关UI组件会自动刷新以反应该变化。
▶第8 ~ 14行:build函数用于构建MessageLine组件的UI实体。当self为真时,执行userMessageLine函数生成用户消息行,否则生成AI消息行。
如图1所示,用户消息行和AI消息行的显示格式(左/右, 底色 … )有所区别,分成两个函数来生成存在其合理性。

图1 用户消息行与AI消息行
我们先讨论用户消息行生成函数userMessageLine。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class MessageLine { @Builder func userMessageLine() { Column(3) { Row { Row { Text(content).fontColor(0x000000).fontSize(16).lineHeight(26) }.constraintSize(maxWidth: 90.percent) .padding(top: 7, bottom: 7, right: 16, left: 16) .backgroundColor(0xebf3fe).borderRadius(20) Row { ChatAvatar(src: @r(app.media.user), size: 36) }.margin(left: 10) }.justifyContent(FlexAlign.End).alignItems(VerticalAlign.Top).width(100.percent) }.width(100.percent).padding(right: 5, left: 5) } }
|

图2 用户消息行的UI结构
图2展示了用户消息行的UI结构。如图所示,一个Column布局容器内包含一个Row布局容器,Row布局内又包含左右两个Row布局。左边的Row布局内有标签组件Text(content),用于显示消息内容; 右边的Row布局内有ChatAvatar(…),用于显示用户图标(@r(app.media.user))。稍后我们将看到,ChatAvatar是一个用@Builder宏修饰的可以生成UI组件的函数。
▶第4行:使用Builder宏修饰的成员函数,可以得行声明式UI描述。
▶第12行:标签组件的背景色被设置为0xebf3fe,即图2中“晚上好!”处的浅蓝色。
接下来讨论AI消息行生成函数aiMessageLine。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class MessageLine { @Builder func aiMessageLine() { Column(3) { Row { Row { ChatAvatar(src: @r(app.media.system), size: 36) }.margin(top: 5) Row { if (content == '' && reason == '') { Row { Image(@r(app.media.loading)).width(25).height(25) } .padding(top: 7, bottom: 7, right: 16, left: 16).backgroundColor(0xffffff) } Column { if (reason != '') { Column { Row { Text('已深度思考') .fontWeight(Bold).fontColor(0x777777).fontSize(14) .lineHeight(20).margin(right: 10) Image(if (showReason) { @r(app.media.on) } else { @r(app.media.down) }).width(20).height(20) } .padding(top: 7, bottom: 7, right: 16, left: 16) .width(90.percent) .justifyContent(FlexAlign.Start) .onClick {_ => showReason = !showReason} if (showReason) { Row { Row().width(10) Row { Text(reason).fontColor(0x777777).fontSize(14).lineHeight(20) }.padding(top: 7, bottom: 0, right: 16, left: 10) .borderStyle(BorderStyle.Solid) .borderWidth(EdgeWidths(left: 2.vp)) .borderColor(0xe5e5e5) }.width(90.percent) } } } if (content != '') { Row { Text(content).fontColor(0x000000).fontSize(16).lineHeight(26) }.width(90.percent).padding(top: 7, bottom: 7, right: 16, left: 16) .backgroundColor(0xffffff) } } } }.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width(100.percent) }.width(100.percent).padding(right: 5, left: 5) } }
|
AI消息行的UI结构比较复杂,包含了多重嵌套的Column和Row容器。图3中未描绘这些Column以及Row容器的详细结构。
▶第8 ~ 10行:在一个Row容器中使用ChatAvatar函数创建系统图标(见图3①处)。@r(app.media.system)即为图中的“海豚”图标。
▶第12 ~ 15行:如果content和reason都为空,说明客户端正在等待并获取AI的回答,此时,使用Image组件显示一个表示正在加载的动图。@r(app.media.loading)即为那个GIF动图。
▶第17行:如果reason不为空,说明回答包含思维链内容,产生后续UI组件以显示思维链输出。
▶第19 ~ 28行:对应图3中②处。由一个Row容器及其内包含的一个Text标签和Image图像组成。
▶第24 ~ 27行:根据showReason的不同,②处的Image显示不同的图像。
▶第32行:当②处的Row容器被点击时,showReason取非。由于showReason是用@State修饰的状态,其改变将引起UI的刷新。
▶第33 ~ 43行:当showReason为真时,生成并显示思维链内容,见图3中④处。
▶第36 ~ 38行:Row容器内包一个Text组件,以字体颜色0x777777(灰色)显示思维链文本。
▶第40 ~ 41行:在Row容器的左边界形成2.vp宽,颜色为0xe5e5e5的灰色边线,见图3中③处。相较于0x777777, 0xe5e5e5由更多的红、绿、蓝光组成,它是更浅的灰色(即更接近白)。
▶第46 ~ 51行:当content不空时,生成Row容器及Text组件以字体颜色0x000000显示消息内容(content)。0x000000表示0红0绿0蓝,它是黑色。

图3 AI消息行的结构
在文件entry/…/src/ui.cj文件内,我们找到了ChatAvatar函数的代码:
1 2 3 4
| @Builder public func ChatAvatar(src!: CJResource, size!: Int64 = 50) { Image(src).width(size).height(size).borderRadius(size / 2) }
|
在经@Builder宏修饰后,ChatAvatar函数可以生成UI组件,在上述代码的第3行,它生成了一个Image(图像)组件,其内显示由参数src所指定的图像资源。