首页 > 其他分享 >HarmonyOS Next 实战卡片开发 03

HarmonyOS Next 实战卡片开发 03

时间:2024-12-15 19:46:01浏览次数:6  
标签:03 string 卡片 ets Next HarmonyOS FormIdStore formId id

HarmonyOS Next 实战卡片开发 03

在前面两张,我们基本掌握了卡片的使用流程,本章节就通过一个实战来加强对卡片使用的理解。

要完成的案例

image-20241024175411585


PixPin_2024-10-24_17-54-18

新建项目和新建服务卡片

image-20241031111045865


image-20241031111130741

设置沉浸式

entry/src/main/ets/entryability/EntryAbility.ets

image-20241031111326923

首页显示轮播图数据

PixPin_2024-10-31_11-27-05

1. 申请网络权限

entry/src/main/module.json5

image-20241031112238553

2. 新建工具文件 /utils/index.ets

entry/src/main/ets/utils/index.ets

export const swiperInit = () => {
  AppStorage.setOrCreate("swiperList", [
    "https://env-00jxhf99mujs.normal.cloudstatic.cn/card/1.webp?expire_at=1729734506&er_sign=e51cb3b4f4b28cb2da96fd53701eaa69",
    "https://env-00jxhf99mujs.normal.cloudstatic.cn/card/2.webp?expire_at=1729734857&er_sign=b2ffd42585568a094b9ecfb7995a9763",
    "https://env-00jxhf99mujs.normal.cloudstatic.cn/card/3.webp?expire_at=1729734870&er_sign=50d5f210191c113782958dfd6681cd2d",
  ]);
  AppStorage.setOrCreate("activeIndex", 0);
};

3. 初始化

entry/src/main/ets/entryability/EntryAbility.ets

image-20241031111803516

4. 页面中使用

entry/src/main/ets/pages/Index.ets


@Entry
@Component
struct Index {
  @StorageProp("swiperList")
  swiperList: string[] = []
  @StorageLink("activeIndex")
  activeIndex: number = 0


  build() {
    Column() {
      Swiper() {
        ForEach(this.swiperList, (img: string) => {
          Image(img)
            .width("80%")
        })
      }
      .loop(true)
      .autoPlay(true)
      .interval(3000)

      .onChange(index => this.activeIndex = index)
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundImage(this.swiperList[this.activeIndex])
    .backgroundBlurStyle(BlurStyle.Thin)
    .backgroundImageSize(ImageSize.Cover)
    .animation({ duration: 500 })
  }
}

5. 效果

PixPin_2024-10-31_11-27-05

创建卡片时,获取卡片 id

PixPin_2024-10-31_13-06-50

image-20241031222230532

1. 获取和返回卡片 id

这里解析下为什么要返回 id 给卡片组件,因为后期卡片想要向应用通信时,应用响应数据要根据卡片 id 来响应。

另外 formExtensionAbility 进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在 10 秒,如 10 秒内没有新的

生命周期回调触发则进程自动退出。针对可能需要 10 秒以上才能完成的业务逻辑,建议拉起主应用进行处理,处理完成后使用updateForm通知卡片进行刷新

entry/src/main/ets/entryformability/EntryFormAbility.ets

  onAddForm(want: Want) {
    class FormData {
      // 获取卡片id
      formId: string = want.parameters!['ohos.extra.param.key.form_identity'].toString();
    }

    let formData = new FormData()
    return formBindingData.createFormBindingData(formData);
  }

2. 接受和显示卡片 id

entry/src/main/ets/widget/pages/WidgetCard.ets

const localStorage = new LocalStorage()

@Entry(localStorage)
@Component
struct WidgetCard {
  @LocalStorageProp("formId")
  formId: string = ""

  build() {
    Row() {
      Text(this.formId)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(10)

  }
}

3. 效果

PixPin_2024-10-31_13-06-50

记录卡片 id,持久化存储

image-20241031223836426

主要流程如下:

