Поменяйте местами элементы в GridView - PullRequest
0 голосов
/ 16 ноября 2018

Я пишу программу QML, которая представляет собой GridView 4x4, заполненный пронумерованными прямоугольниками. Я хотел бы иметь возможность:

  1. Поменяйте местами два элемента из сетки, перетаскивая их

  2. Разрешить замену только для непосредственно смежных элементов

Моя текущая проблема заключается в том, что, как только я перетаскиваю элемент поверх другого, вся сетка корректирует позиции, заполняя промежуток, в котором изначально был элемент. Есть ли способ избежать поведения автоматической настройки для этого типа сетки?

Я знаю, что фрагмент кода ниже может быть ответственным за это поведение, я просто не могу понять, как изменить его более корректно.

DropArea {
            anchors { fill: parent; margins: 15 }
            onEntered: {visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)}
        }

Полный код:

import QtQuick 2.0
import QtQml.Models 2.1

GridView {
    id: root
    width: 320; height: 480
    cellWidth: 80; cellHeight: 80

    displaced: Transition {
        NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }//Animação anima a transicao dos tiles
    }

    model: DelegateModel {
        id: visualModel
        model: ListModel {
            id: colorModel
            ListElement { color: "lightsteelblue" ; text: "1" }
            ListElement { color: "lightsteelblue" ; text: "2" }
            ListElement { color: "lightsteelblue" ; text: "3" }
            ListElement { color: "lightsteelblue" ; text: "4" }
            ListElement { color: "lightsteelblue" ; text: "5" }
            ListElement { color: "lightsteelblue" ; text: "6" }
            ListElement { color: "lightsteelblue" ; text: "7" }
            ListElement { color: "lightsteelblue" ; text: "8" }
            ListElement { color: "lightsteelblue" ; text: "9" }
            ListElement { color: "lightsteelblue" ; text: "10" }
            ListElement { color: "lightsteelblue" ; text: "11" }
            ListElement { color: "lightsteelblue" ; text: "12" }
            ListElement { color: "lightsteelblue" ; text: "13" }
            ListElement { color: "lightsteelblue" ; text: "14" }
            ListElement { color: "lightsteelblue" ; text: "15" }
            ListElement { color: "transparent"  }
        }

        delegate: MouseArea {
            id: delegateRoot

            property int visualIndex: DelegateModel.itemsIndex

            width: 80; height: 80
            drag.target: icon

            Rectangle {
                id: icon
                Text {
                   text: model.text
                   font.pointSize: 30
                   anchors.centerIn: parent
                }
                width: 72; height: 72
                anchors {
                    horizontalCenter: parent.horizontalCenter;
                    verticalCenter: parent.verticalCenter
                }
                color: model.color
                radius: 3

                Drag.active: delegateRoot.drag.active
                Drag.source: delegateRoot
                Drag.hotSpot.x: 36
                Drag.hotSpot.y: 36

                states: [
                    State {
                        when: icon.Drag.active
                        ParentChange {
                            target: icon
                            parent: root
                        }

                        AnchorChanges {
                            target: icon;
                            anchors.horizontalCenter: undefined;
                            anchors.verticalCenter: undefined
                        }
                    }
                ]
            }

            DropArea {
                anchors { fill: parent; margins: 15 }
                onEntered: {visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)}
            }
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 22 ноября 2018

Я пытался что-то, но все еще мало ошибок здесь и там.Надеюсь, это поможет.

import QtQuick 2.0
import QtQml.Models 2.1

GridView {
id: root
width: 320; height: 480
cellWidth: 80; cellHeight: 80

displaced: Transition {
    NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }//Animação anima a transicao dos tiles
}

model: DelegateModel {
    id: visualModel
    model: ListModel {
        id: colorModel
        ListElement { color: "lightsteelblue" ; text: "1" }
        ListElement { color: "lightsteelblue" ; text: "2" }
        ListElement { color: "lightsteelblue" ; text: "3" }
        ListElement { color: "lightsteelblue" ; text: "4" }
        ListElement { color: "lightsteelblue" ; text: "5" }
        ListElement { color: "lightsteelblue" ; text: "6" }
        ListElement { color: "lightsteelblue" ; text: "7" }
        ListElement { color: "lightsteelblue" ; text: "8" }
        ListElement { color: "lightsteelblue" ; text: "9" }
        ListElement { color: "lightsteelblue" ; text: "10" }
        ListElement { color: "lightsteelblue" ; text: "11" }
        ListElement { color: "lightsteelblue" ; text: "12" }
        ListElement { color: "lightsteelblue" ; text: "13" }
        ListElement { color: "lightsteelblue" ; text: "14" }
        ListElement { color: "lightsteelblue" ; text: "15" }
        ListElement { color: "transparent" ; text:"" }
    }

    delegate: MouseArea {
        id: delegateRoot
        property bool held: false
        property int visualIndex: DelegateModel.itemsIndex

        width: 80; height: 80
        drag.target: held ? icon : undefined
        drag.axis: Drag.XAndYAxis
        drag.minimumX: delegateRoot.x-75
        drag.minimumY: delegateRoot.y-75
        drag.maximumX: delegateRoot.x + 85
        drag.maximumY: delegateRoot.y + 85

        onPressed: {
                            held = true
                            icon.opacity = 0.5
                        }
                        onReleased: {
                            if (held === true) {
                                held = false
                                icon.opacity = 1
                                icon.Drag.drop()
                            } else {
                                //action on release
                            }
                        }

        Rectangle {
            id: icon
            Text {
               text: model.text
               font.pointSize: 30
               anchors.centerIn: parent
            }
            width: 72; height: 72
            anchors {
                horizontalCenter: parent.horizontalCenter;
                verticalCenter: parent.verticalCenter
            }
            color: model.color
            radius: 3

            Drag.active: delegateRoot.drag.active
            Drag.source: delegateRoot
            Drag.hotSpot.x: 36
            Drag.hotSpot.y: 36
            states: [
                State {
                    when: icon.Drag.active
                    ParentChange {
                        target: icon
                        parent: root
                    }

                    AnchorChanges {
                        target: icon;
                        anchors.horizontalCenter: undefined;
                        anchors.verticalCenter: undefined
                    }
                }
            ]
        }

        DropArea {
            anchors {
                                   fill: parent
                                   margins: 15
                    }
            onDropped: {
                var sourceNumber = colorModel.get(drag.source.visualIndex).text;
                var targetNumber = colorModel.get(delegateRoot.visualIndex).text;
                var sourceColor = colorModel.get(drag.source.visualIndex).color;
                var targetColor = colorModel.get(delegateRoot.visualIndex).color;
                colorModel.setProperty(drag.source.visualIndex, "text", targetNumber);
                colorModel.setProperty(delegateRoot.visualIndex, "text", sourceNumber);
                colorModel.setProperty(drag.source.visualIndex, "color", targetColor);
                colorModel.setProperty(delegateRoot.visualIndex, "color", sourceColor);
            }
        }
    }
}
}
0 голосов
/ 20 ноября 2018

