鴻蒙開發實戰案例:桌面卡片實現案例

介紹

桌面卡片是比較常見的功能,本案例詳細列舉了卡片開發的大部分功能,如使用postCardAction接口快速拉起卡片提供方應用的指定UIAbility,通過message事件刷新卡片內容等,為開發者提供了卡片功能的展示。

效果圖預覽

使用說明

  1. 長按應用,添加卡片到桌面。
  2. 卡片內可滑動選擇案例,點擊可進入案例詳情。
  3. 部分案例無詳情頁時,點擊跳轉到首頁瀑布流。

實現思路

  1. 新建卡片
  2. 配置formconfig
  3. 編寫卡片UI代碼
  4. 觸發刷新事件
  5. 觸發點擊事件

實現步驟

本例涉及的關鍵特性和實現方案如下:

  1. 新建卡片。右鍵點擊entry目錄,選擇新建->Service Widget->Dynamic Widget,其中Dynamic Widget為動態卡片,Static Widget為靜態卡片。此時會生成幾個文件:配置文件form_config.json;卡片AbilityEntryFormAbility.ets;卡片組件WidgetCard.ets

  2. 新建卡片后,根據需要(如卡片大小,刷新時間,動態靜態卡片設置)配置form_config.json

{
  "forms": [
    {
      "name": "widget", // 卡片的名稱。
      "displayName": "$string:widget_display_name", // 卡片的顯示名稱。
      "description": "$string:widget_desc", // 卡片的描述。 
      "src": "./ets/widget/pages/WidgetCard.ets", // 卡片對應的UI代碼的完整路徑。
      "uiSyntax": "arkts", // 卡片的類型
      "window": { // 用于定義與顯示窗口相關的配置。
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto", // 卡片的主題樣式。
      "isDynamic": true, // 卡片是否為動態卡片。
      "isDefault": true, // 卡片是否為默認卡片。
      "updateEnabled": true, // 卡片是否支持周期性刷新。
      "scheduledUpdateTime": "10:30", // 卡片的定點刷新的時刻。
      "updateDuration": 1, // 卡片定時刷新的更新周期,單位為30分鐘,取值為自然數。
      "defaultDimension": "6*4", // 卡片的默認外觀規格。
      "supportDimensions": [ // 卡片支持的外觀規格,取值范圍。
        "6*4"
      ]
    }
  ]
}
  1. 編寫卡片UI代碼。在主文件WidgetCard.ets中添加UI組件,需要注意的是:ArkTS卡片存在較多約束(如不支持導入共享包),較多邏輯不可在卡片中使用,在使用時需要根據文檔進行操作。

  2. 編寫跳轉事件:當應用未被拉起時,點擊某個卡片時跳轉到具體的案例頁面。在EntryAbility.ets中補充邏輯:onCreate生命周期內獲取want.parameters.params判斷卡片內容的跳轉。

// EntryAbility.ets
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    // 桌面卡片判斷跳轉內容
    if (want?.parameters?.params) {
      // want.parameters.params 對應 postCardAction() 中 params 內容
      let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
      this.selectPage = params.targetPage as string;
    }
    // ...
  }
}
  1. 編寫跳轉事件:當應用在后臺時,點擊某個卡片時跳轉到具體的案例頁面。可從onNewWant生命周期獲取want.parameters.params判斷卡片內容的跳轉。
// EntryAbility.ets
export default class EntryAbility extends UIAbility {
  // 如果UIAbility已在后臺運行,在收到Router事件后會觸發onNewWant生命周期回調
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    if (want?.parameters?.params) {
      // want.parameters.params 對應 postCardAction() 中 params 內容
      let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
      this.selectPage = params.targetPage as string;
    } else {
      this.selectPage = '';
    }
    if (this.currentWindowStage !== null) {
      //  存在窗口時點擊卡片后進行頁面跳轉
      if (this.selectPage) {
        waterFlowData.forEach((item: SceneModuleInfo) => {
          let index = item.appUri.indexOf(this.selectPage);
          if (index > -1) {
            if (DynamicsRouter.appRouterStack.slice(-1)[0].name !== item.appUri) {
              DynamicsRouter.clear();
              DynamicsRouter.pushUri(item.appUri);
            }
            return;
          }
        })
        this.selectPage = '';
      }
    }
  }
}
  1. 具體跳轉邏輯編寫。在onWindowStageCreate生命周期內進行具體的跳轉邏輯。
// EntryAbility.ets
export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 判斷是否存在窗口可進行頁面跳轉
    if (this.currentWindowStage === null) {
       this.currentWindowStage = windowStage;
    }
    //  點擊卡片后進行頁面跳轉
    if (this.selectPage) {
      this.storage.setOrCreate('formNavigationRouter', this.selectPage);
      windowStage.loadContent('pages/EntryView', this.storage, (err, data) => {
        if (err.code) {
          logger.error(DOMAIN_NUMBER.toString(), TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
          return;
        }
        logger.info(DOMAIN_NUMBER.toString(), TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
      });
    }
    // ...
   }
}
  1. 編寫跳轉事件:在EntryAbility.ets編寫事件后,同時在接收案例參數的EntryView.ets內處理頁面跳轉邏輯。通過和DynamicsRouter內的數據對比,判斷通過storage傳入的頁面是哪一個,然后執行pushUri跳轉。
