E-Bike是一款基于HarmonyOS開發的元服務,以萬能卡片的形式給騎行提供便捷服務。首先需要完成HarmonyOS開發環境搭建。
一、元服務E-Bike簡介
E-Bike是一款基于HarmonyOS開發的元服務,以萬能卡片的形式給騎行提供便捷服務,主要功能包括:
- 車輛狀態信息獲取:用戶可在元服務內連接電動自行車(真機和自行車自備),查看車輛位置、剩余電量、續航里程以及累計騎行里程。
- 包括響鈴找車功能:按鈕可觸發車輛鳴響,便于快速確認車輛具體位置(真機和自行車自備)。
- 用戶可通過右上角按鈕添加2x2或2x4卡片,在桌面可直接查看車輛狀態信息。
二、環境搭建
首先需要完成HarmonyOS開發環境搭建。E-Bike是元服務,且為端云一體化開發模式,新建工程可可參照如圖步驟進行(注意該模式下App為Stage模型)。
軟件要求
- DevEco Studio版本:DevEco Studio 3.1 Release及以上版本。
- HarmonyOS SDK版本:API version 9及以上版本。
硬件要求
- 設備類型:華為手機或運行在DevEco Studio上的華為手機設備模擬器。
- HarmonyOS系統:3.1.0 Developer Release及以上版本。
- 電動自行車(獲取真實車輛數據,車輛為作者自制)
環境搭建
安裝DevEco Studio,詳情請參考下載和安裝軟件。
設置DevEco Studio開發環境,DevEco Studio開發環境需要依賴于網絡環境,需要連接上網絡才能確保工具的正常使用,可以根據如下兩種情況來配置開發環境:
- 如果可以直接訪問Inte.NET,只需進行下載HarmonyOS SDK操作。
- 如果網絡不能直接訪問Internet,需要通過代理服務器才可以訪問,請參考配置開發環境。
開發者可以參考以下鏈接,完成設備調試的相關配置:使用真機進行調試使用模擬器進行調試
三、代碼結構解讀
本篇教程只對E-Bike實現的核心代碼進行講解,對于完整代碼,會在源碼下載或gitee中提供。主要的程序框架如下:
entrysrcmAInets
│ ├─common–通用常量和數據
│ ├─entryability – EntryAbility.ts 程序入口
│ ├─entryformability–EntryFormAbility.ts卡片入口
│ ├─pages—Index.ts 應用主頁
│ ├─services
│ ├─widget
│ │ └─pages—2x2 ArkTS卡片
│ └─widget24
│ └─pages—2x4卡片
└─resources —資源文件目錄
四、應用主頁面UI和功能開發
1、主頁面UI
新建工程后,在entrysrcmainetspagesIndex.ts文件中已有一個模板案例,我們需要刪除其中的代碼,然后構建自己的頁面。具體實現方法是:
- 刪除build() { }中的代碼。
- 使用Column、Flex、Row容器和Button、Image、Text組件構建E-Bike布局。
- 在UI中加入邏輯判斷具體要顯示的UI組件。如響鈴找車的Image組件內容由用戶的點擊狀態決定,點擊響鈴找車則Image切換為響鈴狀態,反之亦然。
具體代碼實現框架如下:
@Entry @Component struct Index {
build() { Column({space:10})
{
// 背景圖
Image($r("app.media.Ebike"))
···
Flex()
{
// 響鈴找車
Column()
{
if(this.display_flag==1)
{
Image($r("app.media.ic_ring_on_filled"))
.height("55%")
.objectFit(ImageFit.Contain)
.interpolation(ImageInterpolation.High)
.onClick(() => { this.display_flag +=1; if(this.display_flag>2)
{
this.display_flag =1;
}
})
}
if(this.display_flag==2)
{
Image($r("app.media.ic_ring_off_filled"))
.height("55%")
.objectFit(ImageFit.Contain)
.interpolation(ImageInterpolation.High)
.onClick(() => { this.display_flag+=1; if(this.display_flag>2)
{
this.display_flag =1;
}
})
}
Text("響鈴找車")
···
}
// 獲取定位
Column()
{
Image($r("app.media.ic_statusbar_gps"))
····
})
Text(this.bike_location)
····
}
····
}.width("95%").height("10%")
// 電量 設置
Flex({ direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,})
{
// 電量
Column() { Row()
{
Image($r("app.media.ic_power"))
···
// 電量值
Text(this.bike_power.toString() + '%')
···
}
Text("剩余電量")
···
}
···
// 設置
Column() { Image($r("app.media.ic_public_settings_filled"))
···
Text("車輛設置")
···
}···
}
// 騎行數據
Flex({ direction: FlexDirection.Column})
{
Row()
{
Text(" 累計騎行")
··· Blank()
Text("預計續航 ")
···
}
}
}
}
}
2、主頁功能開發
主頁主要實現的功能包括:連接車輛、獲取需要展示的數據(車輛電量、位置、里程數據等)、將數據持久化便于頁面展示。
車輛與E-Bike通過Socket TCP協議方式連接。鑒于此部分依賴硬件,這里主要介紹E-Bike應用層如何開發 Socket通信。實現如下:
Index.ets:
// 1. 首先要引入模塊,創建TCPSocket對象
import socket from '@ohos.net.socket'
// 創建一個TCPSocket連接,返回一個TCPSocket對象。
let tcp = socket.constructTCPSocketInstance();
tcpInit() {
// 2. 綁定IP地址和端口。
let bindAddress = { address: '192.168.xx.xx',
port: 1234, // 綁定端口,如1234
family: 1
};
tcp.bind(bindAddress, err => { if (err) {
console.log('bind fail'); return;
}
console.log('bind success');
}
// 3.其次是訂閱TCPSocket相關的訂閱事件,如on message,有數據傳入
tcp.on('connect', () => {
this.tcp_status = '連接ok'
prompt.showToast({message:this.tcp_status})
});
tcp.on('message', value => {
this.message_recv = this.resolveArrayBuffer(value.message)
prompt.showToast({message:this.message_recv})
});
tcp.on('close', () => {
prompt.showToast({message:"tcp close"})
});
}
// 4.連接到指定的IP地址和端口。 tcpConnect() {
tcp.getState().then((data) => {
if (data.isClose) { this.tcpInit()
}
// 連接
tcp.connect(
{address: { address: this.GetSetIP, port: this.localAddr.port, family: 1}, timeout: 2000}
).then(() => {
prompt.showToast({message:" tcp connect successful"})
}).catch((error) => { prompt.showToast({message:"tcp connect failed"})
})
})
}
// 5.發送數據
tcpSend() {
tcp.getState().then((data) =>
{ if (data.isConnected) {
// 發送消息
tcp.send(
{ data: this.message_send, }
).then(() => {
prompt.showToast({message:"send message successful"})
}).catch((error) => { prompt.showToast({message:"send failed"})
})
} else {
prompt.showToast({message:"tcp not connect"})
}
})
}
// 6. TCP連接操作界面通過設置按鈕控制,點擊設置按鈕即可彈出,可輸入車輛IP地址,確認連接
if(this.show_setting==1)
{
Flex({ direction: FlexDirection.Row})
{
Row()
{
Text('車輛IP')
TextInput({ placeholder: '192.168.43.164'})
.onChange((value: string) => { this.InputIP = value
})
Button('連接').onClick(()=>
{
this.GetSetIP=this.InputIP;
this.tcpInit()
})
}
}
}
// 7.點擊組件,實現發送指令,并獲取對應數據,這里以電量為例:
Image($r("app.media.ic_power"))
.height("80%")
.objectFit(ImageFit.Contain)
.onClick(() => {
// this.bike_power = 99;
this.message_send = MSG_CMD.GET_BIKE_POWER this.tcpSend()
this.bike_power = this.message_recv;
})
/ 8.連接使用完畢后,主動關閉。取消相關事件的訂閱。
setTimeout(() => {
tcp.close((err) => { console.log('close socket.')
});
tcp.off('message');
tcp.off('connect');
tcp.off('close');
}, 30 * 1000);
五、卡片開發
1、卡片UI開發
開發卡片的目的是將自行車的數據展示在桌面上,讓用戶一目了然。用默認模板創建工程時,自動創建了一張卡片,在form_config.json文件改動配置為自動刷新,支持2x2.
//form_config.json
"colorMode": "auto", "isDefault": true, "updateEnabled": false, "scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2", "supportDimensions": [
"2*2"
]
為展示電量信息,且布局不同,由此需要創建一張2x4的卡片。操作如下:在main目錄下,點擊鼠標右鍵 > New > Service Widget。
然后選擇Hello World卡片模板,點擊Next。
填寫卡片名字(如Widget24Card)、開發語言(ArkTS和JS可選)、支持卡片規格(Support dimension 2x4)、關聯表單(Ability name)點擊Finish完成創建。修改配置為自動刷新,支持2x4。
創建完卡片后,在ets文件目錄下顯示卡片目錄,然后開發者使用ArkTS開發卡片頁面。效果如圖所示:
為兩張卡片開發UI,resource資源共用。使用Flex容器開發卡片,保證不同屏幕大小的顯示效果。同時為組件綁定事件,用戶可以主動獲取數據,具體UI布局代碼不再贅述,實現2x2和2x4效果如下
2、卡片數據刷新
通過message事件刷新卡片內容,在卡片頁面中可以通過postCardAction接口觸發message事件拉起FormExtensionAbility,然后由FormExtensionAbility刷新卡片內容。下面是這種刷新方式更新電量的簡單示例。在卡片頁面通過注冊電量圖標Image組件的onClick點擊事件回調,并在回調中調用postCardAction接口觸發事件至FormExtensionAbility。
// Widget24Card.ets:
let storage = new LocalStorage();
@Entry(storage) @Component
struct WidgetCard24 {
···
@LocalStorageProp('bike_power') bike_power: number = 50;
···
build() {
Row({space:5}) {
// 背景圖 電量
Column()
{
Row()
{
Image($r("app.media.ic_power"))
···
.onClick(() => { postCardAction(this, {
'action': 'message',
'params': { 'bike_power': 55
}
});
})
// 電量值
Text(`${this.bike_power}`+'%')//this.bike_power.toString()+'%')
···
}
···
}
}
}
// EntryFormAbility.ets:
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility'; import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
find_bike: string = "app.media.ic_ring_off" bike_power: number = 55.6
display_flag : number = 1
bike_location: string = "長安街1號"
bike_distance: number = 520
bike_duration: number = 479
my_font_size : number = 12
formData = {
'title': this.find_bike, 'bike_power': this.bike_power,
'bike_distance':this.bike_distance, 'bike_duration':this.bike_duration,
'bike_location':this.bike_location,
'detail': 'Detail Update Success.', // 和卡片布局中對應
}
onFormEvent(formId, message) {
console.info(`FormAbility onEvent, formId = ${formId}, message:
${JSON.stringify(message)}`);
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
...
}
實現效果如下圖:
參考鏈接:元服務官網
想了解更多關于開源的內容,請訪問:
51CTO 開源基礎軟件社區
https://ost.51cto.com