鴻蒙寶藏案例詳解:共享單車“絲滑”騎行體驗(yàn)的代碼實(shí)現(xiàn) ????
大家好!上次分享了鴻蒙那個(gè)超棒的共享單車體驗(yàn)案例,很多朋友留言說(shuō)想看代碼細(xì)節(jié)。沒(méi)問(wèn)題!這就帶大家深入代碼層,看看那些“絲滑”的體驗(yàn)(掃碼直達(dá)、實(shí)時(shí)狀態(tài)窗、路徑規(guī)劃)到底是怎么敲出來(lái)的。官方文檔有時(shí)像藏寶圖,代碼才是真金白銀!
核心目標(biāo)再?gòu)?qiáng)調(diào): 用HarmonyOS的
Scan Kit(掃碼直達(dá))、Map Kit(找車導(dǎo)航)、Live View Kit(實(shí)況窗)三大能力,把掃碼->解鎖->騎行->還車->支付的流程做到極簡(jiǎn)、實(shí)時(shí)、無(wú)感。
?? 模塊一:掃碼直達(dá)解鎖頁(yè) (Scan Kit)
目標(biāo): 用戶在任何地方掃碼,直接跳轉(zhuǎn)到該單車的解鎖確認(rèn)頁(yè),跳過(guò)打開App、找入口的步驟。
關(guān)鍵代碼詳解 (TypeScript/ArkTS)
// 1. 導(dǎo)入關(guān)鍵模塊
import scanBarcode from '@ohos.abilityAccessCtrl'; // Scan Kit核心模塊
import { router } from '@kit.ArkUI'; // 頁(yè)面路由模塊
import { BusinessError } from '@kit.BasicServicesKit'; // 錯(cuò)誤處理
// 2. 掃碼工具類 (ScanUtil.ts)
export class ScanUtil {
public static scan(obj: Object): void {
// 3. 配置掃碼選項(xiàng):支持所有類型碼(ALL)和一維碼(ONE_D_CODE),允許多碼識(shí)別,允許從相冊(cè)選圖
let options: scanBarcode.ScanOptions = {
scanTypes: [scanBarcode.ScanType.ALL, scanBarcode.ScanType.ONE_D_CODE],
enableMultiMode: true,
enableAlbum: true
};
try {
// 4. 啟動(dòng)掃碼并等待結(jié)果 (異步Promise)
scanBarcode.startScanForResult(getContext(obj), options)
.then((result: scanBarcode.ScanResult) => {
console.info('掃碼結(jié)果:', JSON.stringify(result));
// 5. 關(guān)鍵邏輯:判斷掃碼類型 (假設(shè)CyclingConstants.SCAN_TYPE代表單車碼)
if (result.scanType === CyclingConstants.SCAN_TYPE) {
// 6. 設(shè)置應(yīng)用狀態(tài):等待解鎖 (AppStorage是鴻蒙的狀態(tài)管理)
AppStorage.setOrCreate(CyclingConstants.CYCLING_STATUS, CyclingStatus.WAITING_UNLOCK);
// 7. 核心跳轉(zhuǎn)!直接路由到解鎖確認(rèn)頁(yè) 'pages/ConfirmUnlock'
router.pushUrl({ url: 'pages/ConfirmUnlock' });
// 通常這里會(huì)把掃碼得到的數(shù)據(jù)(如單車ID)通過(guò)params傳遞給ConfirmUnlock頁(yè)面
}
})
.catch((error: BusinessError) => {
console.error('掃碼出錯(cuò):', JSON.stringify(error));
// 處理錯(cuò)誤:如提示用戶、重試等
});
} catch (error) {
console.error('啟動(dòng)掃碼失敗:', JSON.stringify(error));
}
}
}
代碼解析 & 關(guān)鍵點(diǎn):
-
權(quán)限申請(qǐng) (
module.json5): 掃碼必須的相機(jī)權(quán)限!必須在配置文件聲明:"requestPermissions": [ { "name": "ohos.permission.CAMERA", "reason": "用于掃描共享單車二維碼", // 給用戶看的理由 "usedScene": { "abilities": ["EntryAbility"], // 在哪個(gè)Ability申請(qǐng) "when": "always" // 使用時(shí)機(jī) } } ], -
ScanOptions配置靈活:-
scanTypes: 指定識(shí)別的碼類型,非常靈活。 -
enableMultiMode: 是否一次掃多個(gè)碼(共享單車通常不需要,關(guān)掉更快)。 -
enableAlbum: 是否允許從相冊(cè)選擇二維碼圖片(重要!用戶可能截圖掃碼)。
-
startScanForResult: 這是啟動(dòng)掃碼的核心API,返回一個(gè)Promise。.then()里處理成功結(jié)果,.catch()處理失敗。-
結(jié)果處理 (
result):-
result.scanType: 識(shí)別出的碼類型(二維碼?條形碼?)。 -
result.value: 掃碼得到的數(shù)據(jù)字符串(通常包含單車唯一ID、解鎖指令等)。這個(gè)例子簡(jiǎn)化了,實(shí)際業(yè)務(wù)中這里會(huì)解析result.value獲取單車信息!
-
狀態(tài)管理 (
AppStorage): 鴻蒙提供的應(yīng)用級(jí)狀態(tài)管理。這里設(shè)置CYCLING_STATUS = WAITING_UNLOCK,告訴應(yīng)用“用戶掃到碼了,等待確認(rèn)解鎖”。這個(gè)狀態(tài)會(huì)被解鎖頁(yè)面使用。router.pushUrl: 實(shí)現(xiàn)“直達(dá)”的關(guān)鍵! 直接路由導(dǎo)航到解鎖確認(rèn)頁(yè)pages/ConfirmUnlock。用戶瞬間從掃碼界面跳到了解鎖按鈕面前,省去所有中間步驟。通常會(huì)把單車ID等信息通過(guò)params傳遞過(guò)去:router.pushUrl({ url: 'pages/ConfirmUnlock', params: { bikeId: parsedBikeId } })。
調(diào)用時(shí)機(jī): 在你的首頁(yè)(Index)、共享單車功能頁(yè)(BikePage),甚至一個(gè)桌面萬(wàn)能卡片(Card)的按鈕點(diǎn)擊事件里,調(diào)用ScanUtil.scan(this)即可觸發(fā)掃碼。
??? 模塊二:智能找車與步行導(dǎo)航 (Map Kit)
目標(biāo): 在“找車”頁(yè)面,顯示用戶位置、車輛位置,并繪制步行路線。
關(guān)鍵代碼詳解 (地圖初始化、定位、路徑規(guī)劃與繪制)
// 1. 導(dǎo)入關(guān)鍵模塊
import { MapComponent, mapCommon, map, navi } from '@kit.MapKit'; // 地圖核心
import geoLocationManager from '@ohos.geoLocationManager'; // 定位管理
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; // 權(quán)限申請(qǐng)
import { BusinessError } from '@kit.BasicServicesKit';
// 2. 在找車頁(yè)面 (FindBikePage.ets)
@Entry
@Component
struct FindBikePage {
// ... 其他狀態(tài)變量 ...
private mapController?: map.MapComponentController; // 地圖控制器
private mapPolyline?: map.MapPolyline; // 用于繪制路線的線對(duì)象
private myPosition: mapCommon.LatLng = { latitude: 0, longitude: 0 }; // 用戶位置
aboutToAppear(): void {
// 3. 初始化地圖回調(diào)
this.callback = async (err, mapController) => {
if (!err) {
this.mapController = mapController;
this.mapController.on('mapLoad', async () => {
// 4. 檢查并申請(qǐng)定位權(quán)限
const hasPerm = await this.checkLocationPermissions();
if (hasPerm) {
this.enableMyLocation(); // 開啟定位并獲取位置
}
});
}
};
}
// 5. 檢查定位權(quán)限
private async checkLocationPermissions(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
try {
const permissions = [
'ohos.permission.LOCATION',
'ohos.permission.APPROXIMATELY_LOCATION'
];
const grantStatus = await atManager.checkAccessToken(
abilityAccessCtrl.AccessTokenID.BASE,
permissions
);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (error) {
console.error('檢查權(quán)限出錯(cuò)', error);
return false;
}
}
// 6. 申請(qǐng)定位權(quán)限
private requestPermissions(): void {
const atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(
getContext(this) as common.UIAbilityContext,
['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION']
).then(() => {
this.enableMyLocation(); // 權(quán)限獲取成功,開啟定位
}).catch((err: BusinessError) => {
console.error('申請(qǐng)權(quán)限失敗', err.code, err.message);
});
}
// 7. 開啟定位并獲取當(dāng)前位置
private enableMyLocation(): void {
if (!this.mapController) return;
// 7.1 設(shè)置地圖顯示我的位置
this.mapController.setMyLocationEnabled(true);
this.mapController.setMyLocationControlsEnabled(true); // 顯示定位按鈕
// 7.2 配置定位請(qǐng)求參數(shù) (高精度、首次定位)
let requestInfo: geoLocationManager.CurrentLocationRequest = {
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,
maxAccuracy: 50 // 精度要求(米)
};
// 7.3 獲取當(dāng)前位置
geoLocationManager.getCurrentLocation(requestInfo)
.then(async (location) => {
console.info('獲取到位置:', location.latitude, location.longitude);
// 7.4 坐標(biāo)轉(zhuǎn)換 (WGS84 -> 國(guó)內(nèi)常用的GCJ02)
let mapPosition: mapCommon.LatLng = await map.convertCoordinate(
mapCommon.CoordinateType.WGS84,
mapCommon.CoordinateType.GCJ02,
{ latitude: location.latitude, longitude: location.longitude }
);
// 7.5 存儲(chǔ)用戶位置 & 移動(dòng)地圖視角
this.myPosition = mapPosition;
AppStorage.setOrCreate('userLat', mapPosition.latitude);
AppStorage.setOrCreate('userLon', mapPosition.longitude);
let cameraUpdate = map.newCameraPosition({
target: mapPosition,
zoom: 16 // 放大到合適級(jí)別
});
this.mapController?.animateCamera(cameraUpdate, 1000); // 1秒動(dòng)畫移動(dòng)到用戶位置
})
.catch((err: BusinessError) => {
console.error('獲取位置失敗', err.code, err.message);
});
}
// 8. 監(jiān)聽地圖點(diǎn)擊 (用戶點(diǎn)選單車位置)
private setupMapListeners(): void {
this.mapController?.on('mapClick', async (clickedPosition: mapCommon.LatLng) => {
// 8.1 清除舊標(biāo)記和路線
this.mapController?.clear();
this.mapPolyline?.remove();
// 8.2 在點(diǎn)擊位置添加一個(gè)標(biāo)記 (Marker)
this.marker = await MapUtil.addMarker(clickedPosition, this.mapController);
// 8.3 關(guān)鍵!發(fā)起步行路徑規(guī)劃 (從用戶位置this.myPosition 到 點(diǎn)擊位置clickedPosition)
const walkingRoutes = await MapUtil.walkingRoutes(clickedPosition, this.myPosition);
if (walkingRoutes && walkingRoutes.routes.length > 0) {
// 8.4 繪制規(guī)劃好的步行路線
await MapUtil.paintRoute(walkingRoutes, this.mapPolyline, this.mapController);
}
});
}
build() {
Column() {
// 9. 集成地圖組件 (核心UI)
MapComponent({
mapOptions: { ... }, // 地圖初始配置 (中心點(diǎn)、縮放級(jí)別等)
mapCallback: this.callback // 地圖加載完成的回調(diào)
})
.onClick(() => {
this.setupMapListeners(); // 通常在地圖加載后設(shè)置監(jiān)聽
})
.width('100%')
.height('100%')
}
}
}
// 10. 路徑規(guī)劃工具類 (MapUtil.ts)
export class MapUtil {
// 10.1 步行路徑規(guī)劃
public static async walkingRoutes(
destination: mapCommon.LatLng,
origin?: mapCommon.LatLng
): Promise<navi.RouteResult | undefined> {
if (!origin) return undefined;
let params: navi.RouteParams = {
origins: [origin], // 起點(diǎn)數(shù)組 (這里一個(gè))
destination: destination, // 終點(diǎn)
type: navi.RouteType.WALKING, // 步行模式
language: 'zh_CN' // 中文結(jié)果
};
try {
const result = await navi.getWalkingRoutes(params); // 調(diào)用Map Kit API
console.info('步行路線規(guī)劃成功', JSON.stringify(result));
return result;
} catch (err) {
console.error('步行路線規(guī)劃失敗', JSON.stringify(err));
return undefined;
}
}
// 10.2 繪制路線到地圖
public static async paintRoute(
routeResult: navi.RouteResult,
mapPolyline: map.MapPolyline | undefined,
mapController?: map.MapComponentController
) {
if (!mapController || !routeResult.routes[0]?.overviewPolyline) return;
// 清除舊線
mapPolyline?.remove();
// 配置新線的樣式 (藍(lán)色,20像素寬)
let polylineOption: mapCommon.MapPolylineOptions = {
points: routeResult.routes[0].overviewPolyline, // 路線坐標(biāo)點(diǎn)數(shù)組
clickable: true,
width: 20,
color: 0xFF2970FF, // ARGB 藍(lán)色
zIndex: 10
};
// 添加折線到地圖并保存引用
mapPolyline = await mapController.addPolyline(polylineOption);
return mapPolyline;
}
// ... (addMarker 方法類似) ...
}
代碼解析 & 關(guān)鍵點(diǎn):
-
權(quán)限 (
module.json5): 定位權(quán)限同樣必須聲明:"requestPermissions": [ { "name": "ohos.permission.LOCATION", "reason": "用于查找附近的共享單車和導(dǎo)航" }, { "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "用于更精準(zhǔn)的找車定位" } ], MapComponent: 地圖的UI組件。mapCallback在地圖加載完成后觸發(fā),此時(shí)才能安全地獲取mapController進(jìn)行操作。-
定位流程 (
enableMyLocation):-
setMyLocationEnabled(true): 讓地圖顯示用戶位置藍(lán)點(diǎn)。 -
getCurrentLocation: 獲取一次精確位置。對(duì)于持續(xù)追蹤,需用on('locationChange')監(jiān)聽。 -
坐標(biāo)轉(zhuǎn)換 (
convertCoordinate): 非常重要! 設(shè)備GPS返回的是WGS84坐標(biāo),國(guó)內(nèi)地圖服務(wù)(如GCJ02)需要轉(zhuǎn)換才能準(zhǔn)確顯示。
-
-
路徑規(guī)劃 (
getWalkingRoutes):- 調(diào)用
navi.getWalkingRoutes(params)是核心。傳入起點(diǎn)(origins)、終點(diǎn)(destination)、類型(WALKING)。 - 返回的
RouteResult包含路線信息,其中overviewPolyline是一串壓縮過(guò)的經(jīng)緯度點(diǎn),用于繪制路線。
- 調(diào)用
-
繪制路線 (
addPolyline):- 使用
mapController.addPolyline(options)繪制折線。 -
options.points傳入路線規(guī)劃得到的坐標(biāo)點(diǎn)數(shù)組 (overviewPolyline需要先解碼,示例代碼假設(shè)MapUtil.walkingRoutes內(nèi)部或返回結(jié)果已處理)。 - 通過(guò)
width,color等屬性定制路線外觀。
- 使用
交互流程: 用戶點(diǎn)擊地圖 -> 獲取點(diǎn)擊點(diǎn)坐標(biāo) -> 清除舊數(shù)據(jù) -> 添加新Marker -> 規(guī)劃并繪制到該Marker的步行路線。
? 模塊三:實(shí)況窗展示騎行狀態(tài) (Live View Kit)
目標(biāo): 解鎖后,在狀態(tài)欄(膠囊)、通知中心、鎖屏實(shí)時(shí)顯示騎行狀態(tài)/時(shí)長(zhǎng)/費(fèi)用;還車后變待支付;支付后結(jié)束。
關(guān)鍵代碼詳解 (創(chuàng)建、更新、銷毀實(shí)況窗)
// 1. 導(dǎo)入關(guān)鍵模塊
import liveViewManager, { LiveViewDataBuilder, TextLayoutBuilder, TextCapsuleBuilder, LiveNotification, LiveViewContext } from '@kit.LiveViewKit';
import { BusinessError } from '@kit.BasicServicesKit';
import wantAgent from '@ohos.app.ability.wantAgent'; // 用于定義點(diǎn)擊動(dòng)作
// 2. 實(shí)況窗控制類 (LiveViewController.ts)
export class LiveViewController {
private liveViewData?: liveViewManager.LiveViewData; // 當(dāng)前實(shí)況窗數(shù)據(jù)
private liveNotification?: LiveNotification; // 實(shí)況窗通知對(duì)象
// 3. 創(chuàng)建并顯示實(shí)況窗 (在用戶點(diǎn)擊"解鎖"后調(diào)用)
public async startLiveView(context: LiveViewContext): Promise<liveViewManager.LiveViewResult> {
// 3.1 構(gòu)建默認(rèn)的實(shí)況窗數(shù)據(jù) (騎行中狀態(tài))
this.liveViewData = await this.buildDefaultView(context);
// 3.2 創(chuàng)建LiveNotification對(duì)象 (關(guān)聯(lián)環(huán)境信息,如業(yè)務(wù)類型'RENT')
let env: liveViewManager.LiveViewEnvironment = { id: 0, event: 'RENT' };
this.liveNotification = LiveNotification.from(context, env);
// 3.3 創(chuàng)建并顯示實(shí)況窗!
return await this.liveNotification.create(this.liveViewData);
}
// 4. 構(gòu)建默認(rèn)騎行中狀態(tài)的實(shí)況窗數(shù)據(jù)
private static async buildDefaultView(context: LiveViewContext): Promise<liveViewManager.LiveViewData> {
// 4.1 構(gòu)建展開態(tài)卡片布局 (鎖屏/通知中心看到的卡片)
const layoutData = new TextLayoutBuilder()
.setTitle('騎行中') // 卡片標(biāo)題
.setContent('已騎行 0 分鐘') // 卡片內(nèi)容 (初始0分鐘)
.setDescPic('bike_icon.png'); // 卡片右側(cè)圖標(biāo)
// 4.2 構(gòu)建膠囊態(tài) (狀態(tài)欄看到的小膠囊)
const capsule = new TextCapsuleBuilder()
.setIcon('bike_small.png') // 膠囊圖標(biāo)
.setBackgroundColor('#FF00FF00') // 膠囊背景色 (綠色)
.setTitle('騎行中'); // 膠囊文字
// 4.3 構(gòu)建點(diǎn)擊動(dòng)作 (點(diǎn)擊實(shí)況窗跳轉(zhuǎn)回App的騎行頁(yè)面)
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: context.bundleName,
abilityName: 'EntryAbility',
parameters: { route: 'pages/RidingPage' } // 跳轉(zhuǎn)到騎行頁(yè)
}
],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0
};
const wantAgentObj = await wantAgent.getWantAgent(wantAgentInfo);
// 4.4 構(gòu)建完整的LiveViewData
const liveViewData = new LiveViewDataBuilder()
.setTitle('騎行中') // 主標(biāo)題
.setContentText(['已騎行 0 分鐘']) // 內(nèi)容文本數(shù)組 (可多行)
.setContentColor('#FFFFFFFF') // 內(nèi)容文字顏色 (白色)
.setLayoutData(layoutData) // 設(shè)置卡片布局
.setCapsule(capsule) // 設(shè)置膠囊樣式
.setWant(wantAgentObj) // 設(shè)置點(diǎn)擊動(dòng)作
// (可選) 配置鎖屏沉浸態(tài)擴(kuò)展Ability (見(jiàn)后面)
.setLiveViewLockScreenAbilityName('LiveViewLockScreenExtAbility')
.setLiveViewLockScreenPicture('bike_lock_icon.png')
.build(); // 構(gòu)建完成
return liveViewData;
}
// 5. 更新實(shí)況窗狀態(tài) (騎行中 -> 待支付 -> 支付完成)
public async updateLiveView(status: number, context: LiveViewContext): Promise<liveViewManager.LiveViewResult> {
if (!this.liveViewData || !this.liveNotification) {
console.error('實(shí)況窗未創(chuàng)建或數(shù)據(jù)為空');
return { code: -1 };
}
switch (status) {
case CyclingStatus.RIDING: // 騎行中 (更新計(jì)時(shí))
// ... 更新 this.liveViewData 的計(jì)時(shí)文本 (e.g., '已騎行 5 分鐘') ...
return await this.liveNotification.update(this.liveViewData);
case CyclingStatus.WAITING_PAYMENT: // 還車成功,待支付
// 5.1 更新標(biāo)題、內(nèi)容、膠囊文字
this.liveViewData.primary.title = '待支付';
this.liveViewData.primary.content = [{ text: '騎行結(jié)束,點(diǎn)擊支付', textColor: '#FFFFFFFF' }];
this.liveViewData.capsule.title = '待支付';
// 5.2 更新點(diǎn)擊動(dòng)作 (點(diǎn)擊跳轉(zhuǎn)到支付頁(yè))
this.liveViewData.primary.clickAction = await this.buildWantAgent(context, 'pages/PaymentPage');
// 5.3 更新卡片布局
this.liveViewData.primary.layoutData = new TextLayoutBuilder()
.setTitle('待支付')
.setContent('費(fèi)用:¥2.50')
.setDescPic('payment_icon.png');
return await this.liveNotification.update(this.liveViewData);
case CyclingStatus.PAYMENT_COMPLETED: // 支付完成
// 5.4 更新為最終狀態(tài)
this.liveViewData.primary.title = '支付成功';
this.liveViewData.primary.content = [{ text: '行程已完成,感謝使用', textColor: '#FFFFFFFF' }];
this.liveViewData.capsule.title = '完成';
// 5.5 關(guān)鍵!停止實(shí)況窗 (顯示最終狀態(tài)幾秒后消失)
return await this.liveNotification.stop(this.liveViewData);
default:
return { code: -1 };
}
}
// ... (buildWantAgent 輔助方法) ...
}
// 6. 鎖屏沉浸態(tài)實(shí)況窗擴(kuò)展Ability (LiveViewLockScreenExtAbility.ets)
import { LiveViewLockScreenExtensionAbility, UIExtensionContentSession } from '@kit.LiveViewKit';
import hilog from '@ohos.hilog';
export default class LiveViewLockScreenExtAbility extends LiveViewLockScreenExtensionAbility {
onSessionCreate(want: Want, session: UIExtensionContentSession) {
hilog.info(0x0000, 'LiveViewLock', '鎖屏擴(kuò)展Ability創(chuàng)建會(huì)話');
// 6.1 加載自定義的鎖屏實(shí)況窗UI頁(yè)面
session.loadContent('pages/LiveViewLockScreenPage'); // 這個(gè)頁(yè)面你用ArkUI自己設(shè)計(jì)!
}
// ... (其他生命周期方法 onForeground, onBackground, onDestroy) ...
}
代碼解析 & 關(guān)鍵點(diǎn):
-
LiveViewDataBuilder: 構(gòu)建實(shí)況窗數(shù)據(jù)的核心工具。它定義了:-
主信息 (
primary): 標(biāo)題、內(nèi)容文本/顏色、點(diǎn)擊動(dòng)作(WantAgent)、卡片布局(LayoutData)、鎖屏擴(kuò)展能力名/參數(shù)/圖片。 -
膠囊態(tài) (
capsule): 狀態(tài)欄顯示的圖標(biāo)、背景色、文字。 -
其他: 顯示時(shí)長(zhǎng)(
keepTime)、是否持久化等。
-
主信息 (
狀態(tài)管理: 實(shí)況窗內(nèi)容不是靜態(tài)的!
updateLiveView方法根據(jù)業(yè)務(wù)狀態(tài) (RIDING,WAITING_PAYMENT,PAYMENT_COMPLETED) 動(dòng)態(tài)更新liveViewData的各個(gè)部分,然后調(diào)用update()或stop()刷新界面。WantAgent: 實(shí)現(xiàn)點(diǎn)擊交互的關(guān)鍵! 定義了用戶點(diǎn)擊實(shí)況窗(膠囊或卡片)后要執(zhí)行的動(dòng)作。最常見(jiàn)的就是跳轉(zhuǎn)回App的特定頁(yè)面(如騎行頁(yè)、支付頁(yè))。wantAgent模塊用于構(gòu)建這個(gè)意圖。LiveNotification: 負(fù)責(zé)實(shí)況窗的生命周期管理 (create,update,stop)。.from(context, env)將實(shí)況窗與特定的業(yè)務(wù)環(huán)境(env)關(guān)聯(lián)起來(lái)。-
沉浸態(tài)鎖屏實(shí)況窗 (高級(jí)):
在
LiveViewDataBuilder中配置setLiveViewLockScreenAbilityName和setLiveViewLockScreenPicture。實(shí)現(xiàn)一個(gè)繼承自
LiveViewLockScreenExtensionAbility的Ability。在
onSessionCreate方法中,使用session.loadContent('你的自定義UI頁(yè)面路徑')加載你用ArkUI編寫的自定義鎖屏卡片界面。這讓你可以展示比默認(rèn)模板更豐富的信息(比如地圖縮略圖、更詳細(xì)的費(fèi)用明細(xì))。-
聲明擴(kuò)展Ability (
module.json5):"extensionAbilities": [ { "name": "LiveViewLockScreenExtAbility", "type": "liveViewLockScreen", // 類型必須為liveViewLockScreen "srcEntry": "./ets/entryability/LiveViewLockScreenExtAbility.ets", "exported": true // 允許系統(tǒng)訪問(wèn) } ],
服務(wù)開通: 使用實(shí)況窗能力前,需要在
AppGallery Connect后臺(tái)為你的應(yīng)用開通Live View Kit服務(wù)權(quán)益。
?? 總結(jié)與思考
把這三塊核心代碼串起來(lái),就構(gòu)成了那個(gè)“絲滑”騎行體驗(yàn)的骨架:
-
ScanUtil.scan()被調(diào)用 -> 掃碼成功 ->router.pushUrl直達(dá)解鎖頁(yè)。 - 用戶點(diǎn)擊解鎖 -> 調(diào)用
LiveViewController.startLiveView()創(chuàng)建實(shí)況窗 (顯示騎行中)。 - 騎行中