Skip to content

鸿蒙 NEXT 开发:一些工具的包装

ObservedArray 可观察的数组类

ts
@Observed
class ObservedArray<T> extends Array<T> {
  constructor(args: T[]) {
    if (args instanceof Array) {
      super(...args);
    } else {
      super(args);
    }
  }
}

纯对象转 Class

class-transformer

ets
@State obj2: BasicJsonStruct = plainToClassFromExist(new BasicJsonStruct(), {
  obj1: {
    obj2: {
      text: '这是obj2的初始值'
    }
  }
})

fill_class

深拷贝(ETS)

ts
function _deepCopy(obj: Object): Object {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (Array.isArray(obj)) {
        let _copy: Object[] = [];
        for (let i = 0; i < obj.length; i++) {
            _copy[i] = _deepCopy(obj[i]);
        }
        return _copy;
    } else {
        let _copy: Record<string, Object> = {};
        let keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            let key: string = keys[i];
            if (Reflect.get(obj, key)) {
                _copy[key] = _deepCopy(Reflect.get(obj, key));
            }
        }
        return _copy;
    }
}

export function deepCopy<T>(obj: T): T {
    return _deepCopy(obj) as T;
}

尺寸适配类

ts
import display from "@ohos.display";

const context = getContext(this);

/**
 * Design drawing width.
 */
const DESIGN_WIDTH = 360;

/**
 * Design drawing height.
 */
const DESIGN_HEIGHT = 780;

/**
 * Fits tools with different sizes and lengths.
 */
export class DimensionUtil {
  static adaptDimension(value: number): number {
    const deviceDisplay = display.getDefaultDisplaySync();
    const widthScale = deviceDisplay.width / DESIGN_WIDTH;
    const virtualHeight = widthScale * DESIGN_HEIGHT;
    const designDim = Math.sqrt(DESIGN_WIDTH * DESIGN_WIDTH + DESIGN_HEIGHT * DESIGN_HEIGHT);
    const virtualDim = Math.sqrt(deviceDisplay.width * deviceDisplay.width + virtualHeight * virtualHeight);
    return (virtualDim * value) / designDim; // 放缩后长度
  }

  static adaptDimensionVp(value: number): number {
    return px2vp(DimensionUtil.adaptDimension(value));
  }

  static getPx(value: Resource): number {
    const beforeVp = context.resourceManager.getNumber(value.id);
    return DimensionUtil.adaptDimension(beforeVp);
  }

  static getVp(value: Resource): number {
    const beforeVp = context.resourceManager.getNumber(value.id);
    return px2vp(DimensionUtil.adaptDimension(beforeVp));
  }

  static getFp(value: Resource): number {
    const beforeFp = context.resourceManager.getNumber(value.id);
    return px2fp(DimensionUtil.adaptDimension(beforeFp));
  }
}

用于懒加载的数据源封装

ts
export class BasicDataSource<T> implements IDataSource {
  private listeners: DataChangeListener[] = [];
  protected dataArray: T[] = [];

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach((listener) => {
      listener.onDataReloaded();
    });
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach((listener) => {
      listener.onDataAdd(index);
    });
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach((listener) => {
      listener.onDataChange(index);
    });
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach((listener) => {
      listener.onDataDelete(index);
    });
  }

  totalCount() {
    return this.dataArray.length;
  }

  // 重新加载数据
  reload() {
    this.notifyDataReload();
  }

  getData(index: number) {
    return this.dataArray[index];
  }

  insertData(index: number, data: T) {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  pushData(data: T) {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  removeData(index: number) {
    this.dataArray.splice(index, 1);
    this.notifyDataDelete(index);
  }
}

权限工具

ts
import { BusinessError } from "@kit.BasicServicesKit";
import { abilityAccessCtrl, bundleManager, PermissionRequestResult, Permissions } from "@kit.AbilityKit";

/**
 * 查询是否有单个权限
 * @param permission 单个权限字符串
 * @returns
 */
export async function checkAccessToken(permission: Permissions): Promise<boolean> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;

  // 获取应用程序的accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(
      bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
    );
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}

/**
 * 用于UIAbility拉起弹框请求用户授权
 * 如果用户拒绝授权,将无法再次拉起弹窗,需要用户在系统应用“设置”的界面中手动授予权限。
 * @param permissions 权限数组
 * @returns
 */
export async function requestPermissions(permissions: Array<Permissions>): Promise<boolean> {
  const context = getContext();
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let ret = true;
  try {
    let data: PermissionRequestResult = await atManager.requestPermissionsFromUser(context, permissions);
    let grantStatus: Array<number> = data.authResults;
    let permissions1 = data.permissions;
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        console.log(`${permissions1} permissions is success`);
      } else {
        console.log(`${permissions1} permissions is failed`);
        ret = false;
      }
    }
  } catch (e) {
    console.log(e.message);
    ret = false;
  }
  return ret;
}

