Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题

  网页嵌入插件最好的应该就是ZFBrowser了, 可是使用起来也是问题多多, 现在最要命的是网页输入不能打中文, 作者也没打算接入IME, 只能自己想办法了...

  搞了半天只想到一个办法, 就是通过Unity的IME去触发中文输入, 然后传入网页, 也就是说做一个透明的 InputField 盖住网页的输入文本框, 然后在 Update 或是 onValueChanged 中把内容传给网页, 这样基本就能实现中文输入了.

  因为对前端不熟悉, 我就做了一个简单网页做测试:

<html>

<head>
    <title>My first pagetitle>
    <style>
        body {
            margin: 0;
        }
    style>
head>

<body>
    <h1>Test Inputh1>
    Field1:    <input type="text" id="field1"> 
    Field2:    <input type="text" id="field2"> 
    <br>
    <br>
    <script>
        function SetInputValue(id, str) {
            document.getElementById(id).value = str;
        }
        function SubmitInput(str)
        {
            document.getElementById("field2").value = "Submited : " + str;
        }
    script>

body>

html>

  这里网页有两个Text Area, 左边作为输入, 右边作为回车后的调用测试:

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题_第1张图片

 

  然后Unity中直接用一个InputField放到 Field1 的位置上, 设置为透明, 通过Browser类提供的CallFunction方式调用就可以了:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace UIModules.UITools
{
    public class BrowserInputField : MonoBehaviour
    {
        [SerializeField]
        public ZenFulcrum.EmbeddedBrowser.Browser browser;
        [SerializeField]
        public InputField input;

        [Space(10.0f)]
        [Header("设置网页 input 函数名称")]
        [SerializeField]
        public string SetInputFuncName = "SetInputFuncName";
        [Header("设置网页 submit 函数名称")]
        [SerializeField]
        public string SubmitFuncName = "SubmitFuncName";
        [Header("网页 input id")]
        [SerializeField]
        public string InputElementID = "InputElementID";

        public bool inited { get; private set; }

        private void Awake()
        {
            this.RequireComponent().alpha = 0.01f;
            Init();
        }

        public void Init()
        {
            if(input && (false == inited))
            {
                inited = true;
                input.RequireComponent();       // IME 跟随

                StartCoroutine(CaretAccess((_caret) =>
                {
                    if(_caret)
                    {
                        var group = _caret.RequireComponent();
                        group.alpha = 1f;
                        group.ignoreParentGroups = true;
                    }
                }));
            }
        }

        IEnumerator CaretAccess(System.Action access)
        {
            if(input)
            {
                var caret = input.transform.Find("InputField Input Caret");
                while(caret == false && input)
                {
                    caret = input.transform.Find("InputField Input Caret");
                    yield return null;
                }
                access.Invoke(caret);
            }
        }

        void Update()
        {
            if(browser && input)
            {
                browser.CallFunction(SetInputFuncName, new ZenFulcrum.EmbeddedBrowser.JSONNode[2]
                {
                    new ZenFulcrum.EmbeddedBrowser.JSONNode(InputElementID),
                    new ZenFulcrum.EmbeddedBrowser.JSONNode(input.isFocused ? input.text : (string.IsNullOrEmpty(input.text)?input.placeholder.GetComponent().text: input.text))
                });
            }
        }
    }
}

  这里InputField它会自动生成 Caret 就是输入标记, 为了让他能显示出来, 需要等待到它创建出来之后设置透明度即可. 这里省掉了IME输入法跟随的代码, 那是其它功能了.

  恩, 因为字体大小不一样, 所以Caret位置不准确, 反正是能输入了.

 

  这是静态的写法, 可以手动去摆 InputField, 可是在很多情况下是不适用的, 比如 Scroll View 里面的元素, 就需要动态去获取了, 可是由于我们无法计算出网页 input 的位置, 所以没法动态地去设置一个InputField来对上网页, 如果对输入标记没有要求的话 (就是那个打字时候会闪的 "|" 竖杠) , 就可以通过注册网页 input 的 onFocus 方法, 来 focus 一个 InputField, 从而触发输入法, 然后再像上面一样监测输入就行了, 而且不需要在网页端写输入函数来调用了, 这个函数我们应该也是可以自己注册进去的...

  这个想法很好, 来测试看看能不能获取网页中的所有 input 节点吧, 在网页那边写测试(因为我确实没写过网页...) : 

