[QML]Functional功能型控件-虚拟键盘

一、展示:

二、使用方法:

1.实例化(如Main.qml)只一次

2.在输入框下面调用函数

第一步-实例化

javascript 复制代码
// main.qml 或 MainContainer.qml
import "qrc:/uiitem/component/" // 键盘所在的路径

Item {
    id: root
    width: 1920
    height: 1080

    // ... 你原有的所有页面内容 ...

    // 【全局唯一】把键盘放在最底层,z值设高,确保永远在最上面
    ReusableVirtualKeyboard {
        id: reusableKeyboard
        z: 9999 
        // 可选:默认关闭弹出/收起的平滑移动动画
        keyboardAnimationEnabled: false
    }
}

第二步-在输入框调用函数

reusableKeyboard为虚拟键盘示例化的id

1.自适应位置,根据输入框位置自动计算不遮挡

setKeyboardWidth 必须在前可以设置大小(最好300大小以上)

showKeyboard只传id时,会自己计算位置。

javascript 复制代码
TextInput {
           id: pingInput
                           
           //selectByMouse: true
           /*============虚拟键盘输入框事件===============*/

           //虚拟键盘输入框点击事件--确保MouseArea不拦截焦点,且正确触发
           MouseArea {
               anchors.fill: parent
               cursorShape: Qt.IBeamCursor
               onClicked: {
                    reusableKeyboard.setKeyboardWidth(pingInput, 400);
                    reusableKeyboard.showKeyboard(pingInput);

                    // 允许事件传播,确保输入框能获取焦点
                    mouse.accepted = false;
               }
          }
}

2.设置固定位置id,"fixed",

setKeyboardWidth 必须在前可以设置大小

showKeyboard中的"fixed"表示固定位置(左上为起点),x水平位置,y垂直位置

javascript 复制代码
TextField {
           id: passwordField

           //。。。。。。。。。
                        
           //虚拟键盘输入框点击事件--确保MouseArea不拦截焦点,且正确触发
           //selectByMouse: true

           MouseArea {
               anchors.fill: parent
               cursorShape: Qt.IBeamCursor
               onClicked: {
               reusableKeyboard.setKeyboardWidth(passwordField, 600);
               reusableKeyboard.showKeyboard(passwordField, "fixed", logWindow.width/2-300, loginCard.y+loginCard.height);//logWindow.width/2-600

               // 允许事件传播,确保输入框能获取焦点
               mouse.accepted = false;
               }
          }
}

三、源码:

1.数字+26键大小写

ReusableVirtualKeyboard.qml
javascript 复制代码
// ReusableVirtualKeyboard.qml ------ 无限制 + 动态大小写版
import QtQuick 2.15
import QtQuick.Window 2.15