图片选择工具

ts
import { picker } from "@kit.CoreFileKit";
import { BusinessError } from "@kit.BasicServicesKit";

export async function selectPicture(): Promise<string | undefined> {
  try {
    let photoSelectOptions = new picker.PhotoSelectOptions();
    photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    photoSelectOptions.maxSelectNumber = 1;
    let photoPicker = new picker.PhotoViewPicker();
    return photoPicker
      .select(photoSelectOptions)
      .then((photoSelectResult: picker.PhotoSelectResult) => {
        if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
          let filePath = photoSelectResult.photoUris[0];
          // Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + filePath);
          return filePath;
        }
        return "";
      })
      .catch((error: BusinessError) => {
        // Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
        return "";
      });
  } catch (err) {
    // Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
    return Promise.reject(err);
  }
  return "";
}

解码多重编码的 URI

ts
/**
 * 解码多重编码的uri
 * @param uri
 * @returns
 */
function decodeUriExt(uri: string) {
    const reg = new RegExp('%[0-9A-Fa-f]{2}')
    while (reg.test(uri)) {
        uri = decodeURIComponent(uri)
    }
    return uri
}

通过路径写文件

ts
export function writeFileSync(filePath: string, buffer: ArrayBuffer | string): number {
  const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  const writeLen = fs.writeSync(file.fd, buffer);
  fs.closeSync(file);
  return writeLen;
}

export async function writeFile(filePath: string, buffer: ArrayBuffer | string): Promise<number> {
  const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  const writeLen = await fs.write(file.fd, buffer);
  await fs.close(file);
  return writeLen;
}

文件上传工具

推荐 @ohos/commons-fileupload

窗口管理工具类

ts
import window from "@ohos.window";
import display from "@ohos.display";
import mediaquery from "@ohos.mediaquery";
import UIAbility from "@ohos.app.ability.UIAbility";

export default class GlobalContext extends AppStorage {
  static mainWin: window.Window | undefined = undefined;
  static mainWindowSize: window.Size | undefined = undefined;
}
/**
 * 窗口、屏幕相关信息管理类
 */
export class WindowManager {
  private static instance: WindowManager | null = null;
  private displayInfo: display.Display | null = null;
  private orientationListener = mediaquery.matchMediaSync("(orientation: landscape)");

  constructor() {
    this.orientationListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => {
      this.onPortrait(mediaQueryResult);
    });
    this.loadDisplayInfo();
  }

  /**
   * 设置主window窗口
   * @param win 当前app窗口
   */
  setMainWin(win: window.Window) {
    if (win == null) {
      return;
    }
    GlobalContext.mainWin = win;
    win.on("windowSizeChange", (data: window.Size) => {
      if (GlobalContext.mainWindowSize == undefined || GlobalContext.mainWindowSize == null) {
        GlobalContext.mainWindowSize = data;
      } else {
        if (GlobalContext.mainWindowSize.width == data.width && GlobalContext.mainWindowSize.height == data.height) {
          return;
        }
        GlobalContext.mainWindowSize = data;
      }

      let winWidth = this.getMainWindowWidth();
      AppStorage.setOrCreate<number>("mainWinWidth", winWidth);
      let winHeight = this.getMainWindowHeight();
      AppStorage.setOrCreate<number>("mainWinHeight", winHeight);
      let context: UIAbility = new UIAbility();
      context.context.eventHub.emit("windowSizeChange", winWidth, winHeight);
    });
  }

  static getInstance(): WindowManager {
    if (WindowManager.instance == null) {
      WindowManager.instance = new WindowManager();
    }
    return WindowManager.instance;
  }

  private onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) {
    if (mediaQueryResult.matches == AppStorage.get<boolean>("isLandscape")) {
      return;
    }
    AppStorage.setOrCreate<boolean>("isLandscape", mediaQueryResult.matches);
    this.loadDisplayInfo();
  }

  /**
   * 切换屏幕方向
   * @param ori 常量枚举值:window.Orientation
   */
  changeOrientation(ori: window.Orientation) {
    if (GlobalContext.mainWin != null) {
      GlobalContext.mainWin.setPreferredOrientation(ori);
    }
  }

  private loadDisplayInfo() {
    this.displayInfo = display.getDefaultDisplaySync();
    AppStorage.setOrCreate<number>("displayWidth", this.getDisplayWidth());
    AppStorage.setOrCreate<number>("displayHeight", this.getDisplayHeight());
  }

  /**
   * 获取main窗口宽度,单位vp
   */
  getMainWindowWidth(): number {
    return GlobalContext.mainWindowSize != null ? px2vp(GlobalContext.mainWindowSize.width) : 0;
  }

  /**
   * 获取main窗口高度,单位vp
   */
  getMainWindowHeight(): number {
    return GlobalContext.mainWindowSize != null ? px2vp(GlobalContext.mainWindowSize.height) : 0;
  }

  /**
   * 获取屏幕宽度,单位vp
   * 预览器无法获取,可手动给360vp
   */
  getDisplayWidth(): number {
    return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0;
  }

  /**
   * 获取屏幕高度,单位vp
   * 预览器无法获取,可手动给360vp
   */
  getDisplayHeight(): number {
    return this.displayInfo != null ? px2vp(this.displayInfo.height) : 0;
  }

  /**
   * 释放资源
   */
  release() {
    if (this.orientationListener) {
      this.orientationListener.off("change", (mediaQueryResult: mediaquery.MediaQueryResult) => {
        this.onPortrait(mediaQueryResult);
      });
    }
    if (GlobalContext.mainWin != null) {
      GlobalContext.mainWin.off("windowSizeChange");
    }
    WindowManager.instance = null;
  }
}

