作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
前言
我们知道 Qt 中虚拟键盘模块遵循的是 GPL 协议,是不可用于商业发布的。如果项目中使用了 Qt 自带的虚拟键盘,在正式发布项目时必须要开源才可以。因此为了避免使用此模块就需要自己来实现一个虚拟键盘功能。博主在网上也搜到了一些资源,基本上都是 widget 来实现的,用 qml 来做的很少,这里我们以官方的虚拟键盘为参照,用 qml 自己实现一个键盘。
功能展示
代码展示
1. main.qml
界面上的元素包括:两个自定义文本输入框 BQTextInput ,一个自定义虚拟键盘 BQVirtualKeyboard ,还有一个切换中英文的按钮。虚拟键盘显示的条件是当前焦点正在文本输入框中
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 1024; height: 768
visible: true
title: qsTr("Virtual Keyboard Demo")
Rectangle {
anchors.fill: parent
color: "lightblue"
BQTextInput {
id: input1
x: 10; y: 10
width: 300; height: 40
pixelSize: 16
textFontFamily: "微软雅黑"
placeholderText: "测试文本1"
}
BQTextInput {
id: input2
x: 10; y: 60
width: 300; height: 40
pixelSize: 16
textFontFamily: "微软雅黑"
placeholderText: "测试文本2"
}
Button {
x: 10; y: 110
width: 120; height: 40
font.pixelSize: 16
text: "切换键盘语言"
onClicked: virtualKeyboard.languageType = virtualKeyboard.languageType == 1 ? 2 : 1
}
BQVirtualKeyboard {
id: virtualKeyboard
y: 180
anchors.horizontalCenter: parent.horizontalCenter
visible: input1.hasFocus || input2.hasFocus
}
}
}
2. BQTextInput.qml
自定义文本输入框,从其他项目中直接拷贝过来的,主要用于实现账号密码登录时的文本输入,如下图所示
import QtQuick 2.15
// 自定义文本输入框
Rectangle {
width: 200; height: 40
color: "white"
property int rightDis: 0 // 右侧缩进
property int pixelSize: 16 // 字体大小
property string textFontFamily: "" // 字体样式
property string placeholderText: "" // 提示文本
property alias textInput: input // 文本输入
property bool isPassword: false // 密码输入
property string imageSource: "" // 图像资源
property bool hasFocus: input.focus // 输入框焦点
signal imagePressed()
TextInput {
id: input
x: 5
width: parent.width - rightDis - 10
height: parent.height
activeFocusOnPress: true
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: TextInput.AlignVCenter
echoMode: isPassword ? TextInput.Password : TextInput.Normal
clip: true
Text {
x: 5
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
text: placeholderText
visible: input.text == ""
}
}
Image {
id: image
width: rightDis; height: rightDis
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: (parent.height - rightDis) / 2
source: imageSource
visible: rightDis != 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
image.focus = true
imagePressed()
}
}
}
}
3. BQVirtualKeyboard.qml
自定义虚拟键盘,一共4行布局,符号可以按照需求自己改
import QtQuick 2.15
// 自定义虚拟键盘
Rectangle {
id: virtualKeyboard
width: 710; height: 290
radius: 5
color: "black"
property bool isUpper: false // 是否大写
property bool isEnglish: true // 是否英文
property int page: 1 // 字符页面
property int pixelSize: 16 // 字体大小
property string textFontFamily: "" // 字体样式
property int languageType: 1 // 语言 1-中文 2-英文
property var en_line1_lower: ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]
property var en_line1_upper: ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
property var en_line2_lower: ["a", "s", "d", "f", "g", "h", "j", "k", "l"]
property var en_line2_upper: ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
property var en_line3_lower: ["z", "x", "c", "v", "b", "n", "m"]
property var en_line3_upper: ["Z", "X", "C", "V", "B", "N", "M"]
property var char_page1_line1: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
property var char_page1_line2: ["~", "-", "+", ";", ":", "_", "=", "|", "\\"]
property var char_page1_line3: ["`", ",", ".", "<", ">", "/", "?"]
property var char_page2_line1: ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"]
property var char_page2_line2: ["[", "]", "{", "}", "'", "\"", "I", "II", "III"]
property var char_page2_line3: ["IV", "V", "VI", "VII", "VIII", "IX", "X"]
// 第一行
Row {
y: 10
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Repeater {
model: {
if (isEnglish) {
isUpper ? en_line1_upper : en_line1_lower
} else {
page == 1 ? char_page1_line1 : char_page2_line1
}
}
Rectangle {
width: 60; height: 60
radius: 5
color: area1.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area1.pressed ? "#5D4B37" : "#FFFFFF"
text: modelData
}
MouseArea {
id: area1
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text = focusedItem.text + modelData
}
}
}
}
}
// 第二行
Row {
y: 80
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Repeater {
model: {
if (isEnglish) {
isUpper ? en_line2_upper : en_line2_lower
} else {
page == 1 ? char_page1_line2 : char_page2_line2
}
}
Rectangle {
width: 60; height: 60
radius: 5
color: area2.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area2.pressed ? "#5D4B37" : "#FFFFFF"
text: modelData
}
MouseArea {
id: area2
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text = focusedItem.text + modelData
}
}
}
}
}
// 第三行
Row {
y: 150
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
// shift
Rectangle {
width: 95; height: 60
radius: 5
color: area_shift.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_shift.pressed ? "#5D4B37" : (isEnglish && isUpper ? "#239B56" : "#FFFFFF")
text: isEnglish ? "Shift" : (page == 1 ? "1/2" : "2/2")
}
MouseArea {
id: area_shift
anchors.fill: parent
focus: false
onClicked: {
if (isEnglish) {
isUpper = !isUpper
} else {
page == 1 ? (page = 2) : (page = 1)
}
}
}
}
Repeater {
model: {
if (isEnglish) {
isUpper ? en_line3_upper : en_line3_lower
} else {
page == 1 ? char_page1_line3 : char_page2_line3
}
}
Rectangle {
width: 60; height: 60
radius: 5
color: area3.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area3.pressed ? "#5D4B37" : "#FFFFFF"
text: modelData
}
MouseArea {
id: area3
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text = focusedItem.text + modelData
}
}
}
}
// backspace
Rectangle {
width: 95; height: 60
radius: 5
color: area_backspace.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_backspace.pressed ? "#5D4B37" : "#FFFFFF"
text: languageType == 1 ? "回 退" : "Backspace"
}
MouseArea {
id: area_backspace
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text = focusedItem.text.slice(0, -1)
}
}
}
}
// 第四行
Row {
y: 220
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
// switch
Rectangle {
width: 95; height: 60
radius: 5
color: area_switch.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_switch.pressed ? "#5D4B37" : "#FFFFFF"
text: isEnglish ? "&123" : "ABC"
}
MouseArea {
id: area_switch
anchors.fill: parent
focus: false
onClicked: isEnglish = !isEnglish
}
}
// space
Rectangle {
width: 375; height: 60
radius: 5
color: area_space.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_space.pressed ? "#5D4B37" : "#FFFFFF"
text: languageType == 1 ? "空 格" : "Space"
}
MouseArea {
id: area_space
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text += " "
}
}
}
// clear
Rectangle {
width: 95; height: 60
radius: 5
color: area_clear.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_clear.pressed ? "#5D4B37" : "#FFFFFF"
text: languageType == 1 ? "清 空" : "Clear"
}
MouseArea {
id: area_clear
anchors.fill: parent
focus: false
onClicked: {
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
focusedItem.text = ""
}
}
}
// hide
Rectangle {
id: hide
width: 95; height: 60
radius: 5
color: area_hide.pressed ? "#2A2826" : "#383533"
Text {
anchors.fill: parent
font.pixelSize: pixelSize
font.family: textFontFamily
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: area_hide.pressed ? "#5D4B37" : "#FFFFFF"
text: languageType == 1 ? "隐 藏" : "Hide"
}
MouseArea {
id: area_hide
anchors.fill: parent
onClicked: hide.focus = true
}
}
}
}
4. 获取当前拥有焦点的控件
在 BQVirtualKeyboard.qml 文件中用到了下面这行代码,其目的是获取当前拥有焦点的控件,这个操作可以说是自己实现虚拟键盘的一个难点,只要获取到当前拥有焦点的控件,就可以根据虚拟键盘上按下的按键,对控件的内容进行修改
var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)
创建一个 CommonFunction 类,实现 getFocusedItem 函数,通过父节点去寻找子控件中哪一个拥有焦点
QQuickItem* CommonFunction::getFocusedItem(QQuickItem* rootItem)
{
if ( rootItem->hasActiveFocus() ) {
return rootItem;
}
QList<QQuickItem*> childItems = rootItem->childItems();
for (QQuickItem *childItem : childItems)
{
QQuickItem *focusedItem = getFocusedItem(childItem);
if (focusedItem) {
return focusedItem;
}
}
return nullptr;
}