  1. 封装持久化存储卡片 id 的工具类
  2. 初始化卡片 id 工具类
  3. 卡片主动上传卡片 id
  4. 应用 Aibility 接收卡片 id
  5. 接收卡片 id 并且持久化
  6. 移除卡片时,删除卡片 id

1. 封装持久化存储卡片 id 的工具类

此时接收到卡片 id 后,需要将卡片 id 持久化存储,避免重新打卡手机时,无法联系到已经创建的卡片

entry/src/main/ets/utils/index.ets

export class FormIdStore {
  static key: string = "wsy_collect";
  static dataPreferences: preferences.Preferences | null = null;
  static context: Context | null = null;

  //  初始化
  static init(context?: Context) {
    if (!FormIdStore.dataPreferences) {
      if (context) {
        FormIdStore.context = context;
      }
      FormIdStore.dataPreferences = preferences.getPreferencesSync(
        FormIdStore.context || getContext(),
        { name: FormIdStore.key }
      );
    }
  }

  //  获取卡片id 数组
  static getList() {
    FormIdStore.init();
    const str = FormIdStore.dataPreferences?.getSync(FormIdStore.key, "[]");
    const list = JSON.parse(str as string) as string[];
    console.log("list卡片", list);
    return list;
  }

  // 新增卡片数组
  static async set(item: string) {
    FormIdStore.init();
    const list = FormIdStore.getList();
    if (!list.includes(item)) {
      list.push(item);
      FormIdStore.dataPreferences?.putSync(
        FormIdStore.key,
        JSON.stringify(list)
      );
      await FormIdStore.dataPreferences?.flush();
    }
  }

  // 删除元素
  static async remove(item: string) {
    FormIdStore.init();
    const list = FormIdStore.getList();
    const index = list.indexOf(item);
    if (index !== -1) {
      list.splice(index, 1);
      FormIdStore.dataPreferences?.putSync(
        FormIdStore.key,
        JSON.stringify(list)
      );
      await FormIdStore.dataPreferences?.flush();
    }
  }
}

2. 初始化卡片 id 工具类

  1. onCreate 中初始化

    entry/src/main/ets/entryability/EntryAbility.ets

      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        FormIdStore.init(this.context)
    
  2. onAddForm 中初始化

    onAddForm(want: Want) {
      FormIdStore.init(this.context)
    

3. 卡片主动上传卡片 id

利用 watch 监听器来触发上传

entry/src/main/ets/widget/pages/WidgetCard.ets

const localStorage = new LocalStorage()

@Entry(localStorage)
@Component
struct WidgetCard {
  @LocalStorageProp("formId")
  @Watch("postData")
  formId: string = ""

  // 上传卡片id
  postData() {
    postCardAction(this, {
      action: 'call',
      abilityName: 'EntryAbility',
      params: {
        method: 'createCard',
        formId: this.formId
      }
    });
  }

  build() {
    Row() {
      Text(this.formId)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(10)

  }
}

4. 应用 Aibility 接收卡片 id

entry/src/main/ets/entryability/EntryAbility.ets

// callee中要求返回的数据类型
class MyPara implements rpc.Parcelable {
  marshalling(dataOut: rpc.MessageSequence): boolean {
    return true
  }

  unmarshalling(dataIn: rpc.MessageSequence): boolean {
    return true
  }
}

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    FormIdStore.init(this.context)
    // 监听事件
    this.callee.on("createCard", (data: rpc.MessageSequence) => {
      // 接收id
      const formId = (JSON.parse(data.readString() as string) as Record<string, string>).formId

      return new MyPara()
    })
  }

5. 接收卡片 id 并且持久化

  1. 开启后台运行权限 "ohos.permission.KEEP_BACKGROUND_RUNNING"

    entry/src/main/module.json5

        "requestPermissions": [
          {
            "name": "ohos.permission.INTERNET"
          },
          {
            "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
          }
        ],
    
  2. 持久化

     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        FormIdStore.init(this.context)
        // 监听事件
        this.callee.on("createCard", (data: rpc.MessageSequence) => {
          // 接收id
          const formId = (JSON.parse(data.readString() as string) as Record<string, string>).formId
          // 2 持久化
          FormIdStore.set(formId)
    
          return new MyPara()
        })
      }
    
    

6. 移除卡片时,删除卡片 id

entry/src/main/ets/entryformability/EntryFormAbility.ets

