作者: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.15Window {width: 1024; height: 768visible: truetitle: qsTr("Virtual Keyboard Demo")Rectangle {anchors.fill: parentcolor: "lightblue"BQTextInput {id: input1x: 10; y: 10width: 300; height: 40pixelSize: 16textFontFamily: "微软雅黑"placeholderText: "测试文本1"}BQTextInput {id: input2x: 10; y: 60width: 300; height: 40pixelSize: 16textFontFamily: "微软雅黑"placeholderText: "测试文本2"}Button {x: 10; y: 110width: 120; height: 40font.pixelSize: 16text: "切换键盘语言"onClicked: virtualKeyboard.languageType = virtualKeyboard.languageType == 1 ? 2 : 1}BQVirtualKeyboard {id: virtualKeyboardy: 180anchors.horizontalCenter: parent.horizontalCentervisible: input1.hasFocus || input2.hasFocus}}
}
2. BQTextInput.qml
自定义文本输入框,从其他项目中直接拷贝过来的,主要用于实现账号密码登录时的文本输入,如下图所示
import QtQuick 2.15// 自定义文本输入框
Rectangle {width: 200; height: 40color: "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: inputx: 5width: parent.width - rightDis - 10height: parent.heightactiveFocusOnPress: truefont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: TextInput.AlignVCenterechoMode: isPassword ? TextInput.Password : TextInput.Normalclip: trueText {x: 5anchors.verticalCenter: parent.verticalCenterfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCentertext: placeholderTextvisible: input.text == ""}}Image {id: imagewidth: rightDis; height: rightDisanchors.verticalCenter: parent.verticalCenteranchors.right: parent.rightanchors.rightMargin: (parent.height - rightDis) / 2source: imageSourcevisible: rightDis != 0MouseArea {anchors.fill: parentcursorShape: Qt.PointingHandCursoronClicked: {image.focus = trueimagePressed()}}}
}
3. BQVirtualKeyboard.qml
自定义虚拟键盘,一共4行布局,符号可以按照需求自己改
import QtQuick 2.15// 自定义虚拟键盘
Rectangle {id: virtualKeyboardwidth: 710; height: 290radius: 5color: "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: 10anchors.horizontalCenter: parent.horizontalCenterspacing: 10Repeater {model: {if (isEnglish) {isUpper ? en_line1_upper : en_line1_lower} else {page == 1 ? char_page1_line1 : char_page2_line1}}Rectangle {width: 60; height: 60radius: 5color: area1.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area1.pressed ? "#5D4B37" : "#FFFFFF"text: modelData}MouseArea {id: area1anchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text = focusedItem.text + modelData}}}}}// 第二行Row {y: 80anchors.horizontalCenter: parent.horizontalCenterspacing: 10Repeater {model: {if (isEnglish) {isUpper ? en_line2_upper : en_line2_lower} else {page == 1 ? char_page1_line2 : char_page2_line2}}Rectangle {width: 60; height: 60radius: 5color: area2.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area2.pressed ? "#5D4B37" : "#FFFFFF"text: modelData}MouseArea {id: area2anchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text = focusedItem.text + modelData}}}}}// 第三行Row {y: 150anchors.horizontalCenter: parent.horizontalCenterspacing: 10// shiftRectangle {width: 95; height: 60radius: 5color: area_shift.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_shift.pressed ? "#5D4B37" : (isEnglish && isUpper ? "#239B56" : "#FFFFFF")text: isEnglish ? "Shift" : (page == 1 ? "1/2" : "2/2")}MouseArea {id: area_shiftanchors.fill: parentfocus: falseonClicked: {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: 60radius: 5color: area3.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area3.pressed ? "#5D4B37" : "#FFFFFF"text: modelData}MouseArea {id: area3anchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text = focusedItem.text + modelData}}}}// backspaceRectangle {width: 95; height: 60radius: 5color: area_backspace.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_backspace.pressed ? "#5D4B37" : "#FFFFFF"text: languageType == 1 ? "回 退" : "Backspace"}MouseArea {id: area_backspaceanchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text = focusedItem.text.slice(0, -1)}}}}// 第四行Row {y: 220anchors.horizontalCenter: parent.horizontalCenterspacing: 10// switchRectangle {width: 95; height: 60radius: 5color: area_switch.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_switch.pressed ? "#5D4B37" : "#FFFFFF"text: isEnglish ? "&123" : "ABC"}MouseArea {id: area_switchanchors.fill: parentfocus: falseonClicked: isEnglish = !isEnglish}}// spaceRectangle {width: 375; height: 60radius: 5color: area_space.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_space.pressed ? "#5D4B37" : "#FFFFFF"text: languageType == 1 ? "空 格" : "Space"}MouseArea {id: area_spaceanchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text += " "}}}// clearRectangle {width: 95; height: 60radius: 5color: area_clear.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_clear.pressed ? "#5D4B37" : "#FFFFFF"text: languageType == 1 ? "清 空" : "Clear"}MouseArea {id: area_clearanchors.fill: parentfocus: falseonClicked: {var focusedItem = commonFun.getFocusedItem(virtualKeyboard.parent)focusedItem.text = ""}}}// hideRectangle {id: hidewidth: 95; height: 60radius: 5color: area_hide.pressed ? "#2A2826" : "#383533"Text {anchors.fill: parentfont.pixelSize: pixelSizefont.family: textFontFamilyverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentercolor: area_hide.pressed ? "#5D4B37" : "#FFFFFF"text: languageType == 1 ? "隐 藏" : "Hide"}MouseArea {id: area_hideanchors.fill: parentonClicked: 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;
}