QML 中的锁屏模式实现

问题描述 投票:0回答:1

我正在寻求实现一个锁屏模式,可用于发现其他设置。为此,我使用

RoundButtons
生成了 3x3 的
Repeater
网格,如下所示:

Grid {
anchors.centerIn: parent
spacing: 10
columns: 3
rows: 3

    Repeater {
    id: rpt
    model: 9
    delegate: RoundButton {
            radius: Math.max(width, height) / 2
        }
    }
}

如何检测手指被拖动到哪个按钮,以便将其添加到图案中?我看过

TapHandler
MouseArea
但我似乎不明白我可以使用哪些属性来检查按下的手指是否被拖动到按钮上。

如果缺少我应该添加的任何其他信息,请告诉我。谢谢。

qml lockscreen unlock
1个回答
0
投票

这是用 QML 编写的

模式锁
的示例。

我发现使用单个

PointHandler
(或
MouseArea
)并创建网格状触摸部分更简单,而不是找到一种方法来连续检测多个
MouseAreas
上的鼠标移动。可以将剩余的鼠标位置获取到图块的
width
,然后获取鼠标相对于每个图块的位置,并检测鼠标是否进入内圈。

const [x,y] = [mousePos.x % tile.width, mousePos.y % tile.height];
/// Now, you have to find the distance of the x/y position from the tile center.

然后将鼠标位置除以图块宽度即可得到当前鼠标位置的区域。

const cIndex = Math.floor(mousePos.x / tile.width);
const rIndex /// same as cIndex..

array.push(rIndex * 3 + cIndex); /// j * 3 + i

最后,您可以将索引保存在数组中作为输入的模式。

注意:另外,值得一提的是,您应该考虑不要访问一个点两次。

上面的描述对于简单的功能来说已经足够了,但我提供了一个完整的示例。 您只需将其复制到

.qml
文件中并在程序中使用该组件即可。

您还可以在此处找到完整的源代码。

预览

使用方法

组件的用法如本例所示。

PatternLock {
    anchors.fill: parent
    onRelease: {
        print(path);
        reset();
    }
}

PatternLock.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Shapes 1.15

Control {
    id: control

    component Circle: Rectangle {
        x: (parent.width - width)/2
        y: (parent.height - height)/2
        width: 5; height: width; radius: width
        color: control.palette.button
        opacity: 0.8
    }

    signal press()
    signal release()

    property alias shapePath: shapePath

    property real snapMargin: 10

    property int columns: 3
    property int rows: 3

    /// Path of pattern
    property var path: []

    function reset() {
        internals.visited = [];
        control.path = [];
        pathpoly.path = [];
    }

    /// Reposition points in case
    onWidthChanged: Qt.callLater(internals.reposition);
    onHeightChanged: Qt.callLater(internals.reposition);

    QtObject {
        id: internals
        property var visited: Array(columns * rows).fill(false);

        readonly property alias pos: pointhandler.point.position;
        readonly property size subgrid: Qt.size(availableWidth/columns, availableHeight/rows);
        /// Current location of the mouse.
        readonly property point region: Qt.point(Math.floor(pos.x / subgrid.width),
                                                 Math.floor(pos.y / subgrid.height));
        /// Mouse position relative to the center of the circles.
        readonly property point subpos: Qt.point(pos.x % subgrid.width - subgrid.width/2,
                                                 pos.y % subgrid.height - subgrid.height/2);
        /// NOTE: There is no way to hide the last PathLine item when the mouse is not pressed,
        ///  so it currently points to the end of the path, if it exists.
        readonly property point trail: pointhandler.active ? pos : (pathpoly.path.slice(-1)[0] ?? Qt.point(0,0));
        readonly property int currentIndex: distance(subpos) < snapMargin ? region.y * columns + region.x : -1

        /// Returns the length of the point.
        function distance(p: point): real {
            return Math.sqrt(p.x * p.x + p.y * p.y);
        }

        /// Reposition all points of polypath if there is any resizing.
        function reposition() {
            if(path.length) {
                const newPath = path.map(idx => {
                    const [i, j] = [idx % columns, Math.floor(idx/columns)];
                    return Qt.point((i + 0.5) * subgrid.width, (j + 0.5) * subgrid.height);
                });
                pathpoly.path = newPath;
            }
        }
    }

    contentItem: Item {
        /// Grid of circles
        Grid {
            columns: control.columns

            Repeater {
                model: control.rows * control.columns
                Item {
                    width: internals.subgrid.width
                    height: internals.subgrid.height

                    Circle {
                        width: index === internals.currentIndex ? 15 : 5
                        Behavior on width {NumberAnimation{}}
                    }
                }
            }
        }

        PointHandler {
            id: pointhandler
            onActiveChanged: {
                if(active) control.press();
                else control.release();
            }

            onPointChanged: {
                if(active && internals.distance(internals.subpos) < control.snapMargin) {
                    const region = internals.region;
                    const index = region.y * columns + region.x;
                    const sg = internals.subgrid;

                    /// Check if the mouse position is valid and the point hasn't been visited yet.
                    if(!internals.visited[index] && 0 <= index && region.x < columns && region.y < rows) {
                        const point = Qt.point((region.x + 0.5) * sg.width, (region.y + 0.5) * sg.height);
                        internals.visited[index] = true;
                        control.path.push(index);
                        /// BUG: pathpoly.path.push() doesn't work on Qt 5.15.2
                        pathpoly.path = [...pathpoly.path, point];
                    }
                }
            }
        }

        Shape {
            width: parent.width
            height: parent.height
            smooth: true
            visible: pathpoly.path.length

            ShapePath {
                id: shapePath
                strokeWidth: 1
                strokeColor: control.palette.button
                fillColor: 'transparent'

                startX: (pathpoly.path[0] ?? {x:0}).x
                startY: (pathpoly.path[0] ?? {y:0}).y

                PathPolyline {
                    id: pathpoly
                    path: []
                }

                /// PathLine to the current mouse position
                PathLine {
                    x: internals.trail.x
                    y: internals.trail.y
                }
            }
        }
    }

    background: Rectangle {
        color: palette.window
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.