  onRemoveForm(formId: string) {
    FormIdStore.remove(formId)
  }

封装下载图片工具类

将下载图片和拼接卡片需要格式的代码封装到文件中 该工具类可以同时下载多张图片,使用了 Promise.all 来统一接收结果

entry/src/main/ets/utils/CardDonwLoad.ets

1. 封装的工具说明

interface IDownFile {
  fileName: string;
  imageFd: number;
}

// 卡片显示 需要的数据结构
export class FormDataClass {
  // 卡片需要显示图片场景, 必填字段(formImages 不可缺省或改名), fileName 对应 fd
  formImages: Record<string, number>;

  constructor(formImages: Record<string, number>) {
    this.formImages = formImages;
  }
}

export class CardDownLoad {
  context: Context | null;
  then: Function | null = null;
  imgFds: number[] = [];

  constructor(context: Context) {
    this.context = context;
  }

  // 下载单张图片
  async downLoadImage(netFile: string) {}

  // 下载一组图片
  async downLoadImages(netFiles: string[]) {}

  // 私有下载网络图片的方法
  private async _down(netFile: string) {}

  // 手动关闭文件
  async closeFile() {
    this.imgFds.forEach((fd) => fileIo.closeSync(fd));
    this.imgFds = [];
  }
}

2. 封装的实现

import { http } from "@kit.NetworkKit";
import { fileIo } from "@kit.CoreFileKit";

interface IDownFile {
  fileName: string;
  imageFd: number;
}

// 卡片显示 需要的数据结构
export class FormDataClass {
  // 卡片需要显示图片场景, 必填字段(formImages 不可缺省或改名), fileName 对应 fd
  formImages: Record<string, number>;

  constructor(formImages: Record<string, number>) {
    this.formImages = formImages;
  }
}

export class CardDownLoad {
  context: Context | null;
  then: Function | null = null;
  imgFds: number[] = [];

  constructor(context: Context) {
    this.context = context;
  }

  // 下载单张图片
  async downLoadImage(netFile: string) {
    const obj = await this._down(netFile);
    let imgMap: Record<string, number> = {};
    imgMap[obj.fileName] = obj.imageFd;
    if (!this.imgFds.includes(obj.imageFd)) {
      this.imgFds.includes(obj.imageFd);
    }
    return new FormDataClass(imgMap);
  }

  // 下载一组图片
  async downLoadImages(netFiles: string[]) {
    let imgMap: Record<string, number> = {};

    const promiseAll = netFiles.map((url) => {
      const ret = this._down(url);
      return ret;
    });
    const resList = await Promise.all(promiseAll);
    resList.forEach((v) => {
      imgMap[v.fileName] = v.imageFd;
      if (!this.imgFds.includes(v.imageFd)) {
        this.imgFds.includes(v.imageFd);
      }
    });

    return new FormDataClass(imgMap);
    // return resList.map(v => `memory://${v.fileName}`)
  }

  // 私有下载网络图片的方法
  private async _down(netFile: string) {
    let tempDir = this.context!.getApplicationContext().tempDir;
    let fileName = "file" + Date.now();
    let tmpFile = tempDir + "/" + fileName;

    let httpRequest = http.createHttp();
    let data = await httpRequest.request(netFile);
    if (data?.responseCode == http.ResponseCode.OK) {
      let imgFile = fileIo.openSync(
        tmpFile,
        fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE
      );

      await fileIo.write(imgFile.fd, data.result as ArrayBuffer);

      const obj: IDownFile = {
        fileName,
        imageFd: imgFile.fd,
      };
      // setTimeout(() => {
      // }, 0)
      // fileIo.close(imgFile);
      httpRequest.destroy();
      return obj;
    } else {
      httpRequest.destroy();
      return Promise.reject(null);
    }
  }

