鸿蒙 NEXT 开发:一些工具的包装
ObservedArray 可观察的数组类
ts
@Observed
class ObservedArray<T> extends Array<T> {
constructor(args: T[]) {
if (args instanceof Array) {
super(...args);
} else {
super(args);
}
}
}
纯对象转 Class
ets
@State obj2: BasicJsonStruct = plainToClassFromExist(new BasicJsonStruct(), {
obj1: {
obj2: {
text: '这是obj2的初始值'
}
}
})
深拷贝(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;
}
文件上传工具
窗口管理工具类
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)
})