Вот реализация DropArea, которая специализируется на замене соседних элементов в сетке 4x4. См. Объяснение ниже.

DropArea {
    id: dropArea

    anchors { fill: parent; margins: 15 }
    onEntered: {
        //  store as local variables
        var from = drag.source.visualIndex;
        var to = delegateRoot.visualIndex;

        console.log(from, "-->", to);

        //  `isAdjacent` is a function implemented below
        if (isAdjacent(from, to))
            console.warn("Yes, adjacent.");
        else {
            console.warn("No, not adjacent.");

            //  jump the gun, we don't care if they're not adjacent
            return;
        }

        //  normal move
        visualModel.items.move(from, to);
        // visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex);   //  this is the same as the line above


        //  if `from`/`to` are horizontally adjacent (left/right)
        //  then the move is already valid

        if (from % 4 < 3 && from + 1 === to)    //  check `to` is right of `from`
            return;
        if (from % 4 > 0 && from - 1 === to)    //  check `to` is left of `from`
            return;


        //  move for vertically adjacent
        if (from < 12 && from + 4 === to)   //  check `to` is below `from`
            visualModel.items.move(to - 1, from);   // CRUCIAL MOVE

        if (from >= 4 && from - 4 === to)   //  check `to` is above `from`
            visualModel.items.move(to + 1, from);   // CRUCIAL MOVE

    }

    function isAdjacent(from, to) {
        if (from % 4 < 3 && from + 1 === to)    //  check `to` is right of `from`
            return true;
        if (from % 4 > 0 && from - 1 === to)    //  check `to` is left of `from`
            return true;
        if (from < 12 && from + 4 === to)   //  check `to` is below `from`
            return true;
        if (from >= 4 && from - 4 === to)   //  check `to` is above from
            return true;

        return false;
    }
}

