消息行(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所指定的图像资源。