场景描述

在特殊的H5场景下需要应用拉起自定义键盘进行输入。

场景一:使用jsBridge拉起自定义弹窗写自定义键盘,再通过jsBridge传参实现输入。

场景二:使用web的同层渲染将原生textInput组件渲染到页面上。

方案描述

通过注册一个js代理对象被web的registerJavaScriptProxy方法调用拉起CustomDialog,在CustomDialog上放置一个customkeyboard

场景一:通过jsBridge拉起自定义弹窗,在自定义弹窗上放置自定义键盘,例如需要输入密码时的安全键盘

效果图

方案

通过注册一个js代理对象被web的registJavaScriptProxy方法调用拉起CustomDialog,在CustomDialog上放置一个自定义键盘组件,通过在H5上input标签的readonly属性和注册的js方法changeNumbers实现在原生端输入数字传到H5上,他们之间通过@Link装饰器绑定的变量进行传值,所以点击删除输入的内容也是可以在H5上实现的。

核心代码

  1. 通过javaScriptProxy方法拉起自定义弹窗,在H5上的input标签绑定一个onclick事件,当点击输入框后会调用从原生注册过来的js代理方法openWindow。

     <input type="text" name="number_info" readonly onclick="openWindow()" value="" style="width: 500px;height: 100px;font-size:50px;border:1px solid # f00;">
      <script>
      function openWindow() {
        let value = document.getElementsByName("number_info")[0].value;
        window.myJsb.openDialog(value)
      }
     </script>
  2. 当H5上openWindow方法被调用后会通过jsBridge调用以下两个js代理方法打开自定义弹窗。

    jsbObject: JsbObject = {
      openDialog: (value: string) => {
        this.showDialog(this, value);
      }
    }
    showDialog(context: object, value: string) {
      // 把自定义弹窗调出来
      this.currentData = value;
      this.dialogController.open()
    }
    
    Web({ src: "resource://rawfile/web_test.html", controller: this.webviewController })
      .javaScriptAccess(true)
      .javaScriptProxy({
        name: "myJsb",
        object: this.jsbObject,
        methodList: ["openDialog"],
        controller: this.webviewController
      })
  3. 将自定义键盘放置在自定义弹窗上。

    @CustomDialog
    struct CustomDialogExample {
      @Link currentData: string
      dialogControllerTwo: CustomDialogController | null = new CustomDialogController({
        builder: CustomDialogExample({ currentData: $currentData }),
        alignment: DialogAlignment.Bottom,
        offset: { dx: 0, dy: -25 }
      })
      controller?: CustomDialogController
    
      build() {
        Column() {
          Button('x').onClick(() => {
            // 关闭自定义键盘
            if (this.controller != undefined) {
              this.controller.close()
            }
          })
          Grid() {
            ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '删除'], (item: number | string) => {
              GridItem() {
                Button(item + "")
                  .width(110).onClick(() => {
                  if (item == '删除') {
                    if (this.currentData.length > 0) {
                      this.currentData = this.currentData.substring(0, this.currentData.length - 1);
                    }
                  } else {
                    this.currentData += item
                  }
                })
              }
            })
          }.maxCount(3).columnsGap(10).rowsGap(10).padding(5)
        }.backgroundColor(Color.Gray)
      }
    }
  4. 在自定义键盘上输入内容的时候会调用onChangeInputValue方法,通过里面的runJavaScript调用H5上的js方法changeNumber传值到H5的输入框中。

    onChangeInputValue(stateName: string){
      console.log('this.currentData:' + this.currentData)
      this.webviewController.runJavaScript('changeNumber("'+ this.currentData +'")')
        .then((result) => {
          console.log('result: ' + result);
        })
    }
<<input type="text" name="number_info" readonly onclick="openWindow()" value="" style="width: 500px;height: 100px;font-size:50px;border:1px solid # f00;" />
 <script>
  function changeNumber(value){
    document.getElementsByName("number_info")[0].value = value;
  }
 </script>

场景二:通过同层渲染渲染一个原生的自定义键盘

效果图

方案

整体实现效果为:通过web的同层渲染功能实现将原生TextInput组件渲染到H5需要使用自定义键盘的页面中,这样就可以实现在H5拉起自定义键盘,并且使用它的全部功能。

