【问题标题】:Swap elements in a GridView在 GridView 中交换元素
【发布时间】:2018-11-15 23:29:33
【问题描述】:

我正在编写一个 QML 程序,它本质上是一个 4x4 GridView,其中填充了编号的矩形。我希望能够:

  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)}
            }
        }
    }
}

【问题讨论】:

    标签: android qt gridview drag-and-drop qml


    【解决方案1】:

    我尝试了一些东西,但仍然有一些错误。希望对您有所帮助。

    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);
                }
            }
        }
    }
    }
    

    【讨论】:

      【解决方案2】:

      这是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;
          }
      }
      

      实际的思考过程变得相当数学化。但它就在这里。

      如何检查邻接关系?

      您可能可以进行谷歌搜索并轻松找到一些东西。但我会一一说明条件。

      //  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) -&gt; 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移动到tofrom + 1to之间的所有图块都会向左移动为了填补空白。

      我们要交换的项目变为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 + 1from之间的所有图块都会向右移动为了填补空白。

      我们要交换的项目变为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
          // ...
      

      而且这个应该能够处理任何整数宽度和高度。


      注意事项

      垂直交换的动画不像水平交换那样平滑或过渡。


      乏味的回答。 ?

      【讨论】:

      • 多么完整的答案!非常感谢!
      猜你喜欢
      • 1970-01-01
      • 2012-09-16
      • 2011-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 2011-02-19
      • 1970-01-01
      相关资源
      最近更新 更多