在UI上的InputField中, 中文输入法的备选框不会跟随在光标旁边, 造成输入不方便.

看到有一个相似的, 可是是WebGL的 : https://blog.csdn.net/Rowley123456/article/details/103726927/

它通过添加Html的Input控件的方式来修改备选框位置, 直接跟平台相关了, 不具有泛用性.

按照这个思路, 直接找Windows的输入控制模块:

[DllImport("imm32.dll")]public static externIntPtr ImmGetContext(IntPtr hWnd);

[DllImport("imm32.dll")]public static extern intImmReleaseContext(IntPtr hWnd, IntPtr hIMC);

[DllImport("imm32.dll")]public static extern bool ImmSetCompositionWindow(IntPtr hIMC, refCOMPOSITIONFORM lpCompForm);

[System.Runtime.InteropServices.DllImport("user32.dll")]private static extern System.IntPtr GetActiveWindow();

然后获取窗口句柄, 设置位置的返回都是正确的, 可是结果并没有改变备选框位置:

voidSetInputPos()

{

IntPtr hImc=ImmGetContext(GetWindowHandle());

COMPOSITIONFORM cf= newCOMPOSITIONFORM();

cf.dwStyle= 2;

cf.ptCurrentPos.X= 500;

cf.ptCurrentPos.Y= 500;bool setcom = ImmSetCompositionWindow(hImc, ref cf); //setcom == true

ImmReleaseContext(GetWindowHandle(), hImc);

}//结构体略

这就比较尴尬了, 设置没有反应没有报错......

考虑到Unity应该有各个平台的底层接口的, 以实现标准化的输入(IME接口), 所以在BaseInputModule里面去找一找, 发现它下面有个BaseInput组件:

//StandaloneInputModule : PointerInputModule//PointerInputModule : BaseInputModule

public abstract classBaseInputModule : UIBehaviour

{protectedBaseInput m_InputOverride;//

//摘要://The current BaseInput being used by the input module.

public BaseInput input { get; }

......

}

这个跟输入貌似有关系, 看到里面的变量跟Windows的API有点像:

public classBaseInput : UIBehaviour

{publicBaseInput();//

//摘要://Interface to Input.imeCompositionMode. Can be overridden to provide custom input//instead of using the Input class.

public virtual IMECompositionMode imeCompositionMode { get; set; }//

//摘要://Interface to Input.compositionCursorPos. Can be overridden to provide custom//input instead of using the Input class.

public virtual Vector2 compositionCursorPos { get; set; }

......

}

估计只要继承它自己设置compositionCursorPos就能达到效果了, 直接创建一个继承类型, 然后通过反射的方式给StandaloneInputModule设定BaseInput:

[RequireComponent(typeof(InputField))]public classIME_InputFollower : BaseInput

{publicInputField inputField;public overrideVector2 compositionCursorPos

{get{return base.compositionCursorPos;

}set{base.compositionCursorPos = new Vector2(200,200);  //test

}

}private static voidSetCurrentInputFollower(IME_InputFollower target)

{var inputModule =EventSystem.current.currentInputModule;if(inputModule)

{var field = inputModule.GetType().GetField("m_InputOverride", BindingFlags.Instance |BindingFlags.NonPublic);if(field != null)

{

field.SetValue(inputModule, target);if(target)

{

target.inputField.OnPointerDown(newPointerEventData(EventSystem.current));int caretPosition = string.IsNullOrEmpty(target.inputField.text) == false ? target.inputField.text.Length : 0;

target.inputField.caretPosition=caretPosition;

}

}

}

}

}

当InputField被focus的时候, SetCurrentInputFollower使用反射的方式设定BaseInput到当前的InputModule中, 然后手动触发一下OnPointerDown和设定光标位置, 这样就能刷新输入法备选框了, 不会因为切换InputField而窗口不跟随. 还有就是在编辑器下窗口的大小为Game窗口的大小, 而不是渲染部分的大小, 所以在编辑器下窗口大小与渲染不同的时候计算位置是不对的.

PS : 在测试时发现在Windows下compositionCursorPos的计算方法是窗口坐标, 并且起始坐标为窗口坐上角(0, 0), 不知道是不是DX平台的特点.

填满窗口看看原始的输入法备选框在哪:

已经超出界面范围了, 现在添加IME_InputFollower组件, 来计算一下位置让备选框出现在输入框的左下角:

public overrideVector2 compositionCursorPos