核心代码

  1. 创建一个自定义键盘并绑定到原生textInput组件上。

    @Component
    struct ButtonComponent {
      controller1: TextInputController = new TextInputController()
      @State inputValue: string = ""
    
      // 自定义键盘组件
      @Builder
      CustomKeyboardBuilder() {
        Column() {
          Button('x').onClick(() => {
            // 关闭自定义键盘
            this.controller1.stopEditing()
          })
          Grid() {
            ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '# '], (item: number | string) => {
              GridItem() {
                Button(item + "")
                  .width(110).onClick(() => {
                  this.inputValue += item
                })
              }
            })
          }.maxCount(3).columnsGap(10).rowsGap(10).padding(5)
        }.backgroundColor(Color.Pink)
      }
    
      @ObjectLink params: Params
      @State bkColor: Color = Color.Red
      @State outSetValueTwo: number = 40
      @State outSetValueOne: number = 40
      @State tipsValue: number = 40
      controller: web_webview.WebviewController = new web_webview.WebviewController();
    
      build() {
        Column() {
          TextInput({ controller: this.controller1, text: this.inputValue })// 绑定自定义键盘
            .customKeyboard(this.CustomKeyboardBuilder()).margin(10).border({ width: 1 })
        }
        .width(this.params.width)
        .height(this.params.height)
      }
    }
  2. 将原生textInput组件通过web同层渲染功能渲染到H5上的embed标签上。

    @Entry
    @Component
    struct WebIndex {
      browserTabController: WebviewController = new webview.WebviewController()
    
      build() {
        Column() {
          Web({ src: $rawfile("test.html"), controller: this.browserTabController })// 配置同层渲染开关开启。
            .enableNativeEmbedMode(true)// 获取embed标签的生命周期变化数据。
            .onNativeEmbedLifecycleChange((embed) => {
              console.log("NativeEmbed surfaceId" + embed.surfaceId);
              // 获取web侧embed元素的id。
              const componentId = embed.info?.id?.toString() as string
              if (embed.status == NativeEmbedStatus.CREATE) {
                console.log("NativeEmbed create" + JSON.stringify(embed.info))
                // 创建节点控制器,设置参数并rebuild。
                let nodeController = new MyNodeController()
                nodeController.setRenderOption({
                  surfaceId: embed.surfaceId as string,
                  type: embed.info?.type as string,
                  renderType: NodeRenderType.RENDER_TYPE_TEXTURE,
                  embedId: embed.embedId as string,
                  width: px2vp(embed.info?.width),
                  height: px2vp(embed.info?.height)
                })
                nodeController.setDestroy(false);
                // 根据web传入的embed的id属性作为key,将nodeController存入map。
                this.nodeControllerMap.set(componentId, nodeController)
                // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
                this.componentIdArr.push(componentId)
              } else if (embed.status == NativeEmbedStatus.UPDATE) {
                let nodeController = this.nodeControllerMap.get(componentId)
                nodeController?.updateNode({
                  textOne: 'update',
                  width: px2vp(embed.info?.width),
                  height: px2vp(embed.info?.height)
                } as ESObject)
              } else {
                let nodeController = this.nodeControllerMap.get(componentId);
                nodeController?.setDestroy(true)
                this.nodeControllerMap.clear();
                this.componentIdArr.length = 0;
              }
            })// 获取同层渲染组件触摸事件信息。
            .onNativeEmbedGestureEvent((touch) => {
              console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
              this.componentIdArr.forEach((componentId: string) => {
                let nodeController = this.nodeControllerMap.get(componentId)
                if (nodeController?.getEmbedId() === touch.embedId) {
                  let ret = nodeController?.postEvent(touch.touchEvent)
                  if (ret) {
                    console.log("onNativeEmbedGestureEvent success " + componentId)
                  } else {
                    console.log("onNativeEmbedGestureEvent fail " + componentId)
                  }
                }
              })
            })
        }
      }
    }           
<html>
<head>
    <title>同层渲染测试html</title>
    <meta name="viewport">
</head>
<body>
<div>
    <div id="bodyId">
        <embed id="nativeTextInput" type="native/TextInput" width="100%" height="100%" src="test?params1=xxx?"
               style="background-color:pink"/>
    </div>
</div>
</body>
</html>

HarmonyOS码上奇行
5k 声望2.5k 粉丝