场景描述

在特殊的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>
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