{get{return base.compositionCursorPos;

}set{#if UNITY_STANDALONE

var size = newVector2(Screen.width, Screen.height);

Vector3[] coners= new Vector3[4];

(inputField.transformasRectTransform).GetWorldCorners(coners);

Vector2 leftBottom= coners[0];var compositionCursorPos = new Vector2(leftBottom.x, size.y -leftBottom.y);base.compositionCursorPos =compositionCursorPos;#else

base.compositionCursorPos =value;#endif}

}

证明确实可行, 这样这个逻辑应该就是可以在全部平台中跑了, 只要添加compositionCursorPos的set逻辑就行了, 而平台的差异只要在计算坐标中注意即可(不过除了Windows也没其他需要的平台了).

全部代码贴一下:

usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.UI;usingUnityEngine.EventSystems;usingSystem.Reflection;namespaceUIModules.UITools

{

[RequireComponent(typeof(InputField))]public classIME_InputFollower : BaseInput

{private static IME_InputFollower _activeFollower = null;private staticIME_InputFollower activeFollower

{get{return_activeFollower;

}set{if(_activeFollower !=value)

{

_activeFollower=value;

SetCurrentInputFollower(value);

}

}

}publicInputField inputField;public Vector2 imeOffset = new Vector2(-20f, -20f);private Common.Determinator m_determin = new Common.Determinator(Common.Determinator.Logic.All, false);public overrideVector2 compositionCursorPos

{get{return base.compositionCursorPos;

}set{#if UNITY_STANDALONE

var size = newVector2(Screen.width, Screen.height);

Vector3[] coners= new Vector3[4];

(inputField.transformasRectTransform).GetWorldCorners(coners);

Vector2 leftBottom= coners[0];

Vector2 leftBottomOffset= leftBottom +imeOffset;var compositionCursorPos = new Vector2(leftBottomOffset.x, size.y -leftBottomOffset.y);base.compositionCursorPos =compositionCursorPos;#else

base.compositionCursorPos =value;#endif}

}protected override voidAwake()

{base.Awake();if(inputField == false)

{

inputField= GetComponent();

}

m_determin.AddDetermine("Selected", () => { return inputField &&inputField.isFocused; });

m_determin.changed+= (_from, _to) =>{if(_to)

{

activeFollower= this;

}else{

CancelSelection();

}

};

}protected override voidOnDisable()

{base.OnDisable();

CancelSelection();

}voidUpdate()

{

m_determin.Tick();

}private voidCancelSelection()

{if(this ==activeFollower)

{

activeFollower= null;

}

}private static voidSetCurrentInputFollower(IME_InputFollower target)

{var inputModule =EventSystem.current.currentInputModule;if(inputModule)

{var field = inputModule.GetType().GetField("m_InputOverride", BindingFlags.Instance |BindingFlags.NonPublic);if(field != null)

{

field.SetValue(inputModule, target);if(target)

{

target.inputField.OnPointerDown(newPointerEventData(EventSystem.current));int caretPosition = string.IsNullOrEmpty(target.inputField.text) == false ? target.inputField.text.Length : 0;

target.inputField.caretPosition=caretPosition;

}

}

}

}

}

}

Determinator 就是一个简单决策器:

usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;namespaceCommon

{public classDeterminator

{public enumLogic

{

All,

One,

}private bool_defaultValue;private bool_lastResult;public Logic logic { get; private set; }private Dictionary> m_determines = new Dictionary>();public System.Action changed = null;public boolResult

{get{var newResult =GetResult();if(_lastResult !=newResult)

{

ApplyChanged(newResult);

}returnnewResult;

}set{if(value !=_lastResult)

{

ApplyChanged(value);

}

}

}public string FailedReason { get; private set; }public string SuccessedReason { get; private set; }public Determinator(Logic logic, booldefaultVal)

{this.logic =logic;

_defaultValue=defaultVal;

_lastResult=_defaultValue;

}public void AddDetermine(string name, System.Funcfunc)

{

m_determines[name]=func;

}public void DeleteDetermine(stringname) { m_determines.Remove(name); }public boolGetResult()

{if(m_determines.Count > 0)

{switch(logic)

{caseLogic.All:

{foreach(var func inm_determines)

{if(func.Value.Invoke() == false)

{

FailedReason=func.Key;return false;

}

}

FailedReason= null;return true;

}break;caseLogic.One:

{foreach(var func inm_determines)

{if(func.Value.Invoke())

{

SuccessedReason=func.Key;return true;

}

}

SuccessedReason= null;return false;

}break;default:return_defaultValue;

}

}else{return_defaultValue;

}

}private void ApplyChanged(boolnewResult)

{var tempLast =_lastResult;

_lastResult=newResult;if(changed != null)

{

changed.Invoke(tempLast, newResult);

}

}public boolTick()

{returnResult;

}

}

}

unity android输入法,Unity输入法相关(IME)相关推荐

  1. Unity Android 之 Unity Android 交互(aar形式)动态申请权限功能实现(权限可人为怎加删减,并含代码工程)

    Unity Android 之 Unity Android 交互(aar形式)动态申请权限功能实现(权限可人为怎加删减,并含代码工程) 目录

  2. unity android 原生,unity创建Android原生插件

    2.打开Android Studio 创建项目 - 输入项目名称 - 输入Company domain或者用默认的 - 点击next 3.点击next 4.因为是创建Android plugin 所以 ...

  3. unity Android 剪贴板,Unity移动端的复制要这么写示例代码

    前言 Unity官网提供了详尽的文档和丰富的教学视频,昨天跟着视频做了一个简单的3d游戏"roll-a-ball".游戏涉及了许多Unity的基本知识,用来入门很不错. 本文主要给 ...

  4. unity android 分包,Unity以分包(obb)形式集成到安卓原生 我慢慢填坑

    Unity以分包(obb)形式集成到安卓原生 我慢慢填坑 Unity以分包(obb)形式集成到安卓原生 我慢慢填坑 工作中有需要将unity项目集成到安卓原生中,随着工作推进需要分包去发布到googl ...

  5. unity android 震动,unity 调用android的震动

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 在unity中调用震动一般有两种方式: 1.使用unity自带的 Handheld.Vibrate(); 优点:方便.简单 缺点:无法控制震动的频率 2. ...

  6. unity Android 指南针,Unity之一天一个技术点(十二)---指南针的实现

    把脚本赋予给主镜头即可 GUI.skin = compassGUISkin; var compassAngle : float = transform.rotation.eulerAngles.y;/ ...

  7. unity android适配,unity实战 手机屏幕适配

    使用背景 为了在UI中使用特效层,项目Canvas采用了Screen Space-Camera类型 UI的Scale Mode 选择的是Scale With Screen Size 的Expand,画 ...

  8. android 获取当前输入法,关于获取安卓手机系统输入法和设置输入法

    关于获取安卓手机系统输入法和设置输入法 获取输入法列表 adb shell ime list -s 出来下面的 io.appium.android.ime/.UnicodeIME com.sec.an ...

  9. unity android 版本,Unity2019与Android混合开发

    0. 开始前的版本对齐 Unity版本:Unity2019.3.4f1 AndroidStudio版本:3.5.3 1. Unity -- 准备项目 新建项目 打开File -> Build S ...

  10. Unity Android 接入高德定位SDK

    Unity版本 2020 第一步首先去高德开放平台注册账号然后申请Key 高德开放平台 | 高德地图API 注册好账号之后 点击控制台 然后选择管理key 新建一个应用 选择添加  然后创建一个key ...

最新文章

  1. 俄罗斯自研Elbrus CPU参数曝光,CEO年近九旬仍未退休
  2. C语言结构体自动初始化实现,C语言中结构体(struct)的几种初始化方法
  3. adb命令开启手机的一些功能,请问怎么操作?
  4. PXE网络装机之centos7(批量自动装机)
  5. APPium-Xpath,swipe练习
  6. 利用丁香园数据生成疫情分布地图(R语言)| 博文精选
  7. 动态规划——物品无限的背包问题
  8. bzoj 2832 宅男小c
  9. 学习一个Vue模板项目
  10. flask_restplus和swagger
  11. 认知机器人:机器人学
  12. CDC Comms Interface 设备解决方案
  13. OSChina 周二乱弹 —— 基于现代生物化学的长生不老药炼制教程
  14. 【论文解读 WWW 2019 | FBMA】Event Detection using Hierarchical Multi-Aspect Attention
  15. 树莓派+SSH反向代理实现远程开机
  16. 阿里技术团队是如何打造的?
  17. 《灵飞经》3·印神无双 第十三章 剑奕星斗 2
  18. 【图像转换】基于matlab灰度图像转换彩色图像【含Matlab 1233期】
  19. 动物细胞无血清培养基的发展和应用
  20. iPhone分辨率及尺寸

热门文章

  1. 极市整理的超全CV资源:CVPR、ECCV、valse资源汇总
  2. HECKTOR2020第三名论文研读
  3. 考研逻辑整理 - 假言判断和假言推理
  4. GPS 定位管理车辆系统
  5. 应届生,找java后端开发要什么水平才算合格?
  6. 工信部第五届“绽放杯”5G应用征集大赛通用产品专题决赛圆满落幕,广和通荣膺双奖!
  7. 软考 | 2017年上半年 软件设计师 下午试卷
  8. 集合的所有子集的算法
  9. 将命令添加进开机自启
  10. bed文件格式(转自http://blog.sina.com.cn/s/blog_70b2b6020100liou.html)