Item {
    id: keyboardRoot
    anchors.fill: parent
    visible: false
    z: 998

    // ===== 兼容原有接口 =====
    property int keyboardAnimationDuration: 250
    property string keyboardStyle: "default"
    property bool hideCandidateList: true
    property bool clearInputOnHide: false
    signal hideKeyboard()

    property Item activeInputField: null
    property bool keyboardAnimationEnabled: false
    property bool isPanelVisible: keyboardPanel.visible
    property int currentKeyboardWidth: 0
    property int targetX: 0
    property int targetY: 0
    property string currentInputText: ""

    // ===== 输入规则属性 =====
    property bool _currentAllowDecimal: true
    property int _currentMaxDecimalPlaces: -1
    property bool _currentAllowLeadingZeros: true

    // ===== 大小写切换属性(默认小写) =====
    property bool _isUpperCase: false

    // 颜色主题
    property var theme: ({
        bgColor: "#2d2d2d",
        keyColor: "#4d4d4d",
        keyPressedColor: "#6d6d6d",
        keyTextColor: "#ffffff",
        specialKeyColor: "#ff6b6b",
        specialKeyTextColor: "#ffffff",
        borderColor: "#3d3d3d"
    })

    // ===== 等比例缩放核心算法 =====
    readonly property real _baseWidth: 450
    readonly property real _baseSpacing: 5
    readonly property real _baseRadius: 10
    readonly property real _basePadding: 15

    readonly property real _scaleFactor: (currentKeyboardWidth > 0 ? currentKeyboardWidth : _baseWidth) / _baseWidth
    readonly property real _dynamicSpacing: _baseSpacing * _scaleFactor
    readonly property real _dynamicRadius: _baseRadius * _scaleFactor
    readonly property real _dynamicPadding: _basePadding * _scaleFactor
    readonly property real _keyWidth: {
        var w = currentKeyboardWidth > 0 ? currentKeyboardWidth : _baseWidth;
        return (w - _dynamicPadding * 2 - _dynamicSpacing * 9) / 10;
    }
    readonly property real _keyHeight: _keyWidth
    readonly property real _keyboardHeight: _keyHeight * 5 + _dynamicSpacing * 4 + _dynamicPadding * 2

    // ===== 点击外部隐藏键盘 =====
    MouseArea {
        id: globalMouseArea
        anchors.fill: parent
        enabled: keyboardRoot.visible
        z: 1
        preventStealing: true
        onPressed: {
            if (!activeInputField) { mouse.accepted = false; return; }
            var inputPos = activeInputField.mapToItem(keyboardRoot, 0, 0);
            var inInput = mouse.x >= inputPos.x && mouse.x <= inputPos.x + activeInputField.width &&
                          mouse.y >= inputPos.y && mouse.y <= inputPos.y + activeInputField.height;
            if (!inInput) {
                var panelPos = keyboardPanel.mapToItem(keyboardRoot, 0, 0);
                var inPanel = mouse.x >= panelPos.x && mouse.x <= panelPos.x + keyboardPanel.width &&
                              mouse.y >= panelPos.y && mouse.y <= panelPos.y + keyboardPanel.height;
                if (!inPanel) {
                    keyboardRoot.hideKeyboardManual();
                    mouse.accepted = true;
                    return;
                }
            }
            mouse.accepted = false;
        }
    }

    // ===== 键盘面板 =====
    Rectangle {
        id: keyboardPanel
        z: 999
        x: keyboardRoot.targetX
        y: keyboardRoot.targetY
        width: keyboardRoot.currentKeyboardWidth > 0 ? keyboardRoot.currentKeyboardWidth : _baseWidth
        height: _keyboardHeight
        visible: keyboardRoot.visible
        color: theme.bgColor
        radius: _dynamicRadius * 1.5
        border.color: theme.borderColor
        border.width: Math.max(1, _scaleFactor)
        clip: true

        MouseArea { anchors.fill: parent; onPressed: mouse.accepted = true; onReleased: mouse.accepted = true }

        Behavior on x { NumberAnimation { duration: keyboardRoot.keyboardAnimationEnabled ? keyboardRoot.keyboardAnimationDuration : 0; easing.type: Easing.InOutQuad } }
        Behavior on y { NumberAnimation { duration: keyboardRoot.keyboardAnimationEnabled ? keyboardRoot.keyboardAnimationDuration : 0; easing.type: Easing.InOutQuad } }

        Column {
            id: keyColumn
            anchors.fill: parent
            anchors.margins: _dynamicPadding
            spacing: _dynamicSpacing

            // 通用按键组件
            Component {
                id: baseKeyComponent
                Rectangle {
                    id: keyRect
                    property string baseChar: ""  // 统一存放基础字符(大写字母/数字/符号)
                    property string action: "char"
                    property bool isSpecial: false

                    // 【核心优化】:通过属性绑定,实时跟随 _isUpperCase 状态动态计算显示和输出字符
                    readonly property string displayLabel: {
                        if (isSpecial) return baseChar;
                        if (baseChar >= 'A' && baseChar <= 'Z') {
                            return keyboardRoot._isUpperCase ? baseChar : baseChar.toLowerCase();
                        }
                        return baseChar;
                    }
                    readonly property string displayCharVal: displayLabel

                    width: keyboardRoot._keyWidth
                    height: keyboardRoot._keyHeight
                    color: keyMouse.pressed ? (isSpecial ? Qt.darker(theme.specialKeyColor, 1.2) : theme.keyPressedColor) : (isSpecial ? theme.specialKeyColor : theme.keyColor)
                    radius: keyboardRoot._dynamicRadius
                    border.color: theme.borderColor
                    border.width: _scaleFactor > 0.6 ? 1 : 0

                    Text {
                        anchors.centerIn: parent
                        text: keyRect.displayLabel
                        color: theme.keyTextColor
                        font { pixelSize: parent.height * 0.4; bold: true; family: "Microsoft YaHei" }
                    }

                    MouseArea {
                        id: keyMouse
                        anchors.fill: parent
                        onClicked: {
                            if (action === "char") {
                                var c = keyRect.displayCharVal;
                                if (c >= '0' && c <= '9')
                                    keyboardRoot.appendDigit(parseInt(c));
                                else
                                    keyboardRoot.insertLetter(c);
                            } else if (action === "decimal") {
                                keyboardRoot.appendDecimal();
                            }
                        }
                    }
                }
            }

            // 第1排:数字 0-9
            Row {
                spacing: _dynamicSpacing
                Repeater {
                    model: ["0","1","2","3","4","5","6","7","8","9"]
                    Loader {
                        sourceComponent: baseKeyComponent;
                        onLoaded: { item.baseChar = modelData; }
                    }
                }
            }

            // 第2排:Q W E R T Y U I O P
            Row {
                spacing: _dynamicSpacing
                Repeater {
                    model: ["Q","W","E","R","T","Y","U","I","O","P"]
                    Loader {
                        sourceComponent: baseKeyComponent;
                        onLoaded: { item.baseChar = modelData; }
                    }
                }
            }

            // 第3排:A S D F G H J K L
            Row {
                spacing: _dynamicSpacing
                anchors.horizontalCenter: parent.horizontalCenter
                Repeater {
                    model: ["A","S","D","F","G","H","J","K","L"]
                    Loader {
                        sourceComponent: baseKeyComponent;
                        onLoaded: { item.baseChar = modelData; }
                    }
                }
            }

            // 第4排:Z X C V B N M .
            Row {
                spacing: _dynamicSpacing
                anchors.horizontalCenter: parent.horizontalCenter
                Repeater {
                    model: ["Z","X","C","V","B","N","M"]
                    Loader {
                        sourceComponent: baseKeyComponent;
                        onLoaded: { item.baseChar = modelData; }
                    }
                }
                Loader {
                    sourceComponent: baseKeyComponent
                    onLoaded: {
                        item.baseChar = ".";
                        item.action = "decimal";
                        item.isSpecial = true;
                    }
                }
            }

            // ===== 第5排:大小写键、清空键 和 删除键 =====
            Row {
                spacing: _dynamicSpacing
                anchors.horizontalCenter: parent.horizontalCenter

                // 大小写键 (占2个按键位 + 1个间距位)
                Rectangle {
                    width: keyboardRoot._keyWidth * 2 + keyboardRoot._dynamicSpacing
                    height: keyboardRoot._keyHeight
                    color: caseMouse.pressed ? theme.keyPressedColor : (keyboardRoot._isUpperCase ? theme.specialKeyColor : theme.keyColor)
                    radius: keyboardRoot._dynamicRadius
                    border.color: theme.borderColor
                    border.width: _scaleFactor > 0.6 ? 1 : 0
                    Text {
                        anchors.centerIn: parent
                        text: "Aa"
                        color: theme.keyTextColor
                        font { pixelSize: parent.height * 0.4; bold: true; family: "Microsoft YaHei" }
                    }
                    MouseArea {
                        id: caseMouse
                        anchors.fill: parent
                        onClicked: { keyboardRoot._isUpperCase = !keyboardRoot._isUpperCase; }
                    }
                }

                // 清空键 (占3个按键位 + 2个间距位)
                Rectangle {
                    width: keyboardRoot._keyWidth * 3 + keyboardRoot._dynamicSpacing * 2
                    height: keyboardRoot._keyHeight
                    color: clearMouse.pressed ? Qt.darker(theme.specialKeyColor, 1.2) : theme.specialKeyColor
                    radius: keyboardRoot._dynamicRadius
                    border.color: theme.borderColor
                    border.width: _scaleFactor > 0.6 ? 1 : 0
                    Text {
                        anchors.centerIn: parent
                        text: "清空"
                        color: theme.specialKeyTextColor
                        font { pixelSize: parent.height * 0.4; bold: true; family: "Microsoft YaHei" }
                    }
                    MouseArea {
                        id: clearMouse
                        anchors.fill: parent
                        onClicked: keyboardRoot.clearAll()
                    }
                }

                // 删除键 (占5个按键位 + 4个间距位)
                Rectangle {
                    width: keyboardRoot._keyWidth * 5 + keyboardRoot._dynamicSpacing * 4
                    height: keyboardRoot._keyHeight
                    color: delMouse.pressed ? theme.keyPressedColor : "#5d5d5d"
                    radius: keyboardRoot._dynamicRadius
                    border.color: theme.borderColor
                    border.width: _scaleFactor > 0.6 ? 1 : 0
                    Text {
                        anchors.centerIn: parent
                        text: "← 删除"
                        color: theme.keyTextColor
                        font { pixelSize: parent.height * 0.35; bold: true; family: "Microsoft YaHei" }
                    }
                    MouseArea {
                        id: delMouse
                        anchors.fill: parent
                        onClicked: keyboardRoot.backspace()
                        onPressAndHold: deleteTimer.start()
                        onReleased: deleteTimer.stop()
                    }
                }
            }
        }
    }

    Timer {
        id: deleteTimer
        interval: 100
        repeat: true
        onTriggered: keyboardRoot.backspace()
    }

    // ===== 核心输入逻辑(完全无限制) =====
    function insertLetter(c) {
        if (!activeInputField) return;
        // 直接追加传入的字符(按键组件本身已处理好大小写转换)
        var currentText = activeInputField.text || "";
        activeInputField.text = currentText + c;
        currentInputText = activeInputField.text;
        activeInputField.cursorPosition = activeInputField.text.length;
    }

    function appendDigit(digit) {
        if (!activeInputField) return;
        var currentText = activeInputField.text || "";
        activeInputField.text = currentText + String(digit);
        currentInputText = activeInputField.text;
        activeInputField.cursorPosition = activeInputField.text.length;
    }

    function appendDecimal() {
        if (!activeInputField) return;
        var currentText = activeInputField.text || "";
        activeInputField.text = currentText + ".";
        currentInputText = activeInputField.text;
        activeInputField.cursorPosition = activeInputField.text.length;
    }

    function backspace() {
        if (!activeInputField) return;
        var currentText = activeInputField.text || "";
        if (currentText.length === 0) return;
        if (activeInputField.selectedText.length > 0) {
            activeInputField.remove(activeInputField.selectionStart, activeInputField.selectionEnd);
        } else {
            var cursorPos = activeInputField.cursorPosition;
            if (cursorPos > 0) {
                activeInputField.text = currentText.substring(0, cursorPos - 1) + currentText.substring(cursorPos);
                activeInputField.cursorPosition = cursorPos - 1;
            }
        }
        currentInputText = activeInputField.text;
    }

    function clearAll() {
        if (!activeInputField) return;
        activeInputField.text = "";
        currentInputText = "";
    }

    // ===== 接口 =====
    function setKeyboardWidth(targetInput, customWidth) {
        if (!targetInput || !(targetInput instanceof Item) || typeof targetInput.width !== "number") return false;
        var finalWidth = (customWidth && !isNaN(customWidth) && customWidth > 0) ? customWidth : Math.max(_baseWidth, targetInput.width);
        keyboardRoot.currentKeyboardWidth = finalWidth;
        return true;
    }

    function showKeyboard(targetInputField, mode, fixedX, fixedY, opts) {
        mode = mode || "dynamic";
        fixedX = fixedX || 0;
        fixedY = fixedY || 0;

        // 强制放开所有限制,无视外部传入的 opts
        _currentAllowDecimal = true;
        _currentMaxDecimalPlaces = -1;
        _currentAllowLeadingZeros = true;

        if (!targetInputField) return;
        if (keyboardRoot.currentKeyboardWidth <= 0) setKeyboardWidth(targetInputField);
        if (activeInputField && activeInputField !== targetInputField) activeInputField.focus = false;

        activeInputField = targetInputField;
        targetInputField.forceActiveFocus();
        currentInputText = targetInputField.text || "";
        keyboardRoot.visible = true;

        var kbHeight = _keyboardHeight;
        var kbWidth = keyboardRoot.currentKeyboardWidth > 0 ? keyboardRoot.currentKeyboardWidth : _baseWidth;

        if (mode === "dynamic") {
            var inputFieldPos = targetInputField.mapToItem(keyboardRoot, 0, 0);
            var calcY = inputFieldPos.y + targetInputField.height + 10;
            var calcX = inputFieldPos.x;
            if (calcX + kbWidth > keyboardRoot.width) calcX = keyboardRoot.width - kbWidth - 10;
            calcX = Math.max(10, calcX);
            if (calcY + kbHeight > keyboardRoot.height) {
                calcY = inputFieldPos.y - kbHeight - 10;
                calcY = Math.max(10, calcY);
            }
            keyboardRoot.targetX = calcX;
            keyboardRoot.targetY = calcY;
        } else {
            keyboardRoot.targetX = fixedX;
            keyboardRoot.targetY = fixedY;
        }
    }

    function hideKeyboardManual() {
        try {
            keyboardRoot.visible = false;
            keyboardRoot.targetX = 0;
            keyboardRoot.targetY = 0;
            keyboardRoot.currentKeyboardWidth = 0;
            keyboardRoot.currentInputText = "";
            if (activeInputField && !activeInputField.destroyed) activeInputField.focus = false;
            activeInputField = null;
            hideKeyboard();
        } catch (e) {
            keyboardRoot.visible = false;
            activeInputField = null;
        }
    }

    function obtainPanelVisible() {
        return isPanelVisible;
    }
}
相关推荐
feVA LTYR2 小时前
Windows上安装Go并配置环境变量(图文步骤)
开发语言·windows·golang
hhb_6182 小时前
C#高性能异步编程实战与底层原理深度解析
开发语言·c#
雾岛听蓝2 小时前
Qt操作指南:状态栏、浮动窗口与对话框使用
开发语言·经验分享·笔记·qt
minji...3 小时前
Linux 线程同步与互斥(五) 日志,线程池
linux·运维·服务器·开发语言·c++·算法
兩尛3 小时前
c++面试常问2
开发语言·c++·面试
Rust研习社3 小时前
添加依赖库时的 features 是什么?优雅实现编译期条件编译与模块化开发
开发语言·后端·rust
Tel199253080043 小时前
ENDAT2.2 协议信号转 SSI /BISS-C转换卡 ENDAT2.2 协议信号转DMC多摩川高速协议转换器 互转卡
c语言·开发语言·网络
Tiger_shl4 小时前
C# 托管对象、非托管对象 讲解
开发语言·c#