QML 中自定义虚拟键盘

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

前言

我们知道 Qt 中虚拟键盘模块遵循的是 GPL 协议,是不可用于商业发布的。如果项目中使用了 Qt 自带的虚拟键盘,在正式发布项目时必须要开源才可以。因此为了避免使用此模块就需要自己来实现一个虚拟键盘功能。博主在网上也搜到了一些资源,基本上都是 widget 来实现的,用 qml 来做的很少,这里我们以官方的虚拟键盘为参照,用 qml 自己实现一个键盘。

功能展示

QML 中自定义虚拟键盘_第1张图片

代码展示

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 childItems = rootItem->childItems();
    for (QQuickItem *childItem : childItems)
    {
        QQuickItem *focusedItem = getFocusedItem(childItem);
        if (focusedItem) {
            return focusedItem;
        }
    }

    return nullptr;
}

你可能感兴趣的:(Qt,QML,qt,qml,虚拟键盘)