Qt 虚拟键盘概述
功能
Qt 虚拟键盘的主要功能包括
- 可自定义的键盘布局和样式,以及动态切换。
- 预测性文本输入,支持单词选择。
- 字符预览和可选字符视图。
- 自动大写化和空格插入。
- 可缩放至不同分辨率。
- 支持不同的字符集(拉丁语、简体中文/繁体中文、印地语、日语、阿拉伯语、希伯来语、韩语等)。
- 支持大多数常见输入语言,并可选择扩展语言支持。
- 从左至右和从右至左输入。
- 支持双向和五向导航的物理键盘。
- 支持手写输入,带有全屏输入的手势。
- 音频反馈。
- 跨平台功能。
- 支持Qt Quick 和 Qt Widgets 应用。
支持的语言
虚拟键盘支持以下语言
要添加对另一种语言的支持,请参阅添加新的键盘布局。
第三方插件
Qt 虚拟键盘支持以下供应商的第三方插件
- Cerence XT9 高级输入。
- Cerence Handwriting 文本输入。
- MyScript 文本手写识别
构建 Qt 虚拟键盘 说明了如何将这些插件集成到 Qt 虚拟键盘中。
基本概念
Qt虚拟键盘项目是一个Qt输入上下文插件,实现了QPlatformInputContextPlugin和QPlatformInputContext接口。这些接口允许插件在Qt应用程序中用作平台输入上下文插件。
插件本身提供了一个支持多种输入方法的输入框架,以及虚拟键盘的QML用户界面。输入框架可以通过插件接口进行扩展,允许在运行时加载第三方输入方法和键盘布局。
输入框架提供了以下主要接口:
- QVirtualKeyboardInputContext:为虚拟键盘和其他输入组件提供上下文信息。作为底层文本输入组件的接口。
- QVirtualKeyboardInputEngine:公开一个API以集成用户输入事件(按键等),并作为输入方法的宿主。
- QVirtualKeyboardAbstractInputMethod:基于C++的输入方法的基础类型。输入方法通常处理键盘事件,但也可以处理鼠标和触摸输入事件。
- InputMethod:基于QML的输入方法的基础类型。输入方法通常处理键盘事件,但也可以处理鼠标和触摸输入事件。
输入上下文
输入上下文被键盘以及具体的输入方法使用。《InputContext》是一个由QML承载的单例实例。应用程序不应直接与输入上下文交互。
上下文信息
输入上下文提供了访问从应用程序起源的上下文信息。这些信息包括但不限于
- InputContext::cursorPosition
- InputContext::cursorRectangle
- InputContext::inputMethodHints
- InputContext::preeditText
- InputContext::selectedText
- InputContext::surroundingText
地区
虚拟键盘引擎从layouts/
中的地区特定的布局目录生成支持地区列表。每个布局目录必须包含对以下布局类型的定义或回退:拨号盘、数字、手写、主、数字、和符号。定义通过.qml
-文件实现,回退通过扩展名为.fallback
的占位符文件定义。必须包含每个布局类型的定义的fallback/
子目录。layouts/
目录必须包含一个包含每个布局类型定义的fallback/
子目录。
每个布局目录可以包含一个或多个布局类型的定义。如果地区特定的布局与回退地区的布局相同,您可以添加一个名为<layout type>.fallback
的占位符文件。这将指示虚拟键盘使用回退布局。
例如:您可以为芬兰添加一个特定地区布局,在main.qml
中定义主要布局类型。对于其他布局类型,您可以选择回退机制。您的layouts/
树将如下所示
. ├── fallback │ ├── dialpad.qml │ ├── digits.qml │ ├── handwriting.qml │ ├── main.qml │ ├── numbers.qml │ └── symbols.qml └── fi_FI ├── dialpad.fallback ├── digits.fallback ├── handwriting.fallback ├── main.qml ├── numbers.fallback └── symbols.fallback
重要的是,必须始终在layouts/fallback
目录中包含一组完整实现文件。
应用程序可以通过更改默认地区来指定初始布局。但是,这必须在应用程序初始化和加载输入方法插件之前完成。如果没有更改默认地区,则使用当前系统地区。
匹配键盘地区的顺序如下
layouts/<language>_<country>
layouts/<language>_*
layouts/fallback
– 此处的默认布局是en_GB。
首先,区域会与完整区域名称进行匹配。如果没有完全匹配,则仅匹配区域语言。最后,当也没有部分匹配时,将使用layouts/fallback
的内容作为回退。
区域选择完成后,键盘将更新输入区域和输入方向以匹配当前布局。应用程序可以通过QInputMethod接口接收此信息。
内部,当前输入区域也将更新到QVirtualKeyboardInputEngine和当前输入方法实例。
输入引擎
输入引擎对象由InputContext拥有。像InputContext一样,只有一个QVirtualKeyboardInputEngine实例。输入引擎包含键盘用于将用户交互(如键按和释放事件)映射到输入方法的API函数。
例如,虚拟键盘键事件通过以下方法进行映射
上述方法旨在用于虚拟键盘的集成,因此方法名称中的“虚拟”一词。这也意味着这些方法不适用于映射物理按键。这仅是因为实际操作仅在按键释放时才执行。
如果按键在释放事件之前被中断,键盘将调用QVirtualKeyboardInputEngine::virtualKeyCancel方法。
输入方法
输入方法是对按键处理器的具体实现。其主要功能是处理按键事件并维护用户输入的状态信息。它通过预编辑文本或键事件与文本编辑器交互QVirtualKeyboardInputContext。
根据用例,可以以各种方式创建输入方法实例
KeyboardLayout::inputMethod
:键盘布局可以仅为此键盘布局创建一个输入方法实例。请注意,此实例将在键盘布局更改时被销毁。因此,此方法通常仅限于非常狭窄的用例。KeyboardLayout::createInputMethod()
:键盘布局可以动态创建可以与此布局以及共享布局(例如符号布局)一起使用的输入方法。这是创建专用输入方法的首选方式,例如涉及复杂语言或手写的输入方法。DefaultInputMethod
:虚拟键盘在启动时尝试创建此类输入方法。此实例将用于所有键盘布局的默认输入方法,除非键盘布局使用自定义输入方法。此实例将跨越语言变化持续存在,是创建和覆盖默认输入方法的首选方法。
虚拟键盘插件
虚拟键盘的src/plugins目录包含现有的虚拟键盘插件。这些插件由QtQuick. plugins QML模块隐式加载。
插件可以提供键盘布局和输入方法(通常是两者都提供)。虚拟键盘使用的输入方法取决于正在使用的键盘布局。键盘布局可以通过KeyboardLayout.createInputMethod()函数提供一个自定义输入方法的实例。否则,使用虚拟键盘创建的默认输入方法(DefaultInputMethod)。
添加键盘布局
插件可以通过将布局文件包含在插件二进制文件的Qt资源中,为虚拟键盘添加键盘布局。
虚拟键盘从特定路径/qt-project.org/imports/QtQuick/VirtualKeyboard/Layouts/<语言_国家>搜索键盘布局(每个语言),因此在插件中也要使用这个确切路径。Qt资源路径可以重叠,意味着插件可以覆盖虚拟键盘上的现有布局。
还可以通过使用环境变量QT_VIRTUALKEYBOARD_LAYOUT_PATH,直接从文件系统加载它们来覆盖内置的键盘布局。
添加输入方法
插件可以通过注册一个默认使用的输入方法(例如DefaultInputMethod
)或一个在插件中私用(通过也提供自定义键盘布局,它创建输入方法)的输入方法,来注册可以由其他键盘布局默认使用的输入方法。
输入方法必须实现一个QVirtualKeyboardAbstractInputMethod(C++)或InputMethod(QML)接口,并且它必须由插件注册为一个QML类型(QML_NAMED_ELEMENT)。
实现自定义输入方法
输入方法的实现首先是通过决定使用哪种接口开始的,QML 或 C++。在这个例子中使用了QML接口。相同的逻辑和接口在QVirtualKeyboardAbstractInputMethod的C++接口中同样适用。在这种情况下,插件必须链接到VirtualKeyboard模块。
以下示例演示了所需的最小功能
// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.VirtualKeyboard // file: CustomInputMethod.qml InputMethod { function inputModes(locale) { return [InputEngine.InputMode.Latin]; } function setInputMode(locale, inputMode) { return true } function setTextCase(textCase) { return true } function reset() { // TODO: reset the input method without modifying input context } function update() { // TODO: commit current state and update the input method } function keyEvent(key, text, modifiers) { var accept = false // TODO: Handle key and set accept or fallback to default processing return accept; } }
在设置输入模式之前,输入引擎会调用InputMethod::inputModes()方法。该方法返回给定区域设置中可用的输入模式列表。
使用区域设置和输入模式初始化输入方法,是InputMethod::setInputMode()方法。在设置区域设置和输入模式后,输入方法应该准备就绪可供使用。
当需要重置输入方法时,会调用InputMethod::reset。重置只能重置输入方法的内部状态,而不能重置用户的文本。
当输入上下文更新并且输入状态可能不同步时,会调用InputMethod::update。输入方法应提交当前文本。
在InputMethod::keyEvent中处理按键事件。此方法处理单个按键事件并返回true
如果已处理事件。否则,按键由默认输入方法处理。
选择列表
选择列表是可选功能,可以集成到输入法中。输入框架支持各种类型的列表,如单词候选列表。在实现列表时,输入法负责内容和活动,例如点击行为。输入框架负责维护列表模型并向用户界面交付。
分配选择列表
当输入法激活时分配选择列表。方法InputMethod::selectionLists()返回所需选择列表类型的列表
function selectionLists() { return [SelectionListModel.Type.WordCandidateList]; }
在上面的示例中,输入法为其自己的使用分配了单词候选列表。
更新选择列表
当输入法需要UI更新选择列表的内容时,它将发出信号InputMethod::selectionListChanged。同样,如果输入法需要UI突出显示列表中的某个项目,它将发出信号InputMethod::selectionListActiveItemChanged。
selectionListChanged(SelectionListModel.Type.WordCandidateList) selectionListActiveItemChanged(SelectionListModel.Type.WordCandidateList, wordIndex)
填充选择列表中的项
项目通过提供方法回调进行填充,这些回调将提供列表中的项目数量以及单个项目的数据。
回调函数InputMethod::selectionListItemCount请求给定类型的列表中的项目数量。
function selectionListItemCount(type) { if (type == SelectionListModel.Type.WordCandidateList) { return wordList.length } return 0 }
回调函数InputMethod::selectionListData请求项目的数据。
function selectionListData(type, index, role) { var result = null if (type == SelectionListModel.Type.WordCandidateList) { switch (role) { case SelectionListModel.Role.Display: result = wordList[index] break default: break } } return result }
role参数标识请求哪个项目数据。例如,SelectionListModel.Role.Display请求显示文本数据。
响应用户操作
当用户在选择列表中选择一个项时,输入法会通过InputMethod::selectionListItemSelected方法回调响应此事件。
function selectionListItemSelected(type, index) { if (type == SelectionListModel.Type.WordCandidateList) { inputContext.commit(wordlist[index]) update() } }
集成手写识别
输入法还可以使用触摸屏或其他输入设备的数据。
当输入开始时,虚拟键盘调用输入法函数 traceBegin ,该函数返回一个新的Trace对象,输入法将通过该对象代表输入法收集输入。同样,当手指或笔被抬起时,使用 traceEnd 调用终止事件。输入法通过InputContext接口处理收集到的数据并产生文本。
有预定义的手写键盘布局,但是默认不包含,手写插件应在自己的资源中包含它们。有关如何操作的示例,请参阅来自MyScript或Cerence的手写现有插件。
手写输入的数据模型
虚拟键盘在特殊的数据模型QVirtualKeyboardTrace中收集手写数据。每个跟踪表示从一个触摸(例如屏幕上的滑动)中采样的一组数据。每个手写输入区域上的触摸都会有QVirtualKeyboardTrace的实例。
定义上,“跟踪”是从一个触摸中采样的一组数据。除了基本点数据外,它还可以包含其他类型的数据,例如每个点的日期。输入法可以在跟踪事件开始时定义所需的输入通道。
输入法不参与实际的轨迹数据收集。然而,输入法对输入有完全控制权,因为它可以接受或拒绝一个QVirtualKeyboardTrace(例如,如果要处理的实例太多)。这也允许精确控制可以同时使用的手指数量。
输入法可以收集其认为合适的任何数量的轨迹,并在必要时开始处理它们。处理甚至可以在采样数据的同时并行执行,尽管不推荐这样做,因为它可能存在性能问题。推荐的方法是在最后输入后的一段时间内开始在一个后台线程中处理,这样处理就不会对用户界面产生负面影响。
输入法轨迹API
轨迹API包括以下虚拟方法,输入法必须实现这些方法以接收和处理轨迹输入数据。
通过实现这些方法,输入法可以接收和处理来自各种输入源(例如键盘布局或全屏)的数据。
patternRecognitionModes方法返回一个模式识别模式列表,这些模式是输入法支持的。例如,手写 模式定义了输入法处理数据的方法。
当输入源检测到新的接触点时,会开始轨迹交互,并为新的轨迹对象调用traceBegin方法。如果输入法接受此交互,它会创建一个新的轨迹对象并将其返回给调用者。从这一点开始,将收集轨迹数据,直到调用traceEnd方法。
当调用traceEnd方法时,输入法可以开始处理轨迹对象中的数据。数据处理完毕后,输入法应该销毁对象。这也移除了显示在屏幕上的轨迹。
键盘布局
键盘布局位于src/layouts/builtin目录中。布局目录的每个子目录代表一个区域设置。区域设置目录是一个字符串,形式为“language_country”,其中language是小于等于两个字母的小写ISO 639语言代码,country是大写、两个或三个字母的ISO 3166国家代码。
布局类型
不同的输入模式使用了不同的键盘布局类型。用于常规文本输入的默认布局称为“主要”布局。布局类型由布局文件名称确定。因此,“主要”布局文件称为“main.qml”。
支持的布局类型列表
main
正常文本输入的主要布局symbols
特殊字符等的符号布局(从主要布局激活)numbers
格式化数字的数字布局(通过Qt::ImhFormattedNumbersOnly激活)digits
仅数字布局(通过Qt::ImhDigitsOnly激活)dialpad
电话号码输入的数字盘布局(通过Qt::ImhDialableCharactersOnly激活)handwriting
手写识别的滚写布局(从主要布局激活)
添加新的键盘布局
键盘布局元素必须基于KeyboardLayout QML类型。此类型定义布局的根项。根项有以下可选属性,如果需要可以设置
属性 var inputMethod | 指定此布局的输入法。如果没有定义输入法,则使用当前输入法。 |
属性 int inputMode | 指定此布局的输入模式。 |
属性 real keyWeight | 指定所有按键默认键重。键重是一个比例值,它影响相对于彼此的各个按键的大小。 |
通过使用 KeyboardRow 类型添加新行到键盘布局。也可以为子元素指定默认的键重。否则,键重将从其父元素继承。
通过使用 Key 类型或其中一个专业键类型添加新键到键盘行。以下是所有键类型的列表
键盘布局的 Backspace 键 | |
键盘布局的换语言键 | |
键盘布局的 Enter 键 | |
键盘布局的填充键 | |
键盘布局的滑动键 | |
键盘布局的手写模式键 | |
键盘布局的隐藏键盘键 | |
键盘布局的输入模式键 | |
键盘布局的正则字符键 | |
键盘布局的通用模式键 | |
键盘布局的专业数字键 | |
键盘布局的 Shift 键 | |
键盘布局的空格键 | |
键盘布局的符号模式键 | |
用于收集触摸输入数据的专用键 |
例如,要添加一个常规键,该键将向输入法发送一个按键事件
import QtQuick import QtQuick.VirtualKeyboard import QtQuick.VirtualKeyboard.Components // file: en_GB/main.qml KeyboardLayout { keyWeight: 160 KeyboardRow { Key { key: Qt.Key_Q text: "q" } } }
按键大小计算
键盘布局是可缩放的,这意味着布局中不能设置任何固定的大小。相反,按键宽度是根据相互关系中的键重来计算的,键盘行通过平均分配空间来计算高度。
在上面的示例中,键大小按以下顺序从父元素继承
键 > KeyboardRow > KeyboardLayout
键重的有效值将是 160。为了说明示例,我们添加了另一个指定自定义键重的键
import QtQuick import QtQuick.VirtualKeyboard import QtQuick.VirtualKeyboard.Components // file: en_GB/main.qml KeyboardLayout { keyWeight: 160 KeyboardRow { Key { key: Qt.Key_Q text: "q" } Key { key: Qt.Key_W text: "w" keyWeight: 200 } } }
现在行的总键重是 160 + 200 = 360。当键盘布局激活时,单个键的宽度按以下方式计算
像素中的键宽 = 键重 / 行中键重的总和 * 像素中的行宽
这意味着键盘可以缩放到任何大小,同时按键的相对大小保持不变。
备用键
键可以指定备用键属性,当用户按下并保持键时,将出现包含备用键的弹出窗口。备用键可以指定一个字符串,也可以指定字符串的列表。如果备用键是一个字符串,用户可以在字符串中的字符之间选择。
样式和布局
键盘布局无法指定任何视觉元素。相反,布局是通过键盘样式可视化的。另一方面,键盘样式不能影响键盘布局的大小。
具有多个键页面的键盘布局
某些键盘布局,如符号布局,可能包含比在单个键盘布局上呈现更为合理的更多按键。一种解决方案是通过使用KeyboardLayoutLoader将多个键盘布局嵌入到同一个上下文中。
当使用KeyboardLayoutLoader作为键盘布局的根项时,实际的键盘布局会被封装在Component元素中。通过将活动组件的id分配给sourceComponent属性来激活键盘布局。
例如
import QtQuick import QtQuick.VirtualKeyboard import QtQuick.VirtualKeyboard.Components // file: en_GB/symbols.qml KeyboardLayoutLoader { property bool secondPage onVisibleChanged: if (!visible) secondPage = false sourceComponent: secondPage ? page2 : page1 Component { id: page1 KeyboardLayout { KeyboardRow { Key { displayText: "1/2" functionKey: true onClicked: secondPage = !secondPage } } } } Component { id: page2 KeyboardLayout { KeyboardRow { Key { displayText: "2/2" functionKey: true onClicked: secondPage = !secondPage } } } } }
手写键盘布局
支持手写识别的每种语言都必须提供名为 handwriting.qml 的特殊键盘布局。
此类型的键盘布局必须满足以下要求
- 键盘布局中包含一个TraceInputKey
- 提供HandwritingInputMethod实例作为输入法。
手写布局还可以包含ChangeLanguageKey。为此,重要的一点是使用customLayoutsOnly属性,这将会过滤掉不使用手写的语言。
主键盘布局和手写布局都应该包含一个键用于激活和关闭手写输入模式。这可以通过向布局中添加HandwritingModeKey来实现。
添加自定义布局
虚拟键盘布局系统支持内置布局以及自定义布局。内置布局作为Qt资源嵌入到插件二进制文件中。自定义布局位于文件系统中,这样它们可以在不重新编译虚拟键盘的情况下进行安装,或者它们可以位于资源文件中。
运行时布局的选择受环境变量 QT_VIRTUALKEYBOARD_LAYOUT_PATH
的影响。
如果没有设置环境变量,或包含无效的目录,虚拟键盘将回退到默认的内置布局。
为了防止在自定义布局时,内置布局被嵌入到虚拟键盘插件中,请将 -no-vkb-layouts
选项添加到 configure
脚本中。更多信息,请参阅配置选项。
键盘样式
虚拟键盘风格系统支持内置样式以及自定义样式。内置样式作为Qt资源嵌入到插件二进制文件中,而自定义样式位于文件系统中,并且可以在不重新编译虚拟键盘本身的情况下进行安装。
运行时样式的选择受环境变量 QT_VIRTUALKEYBOARD_STYLE 的影响,可以设置为内置样式的名称,例如 "retro",或任何安装在样式目录下的自定义样式。
$$[QT_INSTALL_QML]/QtQuick/VirtualKeyboard/Styles
如果没有设置环境变量,或包含无效的样式名称,虚拟键盘将回退到默认的内置样式。
添加自定义样式
创建新样式的过程是从在基于URL的目录结构 QtQuick/VirtualKeyboard/Styles/ 下的QML导入路径中创建新的样式子目录开始的。有关QML导入路径的信息,请参阅QML导入路径。目录名称不能包含空格或除下划线之外的特殊字符。此外,目录名称不能与内置样式相同,目前包括 "default" 和 "retro"。
创建新样式的好方法是使用现有内置样式作为模板进行编辑。您可以从虚拟键盘源目录src/styles/builtin中找到内置样式。将包含内置样式的任何一个目录复制到Styles目录中,并将其重命名为"test"。目录结构现在应如下所示
test/default_style.qrc test/style.qml test/images test/images/backspace.png test/images/check.png test/images/enter.png test/images/globe.png test/images/hidekeyboard.png test/images/search.png test/images/shift.png
此情况中不必要的QRC配置文件可以安全删除。
注意: 应该保持style.qml文件名称不变,否则虚拟键盘无法加载此样式。
接下来,在您喜欢的编辑器中打开style.qml,并将resourcePrefix属性设置为空字符串。由于资源包含在style.qml文件所在的同一目录中,因此不需要资源前缀。
此外,为了更明显地看到自定义样式正在加载并使用,将键盘背景设置为不同的颜色
keyboardBackground: Rectangle {
color: "gray"
}
最后一步是运行带有自定义样式的示例应用程序
QT_VIRTUALKEYBOARD_STYLE=test virtualkeyboard
使用QQuickWidget与Qt虚拟键盘
在使用触摸设备上的QQuickWidget中的Qt虚拟键盘时,需要通过QWidget::setAttribute设置Qt::WA_AcceptTouchEvents属性。如果没有设置此属性,触摸设备的输入事件将被转换为合成的鼠标事件。
© 2024 Qt有限公司。此处包含的文档贡献是各自所有者的版权。提供的文档是根据自由软件基金会发布的GNU自由文档许可证版本1.3许可的。Qt及其相关标志是芬兰和/或其他国家/地区的Qt有限公司的商标。所有其他商标均是各自所有者的财产。