鸿蒙 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
export class GlobalContext {
private static instance: GlobalContext;
private _objects = new Map<string, Object>();
private constructor() {}
public static getContext(): GlobalContext {
if (!GlobalContext.instance) {
GlobalContext.instance = new GlobalContext();
}
return GlobalContext.instance;
}
getObject(value: string): Object | undefined {
return this._objects.get(value);
}
setObject(key: string, objectClass: Object): void {
this._objects.set(key, objectClass);
}
}
尺寸适配类
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 "";
}
文件操作
通过路径写文件
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);
});
App 与 H5 通信-JsBridge 封装
ets
/*
* Index.ets
* 网页与App交互的示例
* 封装JSBridge
*/
import WebView from '@ohos.web.webview';
import JSBridge from './JsBridge';
@Entry
@Component
struct WebJSBridgeExample {
webController: WebView.WebviewController = new WebView.WebviewController();
private jsBridge: JSBridge = new JSBridge(this.webController);
build() {
Column() {
Web({
src: $rawfile('MainPage.html'),
controller: this.webController
})
.javaScriptAccess(true)
.javaScriptProxy(this.jsBridge.javaScriptProxy)
.onPageBegin(() => {
// 初始化JsBridge,向网页注入脚本
this.jsBridge.initJsBridge();
})
}
}
}
ets
// JsBridge.ets
import WebView from '@ohos.web.webview';
import promptAction from '@ohos.promptAction';
import hilog from '@ohos.hilog';
export interface CallType {
call: (func: string, params: string) => void;
}
export interface JavaScriptItem {
object: CallType;
name: string;
methodList: Array<string>;
controller: WebviewController;
}
export interface ParamsItem {
callID: number;
data: ParamsDataItem;
}
export interface ParamsDataItem {
name?: string;
tel?: string;
}
/**
* 定义需要注入到网页上的脚本
*/
export const code = `
const JSBridgeMap = {};
let callID = 0;
function JSBridgeCallback (id, params){
JSBridgeMap[id](params);
JSBridgeMap[id] = null;
delete JSBridgeMap[id];
}
window.ohosCallNative = {
callNative(method, params, callback){
const id = callID++;
const paramsObj = {
callID: id,
data: params || null
}
JSBridgeMap[id] = callback || (() => {}); // 注册回调
JSBridgeHandle.call(method, JSON.stringify(paramsObj));
}
}
`;
/**
* Define bridge class connect WebView and ArkTS.
*/
export default class JsBridge {
controller: WebView.WebviewController;
constructor(controller: WebView.WebviewController) {
this.controller = controller;
}
/**
* Injects the JavaScript object into window and invoke the function in window.
*
* @returns javaScriptProxy object.
*/
get javaScriptProxy(): JavaScriptItem {
let result: JavaScriptItem = {
object: { // 参与注册的对象。只能声明方法,不能声明属性。
call: this.call
},
name: "JSBridgeHandle", // 注册对象的名称,与window中调用的对象名一致。
methodList: ['call'], // 参与注册的应用侧JavaScript对象的方法。
controller: this.controller // 组件的控制器
}
return result;
}
initJsBridge(): void {
this.controller.runJavaScript(code);
}
/**
* 例子:执行网页上的方法
*/
runWebFunction() {
const code = `webFunction()`; // 要在网页上执行的代码
this.controller.runJavaScript(code, (error, result) => {
if (error) {
console.info(`run JavaScript error: ` + JSON.stringify(error))
}
if (result) {
console.info(`The webFunction() return value is: ${result}`)
promptAction.showToast({
message: `The webFunction() return value is: ${result}`
})
}
});
}
/**
* 接收网页端的调用,通过方法名(func)分发
*/
call = (func: string, params: string): void => {
hilog.debug(0x0000, "call_func", func)
hilog.debug(0x0000, "call_params", params)
const paramsObject: ParamsItem = JSON.parse(params);
let result: Promise<string> = new Promise((resolve) => resolve(''));
switch (func) {
default:
break;
}
result.then((data: string) => {
// callID 用于映射回调函数
this.callback(paramsObject?.callID, data);
})
}
/**
* The ArkTS invoke the WebView by using runJavaScript.
*/
callback = (id: number, data: string): void => {
this.controller.runJavaScript(`JSBridgeCallback("${id}", ${JSON.stringify(data)})`);
}
}
静态文件
html
<!-- rawfile/MainPage.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>testApp</title>
</head>
<body>
<div class="container">
<h1>Web Page</h1>
<div id="text"></div>
<div id="text2"></div>
</div>
<script src="./js/mainPage.js"></script>
</body>
</html>
js
// rawfile/js/mainPage.js
function runAppFunctionExample() {
// 调用App注入的方法
window.ohosCallNative.callNative('yourFunctionName', {}, (data) => {
// 执行App的回调
document.getElementById('text2').innerText = JSON.stringify(data);
})
}
function webFunction() {
document.getElementById('text').innerText = '网页上的方法被调用了'
return '返回值'
}
document.getElementById('text').innerText = 'test'
document.getElementById('text2').innerText = '3秒后自动调用App的方法'
setTimeout(() => {
runAppFunctionExample()
}, 3000)
全局弹窗封装
CustomDialog 方案
创建自定义弹窗组件
ets
@CustomDialog
export struct CustomDialogView {
@Link visible: boolean;
controller: CustomDialogController;
// 弹窗交互事件参数,点击确认和取消按钮时的回调函数
onCancel?: () => void;
onConfirm?: () => void;
build() {
Row() {
Button()
.onClick(() => {
this.visible = false;
this.onCancel?.();
})
}
}
}
自定义组件 Dialog,对自定义弹窗组件进行二次封装。
ets
@Component
export struct Dialog {
// 监听外部传入的visible变量,visible值发生变化时触发onChange回调函数
@Watch("onChange") @Link visible: boolean;
onCancel?: () => void;
onConfirm?: () => void;
// 通过CustomDialogController的builder参数绑定弹窗组件CustomDialogView
private controller = new CustomDialogController({
builder: CustomDialogView({
visible: $visible,
onCancel: this.onCancel,
onConfirm: this.onConfirm,
}),
})
/**
* 当visible的值变化时触发回调
*/
onChange(): void{
if (this.visible) {
this.controller.open();
} else {
this.controller.close();
}
}
// 二次封装的Dialog组件主要通过控制器控制弹窗,不需要任何界面
build() {
}
}
使用方导入自定义组件 Dialog 并传入相应入参。
ets
@Component
export struct CustomDialog {
// 外部定义visible变量作为弹窗组件入参,控制弹窗显隐
@State visible: boolean = false;
build() {
Column({ space: 20 }) {
Button()
// 点击修改visible变量后,visible的值可以被Dialog组件监听并响应
.onClick(() => this.visible = !this.visible)
// 通过双向绑定visible变量,实现外部控制弹窗
Dialog({
visible: $visible,
})
}
}
}
其它方案(子窗口)
https://gitee.com/wolfx/ohos_libs/tree/dev/package_utils/src/main/ets/common