1# 一多开发实例(短信)
2
3
4本章从系统预置的应用中,选择短信应用作为典型的案例,从页面开发和工程结构的角度,介绍"一多"的具体实践。系统的产品形态在不断丰富中,当前主要有默认设备和平板两种产品形态,本章的具体实践也将围绕这两种产品形态展开。
5
6
7## 概览
8
9短信是系统中预置的应用,主要包含信息查看、发送短信、接收短信、短信送达报告、删除短信等功能。在不同类型设备上,短信应用的功能完全相同,故短信应用适合使用[部署模型A](introduction.md#部署模型)(即:不同类型的设备上安装运行相同的HAP或HAP组合)。
10
11本案例中,在会话详情页面利用[方舟开发框架](introduction.md#方舟开发框架)提供的“一多”能力,用一套代码同时适配默认设备和平板。
12
13
14### 工程结构
15
16短信应用的工程结构如下图所示,当前该应用的功能较少,所以直接使用了DevEco Studio创建出的默认工程结构。具体采用何种形式的工程结构,并不影响应用的开发。但是使用推荐的工程结构,目录结构更清晰,拓展性也更好。
17
18短信应用UI相关的逻辑集中在views和pages两个目录,分别存放公共组件及页面。当前短信应用主要包含如下页面:
19
20- 信息列表页面:首页,展示信息列表。
21
22- 通知信息列表页面:将通知类信息集中在一起展示,与信息列表页面类似。
23
24- 会话详情页面:展示与某联系人的所有信息往来。
25
26- 报告详情页面:信息发送报告的详情页面。
27
28- 设置页面:消息设置页面,如是否展示送达报告等。
29
30
31```
32/Mms/
33 ├── doc                                        # 资料
34 ├── entry
35 │   └── src
36 │       └── main
37 │           ├── resources                      # 资源配置文件存放目录
38 │           ├── config.json                    # 全局配置文件
39 │           └── ets                            # ets代码目录
40 │               ├── ServiceAbility             # 后台常驻服务
41 │               └── default                    # 业务代码目录
42 │                   ├── data                   # 自定义数据类型
43 │                   ├── model                  # 对接数据库
44 │                   ├── pages                  # 所有页面
45 │                   │   ├── conversation       # 会话详情页面
46 │                   │   ├── conversationlist   # 信息列表页面
47 │                   │   ├── index              # 初始页面
48 │                   │   ├── info_msg           # 通知信息列表页面
49 │                   │   ├── query_report       # 报告详情页面
50 │                   │   └── settings           # 设置页面
51 │                   ├── service                # 业务逻辑
52 │                   ├── utils                  # 工具类
53 │                   ├── views                  # 自定义组件
54 │                   └── app.ets                # 应用生命周期
55 ├── signs                                      # 签名
56 └── LICENSE
57```
58
59短信应用在开发阶段,采用了一层工程结构。由于功能较为简单,所以并没有规划共用的feature和common目录,仅采用了一层product目录。
60
61- 业务形态层(product)
62  该目录采用DevEco Studio工程默认创建的entry目录,开发者可根据需要在创建Module时自行更改该目录名。不同产品形态,编译出相同的短信HAP。
63
64
65
66## 会话详情页面
67
68
69### 页面结构
70
71| 默认设备                                     | 平板                                       |
72| ---------------------------------------- | ---------------------------------------- |
73| ![overview_default](figures/overview_default.png) | ![overview_tablet](figures/overview_tablet.png) |
74
75会话详情页面在默认设备和平板上的样式如上图所示,会话详情页面可以划分为三个部分:
76
77| 页面组成  | 介绍                                       |
78| ----- | ---------------------------------------- |
79| 顶部标题栏 | ![zh-cn_image_0000001335699774](figures/zh-cn_image_0000001335699774.jpg) |
80| 信息列表  | ![zh-cn_image_0000001386060209](figures/zh-cn_image_0000001386060209.jpg) |
81| 底部输入栏 | ![zh-cn_image_0000001386179873](figures/zh-cn_image_0000001386179873.jpg) |
82
83接下来我们详细介绍各部分的实现。
84
85> **说明:**
86> 为了方便理解,我们对会话详情页面做了一定的精简,本小节仅介绍会话详情页面最基础的实现。
87
88
89### 顶部标题栏
90
91| 默认设备                                     | 平板                                       |
92| ---------------------------------------- | ---------------------------------------- |
93| ![zh-cn_image_0000001335539986](figures/zh-cn_image_0000001335539986.jpg) | ![top_title_tablet](figures/top_title_tablet.png) |
94
95顶部标题栏是一个简单的行布局,包含返回图标、联系人头像、联系人姓名和号码、拨号图标、设置图标共5个元素。其中,联系人姓名和号码以列布局的形式放在一起。
96
97在默认设备和平板上,顶部标题栏的组件结构是相同的,仅联系人姓名和号码与拨号图标的间距不同。回顾方舟开发框架一多能力介绍,这个场景可以借助Blank组件使用拉伸能力。
98
99  我们先实现联系人姓名和号码,用Flex组件作为父容器,其包含两个Text子组件,分别用于存放联系人姓名和号码。Flex组件的属性设置如下:
100- direction: FlexDirection.Column:子组件在Flex容器上以列的方式排布,即主轴是垂直方向。
101
102- justifyContent: FlexAlign.Center:子组件在Flex容器主轴(垂直方向)上居中对齐。
103
104- alignItems: ItemAlign.Start:子组件在Flex容器交叉轴(水平方向)上首部对齐。
105
106可以查看[Flex组件](../../reference/apis-arkui/arkui-ts/ts-container-flex.md)及[Text组件](../../reference/apis-arkui/arkui-ts/ts-basic-components-text.md)了解这两个组件各个属性的含义及详细用法。
107
108| 默认设备                                     | 平板                                       |
109| ---------------------------------------- | ---------------------------------------- |
110| ![contact_details_default](figures/contact_details_default.png) | ![contact_details_tablet](figures/contact_details_tablet.png) |
111
112
113```ts
114@Entry
115@Component
116 struct TopArea {
117   build() {
118     Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
119       alignItems: ItemAlign.Start}) {
120       Text('张三').fontSize(16).fontColor("#182431")
121       Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
122     }
123   }
124 }
125```
126
127接下来我们通过width属性和height属性设置四个图标的宽高(详见[尺寸设置](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)),并将它们与联系人姓名和电话以及Blank组件一起放到Flex父容器中。为了便于查看效果,对顶部标题栏设置了淡蓝色的背景色。
128
129| 默认设备                                     | 平板                                       |
130| ---------------------------------------- | ---------------------------------------- |
131| ![top_title_blank_default](figures/top_title_blank_default.png) | ![top_title_blank_tablet](figures/top_title_blank_tablet.png) |
132
133
134```ts
135@Entry
136@Component
137 struct TopArea {
138   build() {
139     Flex({ alignItems: ItemAlign.Center }) {
140       Image($r('app.media.back')) //在应用的resources/base/media目录放置名为back的资源文件
141         .width(24)
142         .height(24)
143       Image($r('app.media.contact')) //在应用的resources/base/media目录放置名为contact的资源文件
144         .width(40)
145         .height(40)
146       Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
147         alignItems: ItemAlign.Start}) {
148         Text('张三').fontSize(16).fontColor("#182431")
149         Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
150       }
151       Blank()                  // 拉伸能力
152       Image($r("app.media.phone")) //在应用的resources/base/media目录放置名为phone的资源文件
153         .width(24)
154         .height(24)
155       Image($r('app.media.dots')) //在应用的resources/base/media目录放置名为dots的资源文件
156         .width(24)
157         .height(24)
158     }
159     .width('100%')
160     .height(56)
161     .backgroundColor('#87CEFA')  // 顶部标题栏背景色,仅用于开发测试
162   }
163 }
164```
165
166当前标题栏中子组件的布局同预期还有些差异,接下来通过margin属性,设置各个元素的左右间距(详见[尺寸设置](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md))。如下图所示,最终顶部工具栏在默认设备和平板上都可以达到预期显示效果。
167
168| 默认设备                                     | 平板                                       |
169| ---------------------------------------- | ---------------------------------------- |
170| ![top_title_done_default](figures/top_title_done_default.png) | ![top_title_done_tablet](figures/top_title_done_tablet.png) |
171
172
173```ts
174@Entry
175@Component
176 struct TopArea {
177   build() {
178     Flex({ alignItems: ItemAlign.Center }) {
179       Image($r('app.media.back')) //在应用的resources/base/media目录放置名为back的资源文件
180         .width(24)
181         .height(24)
182         .margin({ left:24 })             // 设置间距
183       Image($r('app.media.contact')) //在应用的resources/base/media目录放置名为contact的资源文件
184         .width(40)
185         .height(40)
186         .margin({ left:16, right:16 })  // 设置间距
187       Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
188         alignItems: ItemAlign.Start}) {
189         Text('张三').fontSize(16).fontColor("#182431")
190         Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
191       }
192       Blank()
193       Image($r("app.media.phone")) //在应用的resources/base/media目录放置名为phone的资源文件
194         .width(24)
195         .height(24)
196       Image($r('app.media.dots')) //在应用的resources/base/media目录放置名为dots的资源文件
197         .width(24)
198         .height(24)
199         .margin({ left:16, right:24 })  // 设置间距
200     }
201     .width('100%')
202     .height(56)
203     .backgroundColor('#87CEFA')           // 顶部标题栏背景色,仅用于开发测试
204   }
205 }
206```
207
208
209### 底部输入栏
210
211有了顶部工具栏的开发经验,可以发现底部输入栏的结构更为简单,它同样以Flex组件作为父容器,同时包含文本输入框(请访问[文本输入组件](../../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md)查看详细介绍)和消息发送图标两个子节点。
212
213![zh-cn_image_0000001335380378](figures/zh-cn_image_0000001335380378.jpg)
214
215为了便于查看的效果,我们同样给底部输入栏设置了淡蓝色到背景色。注意这里有一个特殊的地方,我们给TextArea设置了flexGrow(1)属性。flexGrow属性仅在父组件是Flex组件时生效,表示Flex容器的剩余空间分配给此属性所在的组件的比例,flexGrow(1)表示父容器的剩余空间全部分配给此组件,详见[Flex布局](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-flex-layout.md)。
216
217| 默认设备                                     | 平板                                       |
218| ---------------------------------------- | ---------------------------------------- |
219| ![bottom_input_default](figures/bottom_input_default.png) | ![bottom_input_tablet](figures/bottom_input_tablet.png) |
220
221
222```ts
223@Entry
224@Component
225 struct BottomArea {
226   build() {
227     Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
228       TextArea({ placeholder:'短信' })
229         .placeholderColor("#99000000")
230         .caretColor("#007DFF")
231         .backgroundColor("#F1F3F5")
232         .borderRadius(20)
233         .height(40)
234         .flexGrow(1)           // 将父容器的剩余空间全部分配给此组件
235
236       Image($r("app.media.send")) //在应用的resources/base/media目录放置名为send的资源文件
237         .height(36)
238         .width(36)
239         .opacity(0.4)
240         .margin({ left:12 })
241     }
242     .height(72)
243     .width('100%')
244     .padding({ left:24, right:24, bottom:8, top:8 })
245     .backgroundColor('#87CEFA')  // 底部输入栏背景色,仅用于开发测试
246   }
247 }
248```
249
250
251### 信息列表
252
253观察信息列表区域,可以发现它是由一个个消息气泡组成的,另外消息气泡在默认设备和平板上的布局有差异。本小节将围绕如下两个主题介绍如何实现消息列表。
254
255- 如何实现自定义消息气泡组件。
256
257- 如何在默认设备和平板上自适应布局。
258
259  | 默认设备                                     | 平板                                       |
260  | ---------------------------------------- | ---------------------------------------- |
261  | ![zh-cn_image_0000001386060209](figures/zh-cn_image_0000001386060209.jpg) | ![message_list_tablet_default](figures/message_list_tablet_default.png) |
262
263**消息气泡**
264
265先做一个最简单的消息气泡,通过borderRadius属性可以设置边框的圆角半径(详见[边框设置](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-border.md))。
266
267| 默认设备                                     | 平板                                       |
268| ---------------------------------------- | ---------------------------------------- |
269| ![message_bubble_basic_default](figures/message_bubble_basic_default.png) | ![message_bubble_basic_tablet](figures/message_bubble_basic_tablet.png) |
270
271
272```ts
273@Entry
274@Component
275struct MessageBubble {
276  private content: string = "Introduction"
277
278  build() {
279    Column() {
280      Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
281        Text(this.content)
282            .fontSize(16)
283            .lineHeight(21)
284            .padding({ left: 12, right: 12, top: 8, bottom: 8 })
285            .backgroundColor("#C0EBDF")
286            .borderRadius(24)
287            .fontColor("#182431")
288      }.width('100%')
289    }
290    .margin({left: 24, right: 24 })
291    .backgroundColor('#87CEFA')  // 消息背景色,仅用于开发和测试
292  }
293}
294```
295
296注意这个简单的消息气泡,左上角(或右上角)的样式,与实际期望不符。我们先修改发送消息右上角的样式,接收消息左上角的实现与之类似。
297
298[Stack组件](../../reference/apis-arkui/arkui-ts/ts-container-stack.md)是一个堆叠容器,其子组件按照轴方向依次堆叠,后一个子组件覆盖前一个子组件。通过其alignContent接口,可以设置子组件在容器内的对齐方式,如alignContent: Alignment.TopStart代表子组件从左上角对齐。
299
300| 默认设备                                     | 平板                                       |
301| ---------------------------------------- | ---------------------------------------- |
302| ![message_bubble_radius_default](figures/message_bubble_radius_default.png) | ![message_bubble_radius_tablet](figures/message_bubble_radius_tablet.png) |
303
304
305```ts
306@Entry
307@Component
308struct MessageBubble {
309  private content: string = "Introduction"
310
311  build() {
312    Column() {
313      Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
314        Stack({ alignContent: Alignment.TopEnd }) {  // 在左上角堆叠一个小色块
315          Column()
316            .backgroundColor("#C0EBDF")
317            .borderRadius(4)
318            .width(24)
319            .height(24)
320          Text(this.content)
321            .fontSize(16)
322            .lineHeight(21)
323            .padding({ left: 12, right: 12, top: 8, bottom: 8 })
324            .backgroundColor("#C0EBDF")
325            .borderRadius(24)
326            .fontColor("#182431")
327        }
328      }.width('100%')
329    }
330    .margin({left: 24, right: 24 })
331    .backgroundColor('#87CEFA')  // 消息背景色,仅用于开发和测试
332  }
333}
334```
335
336接下来我们在消息气泡下方加上时间显示,如下图所示,一个消息气泡自定义组件就基本完成了。
337
338| 默认设备                                     | 平板                                       |
339| ---------------------------------------- | ---------------------------------------- |
340| ![message_bubble_recv_default](figures/message_bubble_recv_default.png) | ![message_bubble_recv_tablet](figures/message_bubble_recv_tablet.png) |
341
342
343```ts
344@Entry
345@Component
346struct MessageBubble {
347  private content: string = "Introduction"
348  private time: string = "上午 10:35"
349
350  build() {
351    Column() {
352      Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
353        Stack({ alignContent: Alignment.TopEnd }) {
354          Column()
355            .backgroundColor("#C0EBDF")
356            .borderRadius(4)
357            .width(24)
358            .height(24)
359          Text(this.content)
360            .fontSize(16)
361            .lineHeight(21)
362            .padding({ left: 12, right: 12, top: 8, bottom: 8 })
363            .backgroundColor("#C0EBDF")
364            .borderRadius(24)
365            .fontColor("#182431")
366        }
367      }.width('100%')
368
369      // 在消息气泡底部增加时间显示
370      Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
371        justifyContent: FlexAlign.End}) {
372        Text(this.time)
373          .textAlign(TextAlign.Start)
374          .fontSize(10)
375          .lineHeight(13)
376          .fontColor("#99182431")
377      }.width('100%').margin({ left: 12, right: 24 })
378    }
379    .margin({left: 24, right: 24 })
380    .backgroundColor('#87CEFA')  // 消息背景色,仅用于开发和测试
381  }
382}
383```
384
385发送出的消息和接收到的消息的消息气泡结构基本一致,可以通过增加一个标志位,让两种消息共用MessageBubble这个自定义组件,代码如下所示。将这个标志位设置true,可以查看接收消息的效果。
386
387| 默认设备                                     | 平板                                       |
388| ---------------------------------------- | ---------------------------------------- |
389| ![message_bubble_send_default](figures/message_bubble_send_default.png) | ![message_bubble_send_tablet](figures/message_bubble_send_tablet.png) |
390
391
392```ts
393@Entry
394@Component
395 struct MessageBubble {
396   private isReceived:boolean = true // 通过标志位,判断是发送or接收场景,进而使用不同的样式
397   private content:string = "Introduction"
398   private time:string = "今天 10:35"
399
400   build() {
401     Column() {
402       Flex({ justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End,
403         alignItems: ItemAlign.Center }) {
404         Stack({ alignContent:this.isReceived? Alignment.TopStart: Alignment.TopEnd }) {
405           Column()
406             .backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
407             .borderRadius(4)
408             .width(24)
409             .height(24)
410           Text(this.content)
411             .fontSize(16)
412             .lineHeight(21)
413             .padding({ left:12, right:12, top:8, bottom:8 })
414             .backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
415             .borderRadius(24)
416             .fontColor("#182431")
417         }
418       }.width('100%')
419
420       Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
421         justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End }) {
422         Text(this.time)
423           .textAlign(TextAlign.Start)
424           .fontSize(10)
425           .lineHeight(13)
426           .fontColor("#99182431")
427       }.width('100%')
428       .margin({ left:this.isReceived?12:0, right:this.isReceived?0:12 })
429     }
430     .margin({left:24, right:24 })
431     .backgroundColor('#87CEFA')  // 消息背景色,仅用于开发和测试
432   }
433 }
434```
435
436**栅格布局**
437
438回顾方舟开发框架一多能力,消息气泡在默认设备和平板上布局不同,可以借助栅格布局来解决。为了方便测试,我们预定义一个全局数组。
439
440
441```ts
442 interface globalMessageItem {
443    time:string,
444    content:string,
445    isReceived:boolean
446 }
447
448const globalMessageList:globalMessageItem[] = [
449  {
450    time:'上午 10:20',
451    content:'项目介绍',
452    isReceived:false
453  },
454  {
455    time:'上午 10:28',
456    content:'"一次开发,多端部署"支撑开发者快速高效的开发支持多种终端设备形态的应用,实现对不同设备兼容的同时,提供跨设备的流转、迁移和协同的分布式体验',
457    isReceived:false
458  },
459  {
460    time:'上午 10:32',
461    content:'系统能力',
462    isReceived:true
463  },
464  {
465    time:'上午 10:35',
466    content:'系统能力(即SystemCapability,缩写为SysCap)指操作系统中每一个相对独立的特性,如蓝牙,WIFI,NFC,摄像头等,都是系统能力之一。每个系统能力对应多个API,随着目标设备是否支持该系统能力共同存在或消失。',
467    isReceived:true
468  }
469]
470```
471
472结合[栅格组件](../../reference/apis-arkui/arkui-ts/ts-container-gridcontainer.md)的定义,考虑我们当前的实际场景,GridRow的各参数设置如下。
473
474- columns:栅格组件中的列数,当前场景默认12列即可。
475
476- gutter:栅格布局列间距,当前场景未使用该参数,默认设置为0即可。
477
478- margin: 栅格布局两侧间距,在开发消息气泡组件时,已经设置了左右间距,故该属性也默认配置为0。
479
480栅格中仅包含我们自定义的消息气泡组件,该组件在各断点上的参数配置如下。
481
482| 断点   | 窗口宽度(vp)        | 栅格总列数 | 消息气泡占用的列数 | 接收场景偏移的列数 | 发送场景偏移的列数 |
483| ---- | --------------- | ----- | --------- | --------- | --------- |
484| sm   | [320, 600) | 12    | 12        | 0         | 0         |
485| md   | [600, 840) | 12    | 8         | 0         | 4         |
486| lg   | [840, +∞)  | 12    | 8         | 0         | 4         |
487
488| 默认设备                                     | 平板                                       |
489| ---------------------------------------- | ---------------------------------------- |
490| ![message_list_default](figures/message_list_default.png) | ![message_list_tablet](figures/message_list_tablet.png) |
491
492
493```ts
494import  { globalMessageList } from "../data/globalMessageList"; //将globalMessageList文件export后导入;
495import  { MessageBubble } from "./MessageBubble"; //将文件MessageBubble去除@entry作为组件export后导入;
496@Component
497export default struct MessageItem {
498  private isReceived?: boolean
499  private content?: string
500  private time?: string
501
502  build() {
503    GridRow() {
504      GridCol({span: {sm: 12, md: 8, lg: 8},
505        offset: {sm: 0, md: this.isReceived? 0 : 4, lg: this.isReceived? 0 : 4}}) {
506        Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.End }) {
507          MessageBubble({
508            isReceived: this.isReceived,
509            content: this.content,
510            time: this.time
511          })
512        }
513      }
514    }
515  }
516}
517
518@Entry
519@Component
520struct Conversation {
521  build() {
522    Column() {                      // 验证效果
523       MessageItem({
524        isReceived: globalMessageList[1].isReceived,
525        content: globalMessageList[1].content,
526        time: globalMessageList[1].time
527      })
528      MessageItem({
529        isReceived: globalMessageList[3].isReceived,
530        content: globalMessageList[3].content,
531        time: globalMessageList[3].time
532      })
533    }.backgroundColor('#87CEFA')    // 消息背景色,仅用于开发和测试
534  }
535}
536```
537
538
539### 组合成型
540
541现在会话详情页面的顶部标题栏、信息列表及底部输入栏都已经准备完毕,将这三部分组合起来即可得到完整的页面。
542
543- 通过[Flex组件](../../reference/apis-arkui/arkui-ts/ts-container-flex.md)将三个部分组合起来,注意justifyContent: FlexAlign.SpaceBetween配置项是将Flex组件中的元素按照主轴方向均匀分配,其中第一个元素与顶部对齐,最后一个元素与底部对齐。
544
545- 通过[List组件](../../reference/apis-arkui/arkui-ts/ts-container-list.md)和[ForEach语法](../../quick-start/arkts-rendering-control-foreach.md),显示整个消息列表。
546
547  | 默认设备                                     | 平板                                       |
548  | ---------------------------------------- | ---------------------------------------- |
549  | ![overview_default](figures/overview_default.png) | ![overview_tablet](figures/overview_tablet.png) |
550
551
552```ts
553import { globalMessageList,globalMessageItem} from "../data/globalMessageList"; //将globalMessageList、globalMessageItem文件export后导入;
554import { MessageItem } from "./MessageItem"; //将文件MessageItem去除@entry作为组件export后导入;
555import { BottomArea } from "./BottomArea"; //将文件BottomArea去除@entry作为组件export后导入;
556import {TopArea } from "./TopArea"; //将文件TopArea去除@entry作为组件export后导入;
557 @Entry
558 @Component
559 struct Conversation {
560   build() {
561     Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start,
562       justifyContent: FlexAlign.SpaceBetween }) {
563       Column() {
564         TopArea()   // 顶部标题栏
565         List() {    // 消息列表
566           ForEach(globalMessageList, (item:globalMessageItem, index) => {
567             ListItem() {
568               MessageItem({
569                 isReceived: item.isReceived,
570                 content: item.content,
571                 time: item.time
572               })
573           }})
574         }
575         .listDirection(Axis.Vertical)
576         .edgeEffect(EdgeEffect.Spring)
577       }
578       BottomArea()  // 底部输入栏
579     }
580     .backgroundColor("#F1F3F5")
581     .width('100%')
582     .height('100%')
583   }
584 }
585```
586
587短信应用在默认设备和平板上的功能完全相同,因此选择了部署模型A。借助方舟开发框架一多能力,短信应用实现了在默认设备和平板上共用同一份代码,同时自然也共用安装包。
588
589在实际开发过程中,会话详情页面需要从底层做数据交互,同时还要支持信息选择、信息删除、信息发送状态、输入框与输入法联动等等功能,会比本小节中介绍的基础版本复杂很多。
590
591## 相关实例
592
593基于短信,可参考以下实例:
594
595- [信息应用](https://gitee.com/openharmony/applications_mms/tree/master)
596
597
598