  // 手动关闭文件
  async closeFile() {
    this.imgFds.forEach((fd) => fileIo.closeSync(fd));
    this.imgFds = [];
  }
}

卡片发起通知,获取网络图片

PixPin_2024-10-31_20-52-33

image-20241031224019379

  1. 准备好卡片代码,用来接收返回的网络图片数据
  2. 应用 Ability 接收卡片通知,下载网络图片,并且返回给卡片

1. 准备好卡片代码,用来接收返回的网络图片数据

const localStorage = new LocalStorage()

@Entry(localStorage)
@Component
struct WidgetCard {
  // 用来显示图片的数组
  @LocalStorageProp("imgNames")
  imgNames: string[] = []
  // 卡片id
  @LocalStorageProp("formId")
  @Watch("postData")
  formId: string = ""
  // 当前显示的大图 -  和 应用-首页保持同步
  @LocalStorageProp("activeIndex")
  activeIndex: number = 0

  postData() {
    postCardAction(this, {
      action: 'call',
      abilityName: 'EntryAbility',
      params: {
        method: 'createCard',
        formId: this.formId
      }
    });
  }

  build() {
    Row() {
      ForEach(this.imgNames, (url: string, index: number) => {
        Image(url)
          .border({ width: 1 })
          .layoutWeight(this.activeIndex === index ? 2 : 1)
          .height(this.activeIndex === index ? "90%" : "60%")
          .borderRadius(this.activeIndex === index ? 12 : 5)
          .animation({ duration: 300 })
      })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(10)
    .backgroundImage(this.imgNames[this.activeIndex])
    .backgroundBlurStyle(BlurStyle.Thin)
    .backgroundImageSize(ImageSize.Cover)
    .animation({ duration: 300 })
  }
}

2. 应用 Ability 接收卡片通知,下载网络图片,并且返回给卡片

entry/src/main/ets/entryability/EntryAbility.ets

// callee中要求返回的数据类型

class MyPara implements rpc.Parcelable {
  marshalling(dataOut: rpc.MessageSequence): boolean {
    return true;
  }

  unmarshalling(dataIn: rpc.MessageSequence): boolean {
    return true;
  }
}

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 监听事件
    this.callee.on("createCard", (data: rpc.MessageSequence) => {
      // 接收id
      const formId = (
        JSON.parse(data.readString() as string) as Record<string, string>
      ).formId;
      // 持久化
      FormIdStore.set(formId);

      class FormData {
        imgName?: string[] = [];
        activeIndex?: number = AppStorage.get("activeIndex")!;
      }

      const formInfo = formBindingData.createFormBindingData(new FormData());
      // 先响应空数据 等待网络图片下载完毕后,再响应网络图片数据
      formProvider.updateForm(formId, formInfo);
      const cardDownLoad = new CardDownLoad(this.context);
      cardDownLoad
        .downLoadImages(AppStorage.get("swiperList") as string[])
        .then((ret) => {
          const urls = Object.keys(ret.formImages).map((v) => `memory://${v}`);
          // 返回卡片数组
          class CimgNames {
            imgNames: string[] = urls;
            formImages: Record<string, number> = ret.formImages;
          }

          const formInfo = formBindingData.createFormBindingData(
            new CimgNames()
          );
          formProvider.updateForm(formId, formInfo);
          //   关闭文件
          cardDownLoad.closeFile();
        });

      // 临时处理、防止报错
      return new MyPara();
    });
  }
}

3. 效果

PixPin_2024-10-31_20-52-33

卡片同步轮播

image-20241031224212664

该功能主要是首页在图片轮播时,通知所有的卡片同时更新

entry/src/main/ets/pages/Index.ets

1. 监听轮播图 onChange 事件,设置当前显示的下标

