Reorder Items in a QML ListView via Touch Gestures

The stock QML ListView is pretty handy for displaying simple data structures. While porting To-Do-O to Qt/QML (MeeGo/Harmattan to be more precise) I stumbled across a use case where I want to move / reorder items in a list. In my opinion the most natural way to do this on a touch UI device is to use some drag-and-drop gesture like handling for moving items. In this post I’ll describe how I implemented a simple solution for moving items in a QML ListView via touch interaction.

The idea behind the implementation is that the user selects the item to be moved, keeps the item pressed and uses a second finger to move the item in the list. Fortunately, QML provides the PinchArea which is designed to handle this kind of two finger gesture. Using a ListView along with a PinchArea the implementation part is pretty straight forward. The following listing shows the code:

import QtQuick 1.1
import com.meego 1.0

PageStackWindow {
  id: appWindow
  initialPage: mainPage

  ListModel{
    id: myModel

    ListElement{ number: "1" }
    ListElement{ number: "2" }
    ListElement{ number: "3" }
    ListElement{ number: "4" }
    ListElement{ number: "5" }
    ListElement{ number: "6" }
    ListElement{ number: "7" }
    ListElement{ number: "8" }
  }

  Page{
    id: mainPage

    Rectangle{
      id: rect
      anchors.fill: parent

      PinchArea{
        id: dndArea
        anchors.fill: parent

        property real lastPosition: 0
        property real moveDelta: 40

        onPinchStarted: lastPosition = pinch.startPoint2.y

        onPinchUpdated: {
          var currentPosition = pinch.point2.y

          if(currentPosition === pinch.point1.y)
            return

          if(lastPosition - currentPosition > moveDelta){
            lastPosition = currentPosition
            moveItem(myList.currentIndex - 1)
          }else if (lastPosition - currentPosition < -moveDelta){
            lastPosition = currentPosition
            moveItem(myList.currentIndex + 1)
          }
        }

        function moveItem(targetIndex){
          if(targetIndex >= 0 && targetIndex < myModel.count){
            myModel.move(myList.currentIndex, targetIndex, 1)
          }
        }
      }

      ListView{
        id: myList
        anchors.fill: parent

        model: myModel

        delegate: Text{
          width: parent.width
          text: number
          font.pointSize: 40
          horizontalAlignment: Text.AlignHCenter

          MouseArea{
            anchors.fill: parent
            onClicked: myList.currentIndex = index
          }
        }

        highlight: Rectangle{ color: "lightblue" }
      }
    }
  }
}

If you want to use this code with a custom C++/Qt model based e.g. on QAbstractItemModel you need to add an according “move” method to your model which takes care of moving the item and updating the ListView. Note that this method must be callable from QML. Hence it has to be either a slot or use the Q_INVOKABLE macro.

Additionally, you need to add a “count” property to your model. This can be done by adding “Q_PROPERTY(int count READ rowCount)” to the class definition of your model (before the “public:” part). You should have implemented a “rowCount” method anyhow when inheriting from QAbstractItemModel.

You can download a runnable version of this example for MeeGo/Harmattan via this link.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Advertisement
This entry was posted in Qt/QML, Snippets and tagged , , , , . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.