@Entry(storage)
@Component
struct EntryView {
  onPageShow(): void {
  // 從卡片進入頁面時判斷具體跳轉頁面
  if (this.formRouter) {
    waterFlowData.forEach((item: SceneModuleInfo) => {
      let index = item.appUri.indexOf(this.formRouter);
      if (index > -1) {
        if (DynamicsRouter.appRouterStack.slice(-1)[0].name !== item.appUri) {
          DynamicsRouter.clear();
          DynamicsRouter.pushUri(item.appUri);
        }
        return;
      }
    })
    this.formRouter = '';
  }
}
}
  1. 判斷卡片大小:獲取卡片詳情,根據寬高比獲取卡片的規格,不同規格顯示內容不同。在EntryFormAbility.ets中補充點擊卡片進入時查找對應案例的邏輯。在onAddForm生命周期內做卡片生成時,createFormBindingData方法傳遞屬性。
// EntryFormAbility.ets
export default class EntryFormAbility extends FormExtensionAbility {
   // 卡片對象集合
  onAddForm(want: Want): formBindingData.FormBindingData {
    let isLongCard: boolean = true;
    if ((want.parameters?.[formInfo.FormParam.WIDTH_KEY] as number) /
      (want.parameters?.[formInfo.FormParam.HEIGHT_KEY] as number) > 0.666) {
      isLongCard = false;
    }
    // 使用方創建卡片時觸發,提供方需要返回卡片數據綁定類
    let obj: Record<string, string | boolean> = {
      'title': 'titleOnAddForm',
      'isLongCard': isLongCard
    };
    let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
    return formData;
  }
}
  1. 編寫刷新事件:當定時更新或定點更新觸發時,需要更新卡片內容。onUpdateForm生命周期發生在定時更新/定點更新/卡片使用方主動請求更新時,在方法內增加獲取案例數據的功能。
// EntryFormAbility.ets
export default class EntryFormAbility extends FormExtensionAbility {
   // 網絡獲取README數據并利用formProvider.updateForm更新到卡片
   async getData(formId: string) {
      let detail: CASES[] = [];
      let httpRequest = http.createHttp();
      let webData: http.HttpResponse = await httpRequest.request(URL);
      if (webData?.responseCode == http.ResponseCode.OK) {
         try {
            detail = this.formatData(webData.result.toString());
            hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent' + 'webData.result:' + webData.result);

            class FormDataClass {
               detail: CASES[] = detail;
            }

            let formData = new FormDataClass();
            let formInfo = formBindingData.createFormBindingData(formData);
            await formProvider.updateForm(formId, formInfo);
            hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'FormAbility updateForm success.');
         } catch (error) {
            hilog.error(DOMAIN_NUMBER, TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);
         }
      } else {
         hilog.error(DOMAIN_NUMBER, TAG, `ArkTSCard download task failed`);
         let param: Record<string, string> = {
            'text': '刷新失敗'
         };
         let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);
         formProvider.updateForm(formId, formInfo);
      }
      httpRequest.destroy();
   }

   async onUpdateForm(formId: string): Promise<void> {
      // 若卡片支持定時更新/定點更新/卡片使用方主動請求更新功能,則提供方需要重寫該方法以支持數據更新
      hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
      this.getData(formId);
   }
}
  1. 編寫刷新事件:手動刷新內容時,需要更新卡片內容。onFormEvent生命周期發生在卡片主動通過postCardAction接口觸發message事件。
// EntryFormAbility.ets
export default class EntryFormAbility extends FormExtensionAbility {
   // 網絡獲取README數據并利用formProvider.updateForm更新到卡片
   async getData(formId: string) {
      let detail: CASES[] = [];
      let httpRequest = http.createHttp();
      let webData: http.HttpResponse = await httpRequest.request(URL);
      if (webData?.responseCode == http.ResponseCode.OK) {
         try {
            detail = this.formatData(webData.result.toString());
            hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent' + 'webData.result:' + webData.result);

            class FormDataClass {
               detail: CASES[] = detail;
            }

            let formData = new FormDataClass();
            let formInfo = formBindingData.createFormBindingData(formData);
            await formProvider.updateForm(formId, formInfo);
            hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'FormAbility updateForm success.');
         } catch (error) {
            hilog.error(DOMAIN_NUMBER, TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);
         }
      } else {
         hilog.error(DOMAIN_NUMBER, TAG, `ArkTSCard download task failed`);
         let param: Record<string, string> = {
            'text': '刷新失敗'
         };
         let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);
         formProvider.updateForm(formId, formInfo);
      }
      httpRequest.destroy();
   }

   async onFormEvent(formId: string, message: string): Promise<void> {
      this.getData(formId);
   }
}
  1. 編寫刷新事件:參數傳到卡片組件內,組件接收參數。處理WidgetCard.ets卡片內邏輯。卡片頁面中使用LocalStorageProp裝飾需要刷新的卡片數據。
let casesCardInfo = new LocalStorage();
@Entry(casesCardInfo)
@Component
struct Widget_DynamicCard {
  @LocalStorageProp('detail') detail: CASES[] = []; // 卡片對象集合
  private swiperController: SwiperController = new SwiperController();
  @LocalStorageProp('isLongCard') isLongCard: boolean = false;

  build() {
    // ...
  }
}

寫在最后

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
  • 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容