一多開(kāi)發(fā)實(shí)例(短信)
本章從系統(tǒng)預(yù)置的應(yīng)用中,選擇短信應(yīng)用作為典型的案例,從頁(yè)面開(kāi)發(fā)和工程結(jié)構(gòu)的角度,介紹"一多"的具體實(shí)踐。系統(tǒng)的產(chǎn)品形態(tài)在不斷豐富中,當(dāng)前主要有默認(rèn)設(shè)備和平板兩種產(chǎn)品形態(tài),本章的具體實(shí)踐也將圍繞這兩種產(chǎn)品形態(tài)展開(kāi)。
概覽
短信是系統(tǒng)中預(yù)置的應(yīng)用,主要包含信息查看、發(fā)送短信、接收短信、短信送達(dá)報(bào)告、刪除短信等功能。在不同類型設(shè)備上,短信應(yīng)用的功能完全相同,故短信應(yīng)用適合使用[部署模型A](即:不同類型的設(shè)備上安裝運(yùn)行相同的HAP或HAP組合)。
本案例中,在會(huì)話詳情頁(yè)面利用[方舟開(kāi)發(fā)框架]提供的“一多”能力,用一套代碼同時(shí)適配默認(rèn)設(shè)備和平板。
工程結(jié)構(gòu)
短信應(yīng)用的工程結(jié)構(gòu)如下圖所示,當(dāng)前該應(yīng)用的功能較少,所以直接使用了DevEco Studio創(chuàng)建出的默認(rèn)工程結(jié)構(gòu)。具體采用何種形式的工程結(jié)構(gòu),并不影響應(yīng)用的開(kāi)發(fā)。但是使用推薦的工程結(jié)構(gòu),目錄結(jié)構(gòu)更清晰,拓展性也更好。
短信應(yīng)用UI相關(guān)的邏輯集中在views和pages兩個(gè)目錄,分別存放公共組件及頁(yè)面。當(dāng)前短信應(yīng)用主要包含如下頁(yè)面:
- 信息列表頁(yè)面:首頁(yè),展示信息列表。
- 通知信息列表頁(yè)面:將通知類信息集中在一起展示,與信息列表頁(yè)面類似。
- 會(huì)話詳情頁(yè)面:展示與某聯(lián)系人的所有信息往來(lái)。
- 報(bào)告詳情頁(yè)面:信息發(fā)送報(bào)告的詳情頁(yè)面。
- 設(shè)置頁(yè)面:消息設(shè)置頁(yè)面,如是否展示送達(dá)報(bào)告等。
- 開(kāi)發(fā)前請(qǐng)熟悉鴻蒙開(kāi)發(fā)指導(dǎo)文檔 :[
gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
/Mms/
├── doc # 資料
├── entry
│ └── src
│ └── main
│ ├── resources # 資源配置文件存放目錄
│ ├── config.json # 全局配置文件
│ └── ets # ets代碼目錄
│ ├── ServiceAbility # 后臺(tái)常駐服務(wù)
│ └── default # 業(yè)務(wù)代碼目錄
│ ├── data # 自定義數(shù)據(jù)類型
│ ├── model # 對(duì)接數(shù)據(jù)庫(kù)
│ ├── pages # 所有頁(yè)面
│ │ ├── conversation # 會(huì)話詳情頁(yè)面
│ │ ├── conversationlist # 信息列表頁(yè)面
│ │ ├── index # 初始頁(yè)面
│ │ ├── info_msg # 通知信息列表頁(yè)面
│ │ ├── query_report # 報(bào)告詳情頁(yè)面
│ │ └── settings # 設(shè)置頁(yè)面
│ ├── service # 業(yè)務(wù)邏輯
│ ├── utils # 工具類
│ ├── views # 自定義組件
│ └── app.ets # 應(yīng)用生命周期
├── signs # 簽名
└── LICENSE
短信應(yīng)用在開(kāi)發(fā)階段,采用了一層工程結(jié)構(gòu)。由于功能較為簡(jiǎn)單,所以并沒(méi)有規(guī)劃共用的feature和common目錄,僅采用了一層product目錄。
- 業(yè)務(wù)形態(tài)層(product) 該目錄采用IDE工程默認(rèn)創(chuàng)建的entry目錄,開(kāi)發(fā)者可根據(jù)需要在創(chuàng)建Module時(shí)自行更改該目錄名。不同產(chǎn)品形態(tài),編譯出相同的短信HAP。
會(huì)話詳情頁(yè)面
頁(yè)面結(jié)構(gòu)
會(huì)話詳情頁(yè)面在默認(rèn)設(shè)備和平板上的樣式如上圖所示,會(huì)話詳情頁(yè)面可以劃分為三個(gè)部分:
接下來(lái)我們?cè)敿?xì)介紹各部分的實(shí)現(xiàn)。
說(shuō)明: 為了方便理解,我們對(duì)會(huì)話詳情頁(yè)面做了一定的精簡(jiǎn),本小節(jié)僅介紹會(huì)話詳情頁(yè)面最基礎(chǔ)的實(shí)現(xiàn)。
頂部標(biāo)題欄
頂部標(biāo)題欄是一個(gè)簡(jiǎn)單的行布局,包含返回圖標(biāo)、聯(lián)系人頭像、聯(lián)系人姓名和號(hào)碼、撥號(hào)圖標(biāo)、設(shè)置圖標(biāo)共5個(gè)元素。其中,聯(lián)系人姓名和號(hào)碼以列布局的形式放在一起。
在默認(rèn)設(shè)備和平板上,頂部標(biāo)題欄的組件結(jié)構(gòu)是相同的,僅聯(lián)系人姓名和號(hào)碼與撥號(hào)圖標(biāo)的間距不同?;仡櫡街坶_(kāi)發(fā)框架一多能力介紹,這個(gè)場(chǎng)景可以借助Blank組件使用拉伸能力。
我們先實(shí)現(xiàn)聯(lián)系人姓名和號(hào)碼,用Flex組件作為父容器,其包含兩個(gè)Text子組件,分別用于存放聯(lián)系人姓名和號(hào)碼。Flex組件的屬性設(shè)置如下:
- direction: FlexDirection.Column:子組件在Flex容器上以列的方式排布,即主軸是垂直方向。
- justifyContent: FlexAlign.Center:子組件在Flex容器主軸(垂直方向)上居中對(duì)齊。
- alignItems: ItemAlign.Start:子組件在Flex容器交叉軸(水平方向)上首部對(duì)齊。
可以查看[Flex組件]及[Text組件]了解這兩個(gè)組件各個(gè)屬性的含義及詳細(xì)用法。
@Component
struct TopArea {
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('張三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
}
}
接下來(lái)我們通過(guò)width屬性和height屬性設(shè)置四個(gè)圖標(biāo)的寬高,并將它們與聯(lián)系人姓名和電話以及Blank組件一起放到Flex父容器中。為了便于查看效果,對(duì)頂部標(biāo)題欄設(shè)置了淡藍(lán)色的背景色。
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
Image($r('app.media.contact'))
.width(40)
.height(40)
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('張三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank() // 拉伸能力
Image($r("app.media.phone"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 頂部標(biāo)題欄背景色,僅用于開(kāi)發(fā)測(cè)試
}
}
當(dāng)前標(biāo)題欄中子組件的布局同預(yù)期還有些差異,接下來(lái)通過(guò)margin屬性,設(shè)置各個(gè)元素的左右間距。如下圖所示,最終頂部工具欄在默認(rèn)設(shè)備和平板上都可以達(dá)到預(yù)期顯示效果。
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
.margin({ left:24 }) // 設(shè)置間距
Image($r('app.media.contact'))
.width(40)
.height(40)
.margin({ left:16, right:16 }) // 設(shè)置間距
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('張三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank()
Image($r("app.media.phone"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
.margin({ left:16, right:24 }) // 設(shè)置間距
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 頂部標(biāo)題欄背景色,僅用于開(kāi)發(fā)測(cè)試
}
}
底部輸入欄
有了頂部工具欄的開(kāi)發(fā)經(jīng)驗(yàn),可以發(fā)現(xiàn)底部輸入欄的結(jié)構(gòu)更為簡(jiǎn)單,它同樣以Flex組件作為父容器,同時(shí)包含文本輸入框)和消息發(fā)送圖標(biāo)兩個(gè)子節(jié)點(diǎn)。
為了便于查看的效果,我們同樣給底部輸入欄設(shè)置了淡藍(lán)色到背景色。注意這里有一個(gè)特殊的地方,我們給TextArea設(shè)置了flexGrow(1)屬性。flexGrow屬性僅在父組件是Flex組件時(shí)生效,表示Flex容器的剩余空間分配給此屬性所在的組件的比例,flexGrow(1)表示父容器的剩余空間全部分配給此組件。
@Component
struct BottomArea {
build() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
TextArea({ placeholder:'短信' })
.placeholderColor("#99000000")
.caretColor("#007DFF")
.backgroundColor("#F1F3F5")
.borderRadius(20)
.height(40)
.flexGrow(1) // 將父容器的剩余空間全部分配給此組件
Image($r("app.media.send"))
.height(36)
.width(36)
.opacity(0.4)
.margin({ left:12 })
}
.height(72)
.width('100%')
.padding({ left:24, right:24, bottom:8, top:8 })
.backgroundColor('#87CEFA') // 底部輸入欄背景色,僅用于開(kāi)發(fā)測(cè)試
}
}
信息列表
觀察信息列表區(qū)域,可以發(fā)現(xiàn)它是由一個(gè)個(gè)消息氣泡組成的,另外消息氣泡在默認(rèn)設(shè)備和平板上的布局有差異。本小節(jié)將圍繞如下兩個(gè)主題介紹如何實(shí)現(xiàn)消息列表。
- 如何實(shí)現(xiàn)自定義消息氣泡組件。
- 如何在默認(rèn)設(shè)備和平板上自適應(yīng)布局。
消息氣泡
先做一個(gè)最簡(jiǎn)單的消息氣泡,通過(guò)borderRadius屬性可以設(shè)置邊框的圓角半徑。
@Component
struct MessageBubble {
private content: string = "Introduction"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,僅用于開(kāi)發(fā)和測(cè)試
}
}
注意這個(gè)簡(jiǎn)單的消息氣泡,左上角(或右上角)的樣式,與實(shí)際期望不符。我們先修改發(fā)送消息右上角的樣式,接收消息左上角的實(shí)現(xiàn)與之類似。
[Stack組件]是一個(gè)堆疊容器,其子組件按照軸方向依次堆疊,后一個(gè)子組件覆蓋前一個(gè)子組件。通過(guò)其alignContent接口,可以設(shè)置子組件在容器內(nèi)的對(duì)齊方式,如alignContent: Alignment.TopStart代表子組件從左上角對(duì)齊。
@Component
struct MessageBubble {
private content: string = "Introduction"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) { // 在左上角堆疊一個(gè)小色塊
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,僅用于開(kāi)發(fā)和測(cè)試
}
}
接下來(lái)我們?cè)谙馀菹路郊由蠒r(shí)間顯示,如下圖所示,一個(gè)消息氣泡自定義組件就基本完成了。
@Component
struct MessageBubble {
private content: string = "Introduction"
private time: string = "上午 10:35"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) {
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
// 在消息氣泡底部增加時(shí)間顯示
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent: FlexAlign.End}) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%').margin({ left: 12, right: 24 })
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,僅用于開(kāi)發(fā)和測(cè)試
}
}
發(fā)送出的消息和接收到的消息的消息氣泡結(jié)構(gòu)基本一致,可以通過(guò)增加一個(gè)標(biāo)志位,讓兩種消息共用MessageBubble這個(gè)自定義組件,代碼如下所示。將這個(gè)標(biāo)志位設(shè)置true,可以查看接收消息的效果。
@Component
struct MessageBubble {
private isReceived:boolean = true// 通過(guò)標(biāo)志位,判斷是發(fā)送or接收?qǐng)鼍埃M(jìn)而使用不同的樣式
private content:string = "Introduction"
private time:string = "今天 10:35"
build() {
Column() {
Flex({ justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End,
alignItems: ItemAlign.Center }) {
Stack({ alignContent:this.isReceived? Alignment.TopStart: Alignment.TopEnd }) {
Column()
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left:12, right:12, top:8, bottom:8 })
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End }) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%')
.margin({ left:this.isReceived?12:0, right:this.isReceived?0:12 })
}
.margin({left:24, right:24 })
.backgroundColor('#87CEFA') // 消息背景色,僅用于開(kāi)發(fā)和測(cè)試
}
}
柵格布局
回顧方舟開(kāi)發(fā)框架一多能力,消息氣泡在默認(rèn)設(shè)備和平板上布局不同,可以借助柵格布局來(lái)解決。為了方便測(cè)試,我們預(yù)定義一個(gè)全局?jǐn)?shù)組。
interface globalMessageItem {
time:string,
content:string,
isReceived:boolean
}
const globalMessageList:globalMessageItem[] = [
{
time:'上午 10:20',
content:'項(xiàng)目介紹',
isReceived:false
},
{
time:'上午 10:28',
content:'"一次開(kāi)發(fā),多端部署"支撐開(kāi)發(fā)者快速高效的開(kāi)發(fā)支持多種終端設(shè)備形態(tài)的應(yīng)用,實(shí)現(xiàn)對(duì)不同設(shè)備兼容的同時(shí),提供跨設(shè)備的流轉(zhuǎn)、遷移和協(xié)同的分布式體驗(yàn)',
isReceived:false
},
{
time:'上午 10:32',
content:'系統(tǒng)能力',
isReceived:true
},
{
time:'上午 10:35',
content:'系統(tǒng)能力(即SystemCapability,縮寫為SysCap)指操作系統(tǒng)中每一個(gè)相對(duì)獨(dú)立的特性,如藍(lán)牙,WIFI,NFC,攝像頭等,都是系統(tǒng)能力之一。每個(gè)系統(tǒng)能力對(duì)應(yīng)多個(gè)API,隨著目標(biāo)設(shè)備是否支持該系統(tǒng)能力共同存在或消失。',
isReceived:true
}
]
結(jié)合[柵格組件]的定義,考慮我們當(dāng)前的實(shí)際場(chǎng)景,GridRow的各參數(shù)設(shè)置如下。
- columns:柵格組件中的列數(shù),當(dāng)前場(chǎng)景默認(rèn)12列即可。
- gutter:柵格布局列間距,當(dāng)前場(chǎng)景未使用該參數(shù),默認(rèn)設(shè)置為0即可。
- margin: 柵格布局兩側(cè)間距,在開(kāi)發(fā)消息氣泡組件時(shí),已經(jīng)設(shè)置了左右間距,故該屬性也默認(rèn)配置為0。
柵格中僅包含我們自定義的消息氣泡組件,該組件在各斷點(diǎn)上的參數(shù)配置如下。
斷點(diǎn) | 窗口寬度(vp) | 柵格總列數(shù) | 消息氣泡占用的列數(shù) | 接收?qǐng)鼍捌频牧袛?shù) | 發(fā)送場(chǎng)景偏移的列數(shù) |
---|---|---|---|---|---|
sm | [320, 600) | 12 | 12 | 0 | 0 |
md | [600, 840) | 12 | 8 | 0 | 4 |
lg | [840, +∞) | 12 | 8 | 0 | 4 |
@Component
export default struct MessageItem {
private isReceived?: boolean
private content?: string
private time?: string
build() {
GridRow() {
GridCol({span: {sm: 12, md: 8, lg: 8},
offset: {sm: 0, md: this.isReceived? 0 : 4, lg: this.isReceived? 0 : 4}}) {
Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.End }) {
MessageBubble({
isReceived: this.isReceived,
content: this.content,
time: this.time
})
}
}
}
}
}
@Entry
@Component
struct Conversation {
build() {
Column() { // 驗(yàn)證效果
MessageItem({
isReceived: globalMessageList[1].isReceived,
content: globalMessageList[1].content,
time: globalMessageList[1].time
})
MessageItem({
isReceived: globalMessageList[3].isReceived,
content: globalMessageList[3].content,
time: globalMessageList[3].time
})
}.backgroundColor('#87CEFA') // 消息背景色,僅用于開(kāi)發(fā)和測(cè)試
}
}
組合成型
現(xiàn)在會(huì)話詳情頁(yè)面的頂部標(biāo)題欄、信息列表及底部輸入欄都已經(jīng)準(zhǔn)備完畢,將這三部分組合起來(lái)即可得到完整的頁(yè)面。
- 通過(guò)[Flex組件]將三個(gè)部分組合起來(lái),注意justifyContent: FlexAlign.SpaceBetween配置項(xiàng)是將Flex組件中的元素按照主軸方向均勻分配,其中第一個(gè)元素與頂部對(duì)齊,最后一個(gè)元素與底部對(duì)齊。
- 通過(guò)[List組件]和[ForEach語(yǔ)法],顯示整個(gè)消息列表。
`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
@Entry
@Component
struct Conversation {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start,
justifyContent: FlexAlign.SpaceBetween }) {
Column() {
TopArea() // 頂部標(biāo)題欄
List() { // 消息列表
ForEach(globalMessageList, (item:globalMessageItem, index) = > {
ListItem() {
MessageItem({
isReceived: item.isReceived,
content: item.content,
time: item.time
})
}})
}
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.Spring)
}
BottomArea() // 底部輸入欄
}
.backgroundColor("#F1F3F5")
.width('100%')
.height('100%')
}
}
短信應(yīng)用在默認(rèn)設(shè)備和平板上的功能完全相同,因此選擇了部署模型A。借助方舟開(kāi)發(fā)框架一多能力,短信應(yīng)用實(shí)現(xiàn)了在默認(rèn)設(shè)備和平板上共用同一份代碼,同時(shí)自然也共用安裝包。
在實(shí)際開(kāi)發(fā)過(guò)程中,會(huì)話詳情頁(yè)面需要從底層做數(shù)據(jù)交互,同時(shí)還要支持信息選擇、信息刪除、信息發(fā)送狀態(tài)、輸入框與輸入法聯(lián)動(dòng)等等功能,會(huì)比本小節(jié)中介紹的基礎(chǔ)版本復(fù)雜很多。
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
57文章
2339瀏覽量
42805 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
188瀏覽量
4382
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論