鴻蒙5開發(fā)寶藏案例分享---快捷觸達(dá)的騎行體驗(yàn)

鴻蒙寶藏案例詳解:共享單車“絲滑”騎行體驗(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):

  1. 權(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ī)
        }
      }
    ],
    
  2. ScanOptions 配置靈活:

    • scanTypes: 指定識(shí)別的碼類型,非常靈活。
    • enableMultiMode: 是否一次掃多個(gè)碼(共享單車通常不需要,關(guān)掉更快)。
    • enableAlbum: 是否允許從相冊(cè)選擇二維碼圖片(重要!用戶可能截圖掃碼)。
  3. startScanForResult: 這是啟動(dòng)掃碼的核心API,返回一個(gè)Promise。.then()里處理成功結(jié)果,.catch()處理失敗。

  4. 結(jié)果處理 (result):

    • result.scanType: 識(shí)別出的碼類型(二維碼?條形碼?)。
    • result.value: 掃碼得到的數(shù)據(jù)字符串(通常包含單車唯一ID、解鎖指令等)。這個(gè)例子簡(jiǎn)化了,實(shí)際業(yè)務(wù)中這里會(huì)解析result.value獲取單車信息!
  5. 狀態(tài)管理 (AppStorage): 鴻蒙提供的應(yīng)用級(jí)狀態(tài)管理。這里設(shè)置CYCLING_STATUS = WAITING_UNLOCK,告訴應(yīng)用“用戶掃到碼了,等待確認(rèn)解鎖”。這個(gè)狀態(tài)會(huì)被解鎖頁(yè)面使用。

  6. 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):

  1. 權(quán)限 (module.json5): 定位權(quán)限同樣必須聲明

    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "用于查找附近的共享單車和導(dǎo)航"
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "用于更精準(zhǔn)的找車定位"
      }
    ],
    
  2. MapComponent: 地圖的UI組件。mapCallback 在地圖加載完成后觸發(fā),此時(shí)才能安全地獲取mapController進(jìn)行操作。

  3. 定位流程 (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)確顯示。
  4. 路徑規(guī)劃 (getWalkingRoutes):

    • 調(diào)用 navi.getWalkingRoutes(params) 是核心。傳入起點(diǎn)(origins)、終點(diǎn)(destination)、類型(WALKING)。
    • 返回的 RouteResult 包含路線信息,其中 overviewPolyline一串壓縮過(guò)的經(jīng)緯度點(diǎn),用于繪制路線。
  5. 繪制路線 (addPolyline):

    • 使用 mapController.addPolyline(options) 繪制折線。
    • options.points 傳入路線規(guī)劃得到的坐標(biāo)點(diǎn)數(shù)組 (overviewPolyline 需要先解碼,示例代碼假設(shè)MapUtil.walkingRoutes內(nèi)部或返回結(jié)果已處理)。
    • 通過(guò) width, color 等屬性定制路線外觀。
  6. 交互流程: 用戶點(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):

  1. 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)、是否持久化等。
  2. 狀態(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() 刷新界面。

  3. 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è)意圖。

  4. LiveNotification: 負(fù)責(zé)實(shí)況窗的生命周期管理 (create, update, stop)。.from(context, env) 將實(shí)況窗與特定的業(yè)務(wù)環(huán)境(env)關(guān)聯(lián)起來(lái)。

  5. 沉浸態(tài)鎖屏實(shí)況窗 (高級(jí)):

    • LiveViewDataBuilder 中配置 setLiveViewLockScreenAbilityNamesetLiveViewLockScreenPicture。

    • 實(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)
        }
      ],
      
  6. 服務(wù)開通: 使用實(shí)況窗能力,需要在 AppGallery Connect 后臺(tái)為你的應(yīng)用開通 Live View Kit 服務(wù)權(quán)益。


?? 總結(jié)與思考

把這三塊核心代碼串起來(lái),就構(gòu)成了那個(gè)“絲滑”騎行體驗(yàn)的骨架:

  1. ScanUtil.scan() 被調(diào)用 -> 掃碼成功 -> router.pushUrl 直達(dá)解鎖頁(yè)。
  2. 用戶點(diǎn)擊解鎖 -> 調(diào)用 LiveViewController.startLiveView() 創(chuàng)建實(shí)況窗 (顯示騎行中)。
  3. 騎行中
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容