Skip to content

OpenHarmony开发:常用业务组件封装

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

树状递归组件示例

数据结构

ets
@Observed
class TreeNode<T> {
  id: number;
  detail: T;
  parent: TreeNode<T> | null;
  children: TreeNode<T>[] = [];

  constructor(id: number, detail: T, parent: TreeNode<T> | null) {
    this.id = id;
    this.detail = detail;
    this.parent = parent;
  }

  addChildren(children: TreeNode<T>[]) {
    children.forEach(child => {
      this.children.push(child);
    });
  }
}

@Component
struct TreeView {
  @ObjectLink tree: TreeNode<string>

  build() {
    Column() {
      Text(this.tree.detail)
        .fontSize(40)
        .onClick(() => {
          this.tree.detail += '1'
        })
      Column() {
        ForEach(this.tree.children, (child: TreeNode<string>) => {
          TreeView({ tree: child })
        }, (child: TreeNode<string>) => child.id.toString())
      }
      .padding({ left: 40 })
      .alignItems(HorizontalAlign.Start)
    }
    .alignItems(HorizontalAlign.Start)
  }
}

组件示例

ets
@Entry
@Component
struct Index {
  @State tree: TreeNode<string> = new TreeNode(0, 'root', null);

  aboutToAppear(): void {
    let node3 = new TreeNode(3, 'child3', this.tree)
    node3.addChildren([
      new TreeNode(44, 'child4', node3),
      new TreeNode(55, 'child5', node3),
    ]);

    this.tree.addChildren([
      new TreeNode(1, 'child1', this.tree),
      new TreeNode(2, 'child2', this.tree),
      node3,
    ]);
  }

  build() {
    Row() {
      Column() {
        Scroll() {
          TreeView({ tree: this.tree })
        }
        .scrollable(ScrollDirection.Vertical) // 滚动方向纵向
      }
      .width('100%')
      .height(200)
      .backgroundColor(Color.Gray)
    }
    .height('100%')
  }
}

自定义渲染节点

https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/reference/apis-arkui/js-apis-arkui-nodeController.md

ets
import { BuilderNode, FrameNode, NodeController, Size } from '@ohos.arkui.node';

class Params {
    text: string = "this is a text"
}

@Builder
function buttonBuilder(params: Params) {
    Column() {
        Button(params.text)
            .fontSize(12)
            .borderRadius(8)
            .borderWidth(2)
            .backgroundColor(Color.Orange)
    }
}

export class MyNodeController extends NodeController {
    private buttonNode: BuilderNode<[Params]> | null = null;
    private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);

    makeNode(uiContext: UIContext): FrameNode {
        console.log("makeNode")
        if (this.buttonNode == null) {
            this.buttonNode = new BuilderNode(uiContext);
            this.buttonNode.build(this.wrapBuilder, { text: 'This is a Button' })
        }
        return this.buttonNode!.getFrameNode()!;
    }

    aboutToResize(size: Size) {
        console.log("aboutToResize width : " + size.width + " height : " + size.height)
    }

    changeText(text: string) {
        this.buttonNode?.update({ text } as Params)
        // this.rebuild()
    }

    aboutToAppear() {
        console.log("aboutToAppear")
    }

    aboutToDisappear() {
        console.log("aboutToDisappear");
    }

    onTouchEvent(event:TouchEvent) {
        console.log("onTouchEvent");
    }
}
ets
private myNodeController: MyNodeController = new MyNodeController();

// ...

Column() {
    NodeContainer(this.myNodeController).onClick(() => {
        // promptAction.showToast({message: '111'})
        this.myNodeController.changeText('111')
    })
    Text(JSON.stringify(this.obj))
    Text(this.msg).fontSize(20).fontColor(Color.White)
}

最后编辑时间:

Version 4.3