一、演示
支持自定义行列数;
支持自定义颜色;
支持自定义吸附半径。
二、简述
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 = []
}
}
}
由于作者能力有限,文章难免疏漏,如有错误之处,欢迎指出,及时更改