      Swiper() {
        ForEach(this.swiperList, (img: string) => {
          Image(img)
            .width("80%")
        })
      }
      .loop(true)
      .autoPlay(true)
      .interval(3000)
      .onChange(index => this.activeIndex = index)

2. 监听下标的改变,通知持久化存储中所有的卡片进行更新

  @StorageLink("activeIndex")
  @Watch("changeIndex")
  activeIndex: number = 0

  // 通知所有卡片一并更新
  changeIndex() {
    const list = FormIdStore.getList()
    const index = this.activeIndex
    list.forEach(id => {
      class FdCls {
        activeIndex: number = index
      }

      const formInfo = formBindingData.createFormBindingData(new FdCls())
      formProvider.updateForm(id, formInfo)
    })
  }

3. 效果

PixPin_2024-10-31_22-18-40

总结

FormExtensionAbility 进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在 10 秒,如 10 秒内没有新的

生命周期回调触发则进程自动退出。针对可能需要 10 秒以上才能完成的业务逻辑,建议拉起主应用进行处理,处理完成后使用updateForm通知卡片进行刷

新。

1. 项目开发流程

  1. 新建项目与服务卡片:创建新的项目和服务卡片,为后续开发搭建基础框架。
  2. 设置沉浸式体验:在EntryAbility.ets中进行相关设置,优化用户视觉体验。

2. 首页轮播图数据显示

  1. 申请网络权限:在module.json5中申请,为数据获取做准备。
  2. 新建工具文件:在/utils/index.ets中创建swiperInit函数,用于初始化轮播图数据,包括设置轮播图列表和初始索引。
  3. 初始化操作:在EntryAbility.ets中进行初始化。
  4. 页面使用:在Index.ets中构建轮播图组件,通过SwiperForEach等实现轮播效果,轮播图可自动播放、循环,并能响应索引变化。

3. 卡片 id 的处理

  1. 获取与返回卡片 id:在EntryFormAbility.etsonAddForm函数中获取卡片 id,并返回给卡片组件。原因是后期卡片向应用通信时,应用需根据卡片 id 响应,同时注意formExtensionAbility进程的后台限制。
  2. 接受与显示卡片 id:在WidgetCard.ets中接受并显示卡片 id。
  3. 卡片 id 的持久化存储
    • 封装工具类:在/utils/index.ets中封装FormIdStore类,实现初始化、获取卡片 id 列表、新增和删除卡片 id 等功能。
    • 初始化工具类:在EntryAbility.etsonCreateonAddForm中初始化。
    • 卡片主动上传:在WidgetCard.ets中利用watch监听器触发上传卡片 id。
    • 应用接收与持久化:在EntryAbility.ets中接收卡片 id 并持久化,同时需开启后台运行权限。
    • 移除卡片时处理:在EntryFormAbility.etsonRemoveForm中删除卡片 id。

4. 图片相关操作

  1. 封装下载图片工具类:在CardDonwLoad.ets中封装,包括下载单张或一组图片的功能,以及手动关闭文件功能,涉及网络请求和文件操作。
  2. 卡片发起通知获取网络图片
    • 卡片准备接收数据:在WidgetCard.ets中准备接收网络图片数据的代码,包括显示图片数组、卡片 id 等相关变量和操作。
    • 应用处理与返回数据:在EntryAbility.ets中接收卡片通知,下载网络图片并返回给卡片,先响应空数据,下载完成后再更新卡片数据。

5. 卡片同步轮播功能

  1. 监听轮播图 onChange 事件:在Index.ets中通过Swiper组件的onChange事件设置当前显示下标。
  2. 通知卡片更新:在Index.ets中监听下标改变,通知持久化存储中的所有卡片更新,实现首页与卡片轮播同步。

标签:03,string,卡片,ets,Next,HarmonyOS,FormIdStore,formId,id
From: https://www.cnblogs.com/aspXiaoBai/p/18608387

相关文章