Фактический мыслительный процесс стал довольно математическим. Но вот оно.

Как проверить соседство?

Возможно, вы могли бы выполнить поиск в Google и легко найти что-нибудь. Но я объясню условия по одному.

//  check `to` is right of `from`
from % 4 < 3         // first make sure that `from` is not on the last column
from + 1 === to      // then check that `to` is on the next tile

//  check `to` is left of `from`
from % 4 > 0         // first make sure that `from` is not on the first column
from - 1 === to      // then check that `to` is on the previous tile

//  check `to` is below `from`
from < 12            // first make sure that `from` is not on the last row
from + 4 === to      // then check that `to` is four tiles to the right
                     // with the grid's wraparound, this will check if `to` is
                     // below `from`

//  check `to` is above from
from >= 4            // first make sure that `from` is not on the first row
from - 4 === to      // then check that `to` is four tiles to the left
                     // with the grid's wraparound, this will check if `to` is
                     // above `from`

Как можно получить Критические Движения ?

Я начал с рисования на бумаге того, что произойдет с сеткой 2x2. Для горизонтально смежных элементов нет проблем в обмене. Единственная проблема возникает при замене вертикально смежных элементов.

Let X(i) -> Y(j) denote an object with display X at index i moving to index j, where index j was originally occupied by an object with display Y. The index of X becomes j, making it X(j) and object Y becomes displaced.

Consider if we want to swap B(1) with D(3) in a 2x2 grid.

+------+------+                   +------+------+
| A(0) | B(1) |    ←  ↖           | A(0) | D(1) |
+------+------+         >   -->   +------+------+
| C(2) | D(3) |    ←  ↙           | C(2) | B(3) |
+------+------+                   +------+------+

>>> User drags B(1) to D(3).

>>> var from = 1;
>>> var to = 3;

Just executing the command
>>> visualModel.items.move(from, to);

will give

B(1) -> D(3) -> C(2) -> (1)

i.e.
Object B goes to index 3. Displaces object D.
Object D goes to index 2. Displaces object C.
Object C goes to index 1, which is empty, since B was already moved.

And the result of the grid is

+------+------+
| A(0) | C(1) |
+------+------+
| D(2) | B(3) |
+------+------+

To achieve the desired result, we need to swap C(1) and D(2).

>>> visualModel.items.move(1, 2);

C(1) -> D(2) -> (1)

The result is shown below.

+------+------+
| A(0) | D(1) |
+------+------+
| C(2) | B(3) |
+------+------+

Now consider swapping in a 3x3 grid.

Consider swapping E(4) with H(7).

+------+------+------+                +------+------+------+
| A(0) | B(1) | C(2) |                | A(0) | B(1) | C(2) |
+------+------+------+                +------+------+------+
| D(3) | E(4) | F(5) |      -->       | D(3) | H(4) | F(5) |
+------+------+------+                +------+------+------+
| G(6) | H(7) | I(8) |                | G(6) | E(7) | I(8) |
+------+------+------+                +------+------+------+

>>> User drags E(4) to H(7)
>>> var from = 4;
>>> var to = 7;
>>> visualModel.items.move(from, to);