// ... 省略代码了
    Field1: <input type="text" id="field1">
    Field2: <input type="text" id="field2">
    <button type="button" onclick="Click()">Get IDbutton>
    <br>
    <p id="show">Showp>
    <script>
        function Click() {
            var inputs, index;
            var showInfo = "";
            inputs = document.getElementsByTagName('input');
            for (index = 0; index < inputs.length; ++index) {
                showInfo = showInfo + inputs[index].id + " ";
            }
            document.getElementById("show").innerHTML = showInfo;
        }
    script>
// ...

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题_第2张图片

  没错是能获取ID, 那我从Unity那边来添加这个函数试试:

    private void OnGUI()
    {
        if(GUI.Button(new Rect(500, 100, 100, 50), "TestClick"))
        {
            var script = @"
function TestClick() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";
            var inputs = browser.EvalJS(script);
            if(inputs != null)
            {
                inputs.Done((_value) =>
                {
                    if(_value != null)
                    {
                        Debug.Log(_value.ToString());
                    }

                    var retVal = browser.CallFunction("TestClick");
                    if(retVal != null)
                    {
                        retVal.Done((_ret) =>
                        {
                            if(_ret != null)
                            {
                                Debug.Log(_ret.ToString());
                            }
                        });
                    }
                });
            }
        }
    }

  我创建了一个 TestClick 方法, 通过 EvalJS 解释到网页中, 还好这些解释语言的套路都差不多, 只是不知道它给我返回的是啥, 第一个解释 js function的返回有点意外, 居然是个空 :

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题_第3张图片

  不过没关系, 后面的函数调用返回是我要的 : 

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题_第4张图片

  不错, 返回了我要的节点名称, 这样函数就注册进去然后调用成功了, 说明确实可以通过注入式的代码完成调用, 然后我只需要把另一个设置 input 内容的代码注入进去, 就可以随时修改所有 input 对象了.

// 本作核心代码
    function SetInputValue(id, str) {
        document.getElementById(id).value = str;
    }

  马上加进去看看, 先整合一下代码把请求提取出来 : 

    public static void WebBrowserFunctionRegister(ZenFulcrum.EmbeddedBrowser.Browser browser, string function, System.Action succ = null)
    {
        if(browser)
        {
            var register = browser.EvalJS(function);
            if(register != null)
            {
                register.Done((_value) =>
                {
                    if(succ != null)
                    {
                        succ.Invoke(_value);
                    }
                });
            }
        }
    }
    public static void WebBrowserFunctionCall(ZenFulcrum.EmbeddedBrowser.Browser browser, string functionname, ZenFulcrum.EmbeddedBrowser.JSONNode[] param,
        System.Action result = null)
    {
        if(browser)
        {
            var retVal = param != null && param.Length > 0 ? browser.CallFunction(functionname, param) : browser.CallFunction(functionname);
            if(retVal != null)
            {
                retVal.Done((_ret) =>
                {
                    if(result != null)
                    {
                        result.Invoke(_ret);
                    }
                });
            }
        }
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(500, 100, 100, 50), "TestClick"))
        {
            var testClick = @"
function TestClick() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";
            var coreScript = @"
    function SetInputValue(id, str) {
        document.getElementById(id).value = str;
    }
";
            WebBrowserFunctionRegister(browser, testClick, (_) =>
            {
                WebBrowserFunctionCall(browser, "TestClick", null, (_ret) =>
                {
                    WebBrowserFunctionRegister(browser, coreScript, (__) =>
                    {
                        var list = LitJson.JsonMapper.ToObjectstring>>(_ret.AsJSON);
                        if(list != null)
                        {
                            foreach(var id in list)
                            {
                                WebBrowserFunctionCall(browser, "SetInputValue", new ZenFulcrum.EmbeddedBrowser.JSONNode[2] {
                                    new ZenFulcrum.EmbeddedBrowser.JSONNode(id),
                                    new ZenFulcrum.EmbeddedBrowser.JSONNode("测试:" + id),
                                });
                            }
                        }
                    });
                });
            });
        }
    }

  因为我不确定它是不是都是异步的, 所以都用回调的形式来做了, 结果喜人, 确实能够正确运行了:

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题_第5张图片

  几乎成了, 下一步就是注册一下 input 的 focus 事件, 在网页触发 focus 之后就创建一个 InputField 按照套路走就行了, 在 InputField 的focus取消的时候销毁它, 就能完美解决输入法问题了...

 

你可能感兴趣的:(Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入网页无法输入中文问题)