判断手机旋转方向

ts
import sensor from "@ohos.sensor";

// ...

sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
    let angle = 0;
    let magnitude = data.x * data.x + data.y * data.y;
    // Don't trust the angle if the magnitude is small compared to the y value
    if (magnitude * 4 >= data.z * data.z) {
        let OneEightyOverPi = 57.29577957855;
        angle = 90 - Math.round(Math.atan2(-data.y, data.x) * OneEightyOverPi);
        // normalize to 0 - 359 range
        while (angle >= 360) {
            angle -= 360;
        }
        while (angle < 0) {
            angle += 360;
        }
    }

    const orientation = this.convertAngle(angle)
    this.orientation(orientation);
    // ...
}, { interval: 500000000 });

// ...

function convertAngle(angle: number) {
    angle = angle % 360;
    Log.i(TAG, `angle ${angle}`);
    if (angle >= 120 && angle <= 240) {
        return window.Orientation.PORTRAIT;
    } else if (angle >= 30 && angle <= 150) {
        return window.Orientation.LANDSCAPE_INVERTED;
    } else if (angle >= 300 || (angle <= 60 && angle > 0)) {
        return window.Orientation.PORTRAIT_INVERTED;
    } else if (angle >= 210 && angle <= 330) {
        return window.Orientation.LANDSCAPE;
    } else {
        return window.Orientation.UNSPECIFIED;
    }
}

function orientation(orientation: number) {
    if (orientation == window.Orientation.PORTRAIT) {
        return "DeviceOrientation.portraitUp";
    } else if (orientation == window.Orientation.PORTRAIT_INVERTED) {
        return "DeviceOrientation.portraitDown";
    } else if (orientation == window.Orientation.LANDSCAPE) {
        return "DeviceOrientation.landscapeRight";
    } else if (orientation == window.Orientation.LANDSCAPE_INVERTED) {
        return "DeviceOrientation.landscapeLeft";
    } else {
        return null;
    }
}

强制旋转屏幕

ts
import window from "@ohos.window";

window.getLastWindow(this.ability?.context).then((windowClass) => {
  windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
});

获取资源字符串的值

ts
import resourceManager from '@ohos.resourceManager';
import Log from './Log';

const TAG = "[ResUtils]";

export class ResUtils {
  private static resourceManager: resourceManager.ResourceManager | undefined;

  private static getResourceManager() {
    if (!ResUtils.resourceManager) {
      ResUtils.resourceManager = getContext().resourceManager;
    }
    return ResUtils.resourceManager;
  }

  /**
   * 经测试可提升渲染性能
   */
  private static cacheMap = new Map<number, string>();

  /**
   * 资源转字符串
   * @param res 资源
   */
  static getResStr(res: Resource | undefined, params: Array<string | number> = []): string {
    if (!res) {
      return '';
    }

    const cacheKey = res.id;
    let stringRes: string = '';
    const cacheValue = ResUtils.cacheMap.get(cacheKey);
    if (cacheValue) {
      stringRes = cacheValue;
    } else {
      try {
        const resMgr = ResUtils.getResourceManager();
        stringRes = resMgr.getStringSync(res);
        // 缓存资源
        ResUtils.cacheMap.set(cacheKey, stringRes);
      } catch (e) {
        stringRes = ''
        Log.e(TAG, "getStringSync failed: msg= " + JSON.stringify(e))
      }
    }
    params.forEach(val => {
      stringRes = stringRes.replace('%s', val + '');
    });
    return stringRes;
  }
}

通过 p7b 获取 profile

ts
const p7b = String.raw`E:\WORK\kop\kh_applications_filemanager\signature\default_filemanageopenharmonypc_S86bmM4Fdn9PFIZA-EIxvYrLvmWI7jL_0zg32xGcPXI=.p7b`

const text = Deno.readTextFileSync(p7b);
const reg = /"distribution-certificate":"(.+?)"/
const m = reg.exec(text);
const s = m?.[1] || ''

s.split('\\n').forEach((line) => {
    console.info(line)
})

最后编辑时间:

Version 4.3