  • HarmonyOS Next V2 @Event
    HarmonyOSNextV2@Event背景在上一节中,我们针对父子组件,讲了关于传递数据的知识。我们了解到@Local是管理自己内部的数据的,@Param是负责接收父组件的数据的,而且子自己内部不能直接修改按照一个组件最基本的功能,既能接收外部传入的数据,也要向外部传递数据。那么@Event......
  • HarmonyOS Next V2 @Local 和@Param
    HarmonyOSNextV2@Local和@Param@Local背景@Local是harmony应用开发中的v2版本中对标@State的状态管理修饰器,它解决了@State对状态变量更改的检测混乱的问题:@State修饰的状态变量可以是组件内部自己定义的@State修饰的状态也可以由外部父组件传递这样就导致......
  • HarmonyOS Next V2 状态管理实战
    HarmonyOSNextV2状态管理实战介绍以下案例适合刚开始手鸿蒙开发的小伙伴,有大量的最新逻辑锻炼、鸿蒙核心语法、使用最新鸿蒙的@Local、@Computed等装饰器来完成。另外,考虑在学习知识的知识时候,优先关注核心功能,所以提供的布局都会适当简化,但是能保证把核心功能展示出来。......
  • HarmonyOS Next V2 状态管理@ObservedV2 基本使用
    HarmonyOSNextV2状态管理@ObservedV2基本使用背景最近Harmony应用开发技术中推出了新版的状态管理技术,试用过后,直呼很香。我们来看为什么?因为在Harmony应用开发过程中,我们一定会碰到监听嵌套类/深层次属性的需求,如希望son的weight属性变化后,可以引起UI刷新。@Obser......
  • HarmonyOS Next V2 状态管理 AppStorageV2 和 PersistenceV2
    HarmonyOSNextV2状态管理AppStorageV2和PersistenceV2前言在HarmonyOS应用开发过程中,我们已经学习过了不少关于状态管理相关的技术,如@ObservedV2装饰器和@Trace装饰器:类属性变化观测@ComponentV2装饰器:自定义组件@Local装饰器:组件内部状态@Param:组件外部输入@Once:初......
  • HarmonyOS Next 关于页面渲染的性能优化方案
    HarmonyOSNext关于页面渲染的性能优化方案HarmonyOSNext应用开发中,用户的使用体验至关重要。其中用户启动APP到呈现页面主要包含三个步骤:框架初始化页面加载布局渲染从页面加载到布局渲染中,主要包含了6个环节:执行页面文件生成页面节点树页面节点树挂载布局渲......
  • HarmonyOS Next 浅谈 发布-订阅模式
    HarmonyOSNext浅谈发布-订阅模式前言其实在目前的鸿蒙应用开发中,或者大前端时代、vue、react、小程序等等框架、语言开发中,普通的使用者越来越少的会碰到必须要掌握设计模式的场景。大白话意思就是一些框架封装太好了,使用者只管在它们的体系下使用就行,哪怕不懂设计模式,也不妨......
  • HarmonyOS Next 元服务新建到上架全流程
    HarmonyOSNext元服务新建到上架全流程接上篇这篇文章的主要目的是介绍元服务从新建到上家的完整流程在AGC平台上新建一个项目链接一个项目可以多个应用AGC新建一个元服务应用新建一个本地元服务项目如果成功在AGC平台上新建过元服务,那么这里会自动显示修改元服务......
  • HarmonyOS Next 简单上手元服务开发
    HarmonyOSNext简单上手元服务开发万物互联时代,人均持有设备量不断攀升,设备种类和使用场景更加多样,使得应用开发、应用入口变得更加复杂。在此背景下,应用提供方和用户迫切需要一种新的服务提供方式,使应用开发更简单、服务(如听音乐、打车等)的获取和使用更便捷。为此,HarmonyOS除......
  • HarmonyOS Next 如何优雅的编写注释
    HarmonyOSNext如何优雅的编写注释程序员箴言我最讨厌世界上的两种人:第一种是不写注释的人第二种是让我写注释的人前言随着HarmonyOSNEXT的发展加快,不少的公司已经陆续加大了资源来开发软件项目。那么伴随项目的发展,项目团队也需要按照一定的规范来编写项目注释或者......