QML 实现九宫格图案解锁

《QML基础教程》总目录


一、演示

请添加图片描述
支持自定义行列数;
支持自定义颜色;
支持自定义吸附半径。


二、简述

1.使用GridView显示圆圈,方便自定义。数据模型使用一个selectedIndex数组,保存选中index。
2.画线使用Canvas,借助选中的索引数组,变化时刷新连线。
3.鼠标检测使用一个MouseArea,当光标移动时,检测光标位置所处的格子索引,再做坐标转换。计算光标距离格子中心的半径,处于圆圈中就选中该索引。


三、代码

main.qml

import QtQuick

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    PatternPassword{
        width: 300
        height: 300
        anchors.centerIn: parent
        onReleased: console.log("released:",selectIndexs)
    }
}

图案解锁控件源码

import QtQuick

/*
  图案密码控件

  通过width,height指定控件的宽高。row,column指定图案密码的行数和列数。网格宽=width/column,网格高=height/row。
  通过hotRadius指定网格中圆圈的半径,单位像素。半径以内可以吸附线。、
  lineWidth指定线宽,单位像素。
  [signal] released() 图案松开信号,通过selectIndexs获取选中的点。index从0开始,从左到右依次+1。
*/
Item {
    id: root

    property int row: 4                                             //网格行数
    property int column: 4                                          //网格列数
    property int pressedIndex: -1                                   //鼠标按到了某个网格的热区内
    property var selectIndexs: []                                   //鼠标滑过选中的点
    property int lineWidth: 6                                       //连线宽度,圆圈线宽
    property int hotRadius: Math.min(cellWidth,cellHeight) * 0.2    //热区半径,半径之内点会被选中
    property color color: "white"                                   //圆圈颜色
    property color borderColor: "darkGray"                          //默认圆圈边框颜色
    property color selectedBorderColor: "gray"                      //选中圆圈边框颜色
    property color lineColor: "gray"                                //连线颜色
    readonly property alias pressed: mouseArea.pressed              //网格中有鼠标按下
    readonly property alias cellWidth: gridView.cellWidth           //网格宽度
    readonly property alias cellHeight: gridView.cellHeight         //网格高度

    signal released()                                               //图案松开

    //绘制选中点之间的连线
    Canvas{
        id: canvas
        anchors.fill: parent

        onPaint: {
            var ctx = getContext("2d")
            ctx.lineJoin = 'round';
            ctx.clearRect(0,0,canvas.width,canvas.height)

            if(0 === selectIndexs.length)
                return ;

            ctx.strokeStyle = root.lineColor
            ctx.lineWidth = lineWidth

            for(var i = 0; i < root.selectIndexs.length; i++) {
                var posX = (Math.floor(selectIndexs[i] % column) + 0.5) * cellWidth
                var posY = (Math.floor(selectIndexs[i] / column) + 0.5) * cellHeight
                if(0 === i){
                    ctx.beginPath()
                    ctx.moveTo(posX, posY)
                }else{
                    ctx.lineTo(posX, posY)
                }
            }

            ctx.lineTo(mouseArea.mouseX, mouseArea.mouseY)
            ctx.stroke()
        }


    }

    //图案网格圈显示
    GridView{
        id: gridView
        anchors.fill: parent
        cellHeight: gridView.height / row
        cellWidth: gridView.width / column
        interactive: false
        model: row * column
        delegate: Item{
            property bool checked: root.pressed ? (pressedIndex === index? true : checked) : false

            width: gridView.cellWidth
            height: gridView.cellHeight

            Rectangle{
                anchors.centerIn: parent
                width: hotRadius * 2
                height: width
                border.width: lineWidth
                border.color: checked ?  root.selectedBorderColor : root.borderColor
                color: root.color
                radius: width / 2
            }
        }
    }

    //检测选中点模型管理
    MouseArea{
        id: mouseArea

        anchors.fill: parent
        onPressedChanged: refresh()
        onMouseXChanged: refresh()
        onMouseYChanged: refresh()

        function refresh(){
            if (!pressed || mouseX >= width || mouseY >= height
                    || mouseX < 0 ||  mouseY < 0){
                canvas.requestPaint()
                return
            }
            //找到光标所在的格子
            var mouseRow = Math.floor(mouseY / cellHeight)
            var mouseColumn = Math.floor(mouseX / cellWidth)

            //坐标转换 以当前格子中心点为0,0点
            var newPosX = mouseX - (mouseColumn + 0.5) * cellWidth
            var newPosY = mouseY - (mouseRow + 0.5) * cellHeight

            //计算与原点的距离,判断是否在圆形热区中
            var distance = Math.sqrt(newPosX * newPosX + newPosY * newPosY);
            if (distance < hotRadius) {
                //计算热区的index,添加选中
                var index = mouseRow * root.column + mouseColumn
                if(-1 === selectIndexs.indexOf(index)) {
                    selectIndexs.push(index)
                    pressedIndex = index
                }
            }else{
                pressedIndex = -1
            }

            //重绘连线
            canvas.requestPaint()
        }

        onPressed: selectIndexs = []
        onReleased: {
            root.released()
            selectIndexs = []
        }
    }
}

由于作者能力有限,文章难免疏漏,如有错误之处,欢迎指出,及时更改

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值