E(4) -> H(7) -> G(6) -> F(5) -> (4)

This results in

+------+------+------+
| A(0) | B(1) | C(2) |
+------+------+------+
| D(3) | F(4) | G(5) |
+------+------+------+
| H(6) | E(7) | I(8) |
+------+------+------+

To get our desired result, to get H(6) up to (4),
we need to simulate the user dragging H(6) to (4).

>>> visualModel.items.move(6, 4);

H(6) -> F(4) -> G(5) -> (6)

This achieves our desired result and gives us

+------+------+------+
| A(0) | B(1) | C(2) |
+------+------+------+
| D(3) | H(4) | F(5) |
+------+------+------+
| G(6) | E(7) | I(8) |
+------+------+------+

The crucial move here was with that second move command.
>>> visualModel.items.move(6, 4);

We can generalise that...

Каждый раз, когда мы перемещаем элемент вниз на соседнюю плитку с from до to, все плитки от from + 1 до to будут сдвигаться влево в порядке заполнить пробел.

Предмет, который мы хотим обменять, смещается на to - 1. Таким образом, мы перемещаемся to - 1 в from. Таким образом, мы получаем visualModel.items.move(to - 1, from); для сдвигающихся плиток, где to на ниже from.


We've tried dragging with `from` < `to`.
I.e., we dragged from an upper row to a lower row.

But what if we were to drag from a lower row to an upper row?
I.e. `to` < `from`.

The grid and desired result is the same.

+------+------+------+                +------+------+------+
| A(0) | B(1) | C(2) |                | A(0) | B(1) | C(2) |
+------+------+------+                +------+------+------+
| D(3) | E(4) | F(5) |      -->       | D(3) | H(4) | F(5) |
+------+------+------+                +------+------+------+
| G(6) | H(7) | I(8) |                | G(6) | E(7) | I(8) |
+------+------+------+                +------+------+------+

But...

>>> User drags H(7) to E(4)

Note: previously, it was "User drags E(4) to H(7)".

Thus,
>>> var from = 7;
>>> var to = 4;
>>> visualModel.items.move(from, to);

H(7) -> E(4) -> F(5) -> G(6) -> (7)

The grid is then

+------+------+------+
| A(0) | B(1) | C(2) |
+------+------+------+
| D(3) | H(4) | E(5) |
+------+------+------+
| F(6) | G(7) | I(8) |
+------+------+------+

This time, simulating (6) moving up to (4), will gives us an incorrect grid.
We want to move E(5) down to (7).

>>> visualModel.items.move(5, 7);

E(5) -> G(7) -> F(6) -> (5)

This gives us

+------+------+------+
| A(0) | B(1) | C(2) |
+------+------+------+
| D(3) | H(4) | F(5) |
+------+------+------+
| G(6) | E(7) | I(8) |
+------+------+------+

Каждый раз, когда мы перемещаем элемент вверх смежную плитку с from до to, все плитки от to + 1 до from будут сдвигаться вправо в порядке заполнить пробел.

Элемент, который мы хотим обменять, смещается на to + 1. Таким образом, мы перемещаемся to - 1 в from. Таким образом, мы получаем visualModel.items.move(to + 1, from); для сдвигающихся плиток, где to на выше from.


Что если моя сетка имеет переменную ширину и высоту?

Это оставлено в качестве упражнения для читателя.

Шучу, все, что тебе нужно сделать, это изменить проверку состояния.

if (from % width < width - 1 && from + 1 === to)    //  check `to` is right of `from`
    // ...
if (from % width > 0 && from - 1 === to)    //  check `to` is left of `from`
    // ...
if (from < (width * height - width) && from + width === to) //  check `to` is below `from`
    // ...
if (from >= width && from - width === to)   //  check `to` is above from
    // ...

И этот должен работать с любой целой шириной и высотой.


Примечания

Анимация для вертикального свопа не такая плавная или переходная, как горизонтальный своп.


Утомительный ответ. ?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...