ListView with MouseArea under to cach swipe gesture



  • Hi all,
    I have ListView which uses delegate for each item, this "AndroidDelegate" should get mouseEvent clicked, pressed, released.
    I also need MouseArea "under" ListView to catch swipe gesture (to exit current page).
    I was testing MouseArea inside ListView, and outside, also changing Z value.
    In the code below it almost works, there are two problems:

    1. First time app is started and first press and drag on ListView - ListView does not scroll items, instead "clicked" is fired from delegate on which I stop dragging, all subsequent press and drag works fine, ListView is scrolled.
    2. "AndroidDelegate" does not receive "pressed" signal, and I would like to change its color when pressed

    If I get rid of additional MouseArea, ListView and delegate works fine, but I don't have the swipe signal.
    How to achieve working ListView with delegates and swipe signal (to change the page)

    ListView {
        id: catalogListView
        anchors.top: phraseInput.bottom
        anchors.bottom: parent.bottom
        anchors.topMargin: Style.catalogFontSize
        width: parent.width
        clip: true
        z: 5
        model: CatalogModel
    
    
        delegate: AndroidDelegate {
            catalog_id: id_role
            text: name_role
            isDir: (dir_role ? 1 : 0)
            back_visible: back_visible_role
            onClicked: {
                if(dir_role) {
                    catalogDirClicked(catalog_id)
                } else {
                    catalogItemClicked(catalog_id,1)
                }
            }
        }
        MouseArea {
            id: catalogMouseArea
            z:4
            anchors.fill: parent
            preventStealing: false
            propagateComposedEvents: true
            property real velocity: 0.0
            property int xStart: 0
            property int xPrev: 0
            property bool tracing: false
            property int swiping
            onPressed: {
                xStart = mouse.x
                xPrev = mouse.x
                velocity = 0
                tracing = true
                swiping = false
                console.log("catalog mouse pressed");
            }
            onPositionChanged: {
                if ( !tracing ) return
                var currVel = (mouse.x-xPrev)
                velocity = (velocity + currVel)/2.0
                xPrev = mouse.x
                console.log("vel:"+velocity)
                if ( velocity < -15 ) {
                    tracing = false
                    swiping=true
                    console.log("left swipe detected");
                }
                if ( velocity > 15 ) {
                    tracing = false
                    swiping=true
                    console.log("right swipe detected")
                    catalogDirBack()
                }
            }
            onReleased: {
                tracing = false
            }
        }
    }
    //AndroidDelegate.qml
    Rectangle {
        id: root
        width: parent.width
        height: Style.catalogFontSize*2
    
        property alias text: textitem.text
        property int catalog_id
        property int back_visible;
        signal clicked
    
        Rectangle {
            anchors.fill: parent
            color: "black"
            visible: mouse.pressed
        }
    
        Text {
            id: textitem
            color: "black"
            font.pixelSize: Style.catalogFontSize
            fontSizeMode: Text.Fit
            verticalAlignment: Text.AlignVCenter
    
            anchors.left: parent.left
            anchors.leftMargin: backImageItem.width
            width: (isDir ? parent.width-100 : parent.width-20)
            height: parent.height
            elide: Text.ElideRight
        }
        MouseArea {
            id: mouse
            anchors.fill: parent
            property int moved: 0
            property int last_x: 0
            onClicked: {
                console.log("delegate clicked");
                if(!catalogMouseArea.swiping) {
                    catalogListView.in_x_from= catalogRect.width
                    catalogListView.in_x_to= 0
                    catalogListView.out_x_from= 0
                    catalogListView.out_x_to= -catalogRect.width
                    catalogListView.direction=1
                    root.clicked()
                }
            }
            onPressed: {
                console.log("delegate pressed");
            }
            onPressAndHold: {
                console.log("delegate pressAndHold")
            }
            onReleased: {
                console.log("delegate released");
            }
            onPositionChanged: {
                console.log("delegate positionChanged")
            }
        }
    }
    

  • Moderators

    Put your ListView inside a SwipeView.



  • @sierdzio Thanks for answer.

    Well, I can't do that. My ListView is like a Tree, for instance a view of file system. My model is QAbstractListModel exposed from C++. At first it shows all dirs from path "/". When delegate is clicked, I send signal to model which reloads its data and then shows items from "/selectedDir" directory, next delegate clicked - "/selectedDir/nextDir" and so on.
    It's hard to believe that I need to implement "back" button to navigate ListView back, I would like to do that by swiping.
    In widgets application based on QGraphicsView I would catch that "swipe" gesture from underlying QGraphicsScene.

    here z order for mouseArea doesn't work in ListView someone is doing similar thing but with clicked event which I suppose is propagated because this is composedEvent, but press, positionChanged, and release are not.

    Best Regards
    Marek


  • Moderators

    Oh, I see. That indeed complicates matters. It is quite ridiculous that QtQuick does not support custom gestures well... even though it was created specifically for mobile, touch-enabled devices.

    What about using SwipeDelegate, then? The example in docs provides a SwipeDelegate for ListView. Looks like you could use the swipe.left property to implement your use case.

    Note: I've never used SwipeDelegate myself, so it is a kind of shot in the dark ;-)



  • @sierdzio I will give it a try, just to check how it works. However I could implement swiping gesture in my custom delegate, no problem, but what with such a case: ListView spans across the phone screen and it has only one item at the top (in currentDir there is only one file). I need to perform swipe gesture on this element only, I mean, with SwipeDelegate or without, swipe won't work on an empty part of ListView.
    Hmm... maybe I can check whether contentHeight is less than screenHeight or parentHeight and then create last delegate that is high enough to cover empty part of ListView?



  • Excellent ;) it works!
    The only drawback is that I can't swipe across items, but I can live with that.
    My intention was also that the last "stretched" item would look like the other items, in other words stretching is invisible., and the item is resizable when I set different fontSize.
    Working example below (If one would like to test it, one needs to provide Model, maybe static model defined in QML would do)

    import QtQuick 2.9
    import QtGraphicalEffects 1.0
    import "style"
    
    Rectangle {
        id: catalogRect
        width: parent.width
    
        ListView {
            id: catalogTestListView
            anchors.fill: parent
            width: parent.width
            clip: true
    
            property int direction: 1
            property int in_x_from: catalogRect.width
            property int in_x_to: 0
            property int out_x_from: 0
            property int out_x_to: -catalogRect.width
            model: CatalogModel
    
    
            delegate: TestDelegate {
                catalog_id: id_role
                title: name_role
                latin: name_role
                isDir: (dir_role ? 1 : 0)
                back_visible: back_visible_role
                last: (index==(CatalogModel.rowCount()-1) ? 1 : 0)
                onClicked: {
                    if(dir_role) {
                        console.log("dir catalog_id:"+catalog_id+" clicked");
                        catalogDirClicked(catalog_id)
                    } else {
                        console.log("item catalog_id:"+catalog_id+" clicked");
                        catalogItemClicked(catalog_id,1)
                    }
                }
            }
            add: Transition {
                id: addTrans
                ScriptAction {
                    script: {
                        if(catalogTestListView.direction==1) {
                            addTrans.ViewTransition.item.x=catalogRect.width
                        }
                        if(catalogTestListView.direction==-1) {
                            addTrans.ViewTransition.item.x=-catalogRect.width
                        }
                        console.log("addTrans direction:"+catalogTestListView.direction);
                    }
                }
                SequentialAnimation {
                    PauseAnimation {
                        duration: (addTrans.ViewTransition.index) * 50
                    }
                    NumberAnimation {
                        properties: "x"; from: catalogTestListView.in_x_from; to: catalogTestListView.in_x_to; duration: 200
                    }
                }
            }
    
            remove: Transition {
                id: removeTrans
                SequentialAnimation {
                    PauseAnimation {
                        duration: (removeTrans.ViewTransition.index) * 50
                    }
                    NumberAnimation {
                        properties: "x"; from: catalogTestListView.out_x_from; to: catalogTestListView.out_x_to; duration: 200
                    }
                }
            }
        }
    }
    //TestDelegate.qml
    import QtQuick 2.9
    import QtGraphicalEffects 1.0
    import "style"
    
    Rectangle {
        id: root
        width: parent.width
    
        property alias title: titleItem.text
        property alias isDir: imageItem.visible
        property int catalog_id
        property int back_visible;
        property string latin
        property int last
        property int defaultHeight: Style.catalogFontSize*2.5
        signal clicked
    
        height: {
            if(last && catalogRect.height>((CatalogModel.rowCount()-1)*defaultHeight)) {
                if(catalogRect.height-((CatalogModel.rowCount()-1)*defaultHeight)<defaultHeight)
                    defaultHeight
                else
                    catalogRect.height-((CatalogModel.rowCount()-1)*defaultHeight)
            }
            else {
                defaultHeight
            }
        }
    
        Rectangle {
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            height: defaultHeight
            anchors.leftMargin: 15
            anchors.rightMargin: 15
            anchors.topMargin: 5
            anchors.bottomMargin: 5
            radius:15
            color: "#c7ffc3"
            visible: ((mouse.pressed && mouse.mouseY<defaultHeight) ? true : false)
        }
    
        Text {
            id: titleItem
            color: "#525252"
            font.pixelSize: Style.catalogFontSize
            fontSizeMode: Text.Fit
            verticalAlignment: Text.AlignBottom
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.leftMargin: backImageItem.width
            width: (isDir ? parent.width-imageItem.width*2 : parent.width-20)
            height: defaultHeight*0.6666
            elide: Text.ElideRight
    
            style: Text.Raised; styleColor: "gray"
        }
        Text {
            id: latinItem
            color: "gray"
            text:"("+latin+")"
            font.pixelSize: 2*Style.catalogFontSize/3
            font.italic: true
            fontSizeMode: Text.Fit
            verticalAlignment: Text.AlignTop
            anchors.top: titleItem.bottom
            anchors.left: titleItem.left
            anchors.leftMargin: backImageItem.width
            width: parent.width
            height: defaultHeight/2
            elide: Text.ElideRight
            visible: !isDir
        }
    
    
        Rectangle {
            id: bottomLine
            anchors.top: parent.top
            anchors.topMargin: defaultHeight
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.leftMargin: 15
            anchors.rightMargin: 15
            height: 1
            color: "#424246"
            layer.enabled: true
            layer.effect: DropShadow {
                verticalOffset: -1
                horizontalOffset: 0
                samples: 10
                spread: 0.5
            }
        }
    
        Image {
            id: imageItem
            height: defaultHeight*0.5
            anchors.right: parent.right
            anchors.top: parent.top
            anchors.topMargin: (defaultHeight-imageItem.height)/2
            source: "../images/navigation_next_item.png"
            fillMode: Image.PreserveAspectFit
            smooth: true
        }
        Image {
            id: backImageItem
            height: defaultHeight*0.5
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.topMargin: (defaultHeight-imageItem.height)/2
            source: "../images/navigation_prev_item.png"
            visible: parent.back_visible
            fillMode: Image.PreserveAspectFit
            smooth: true
        }
    
        MouseArea {
            id: mouse
            anchors.fill: parent
            property real velocity: 0.0
            property int xStart: 0
            property int xPrev: 0
            property bool tracing: false
            property bool swiping: false
            onPressed: {
                xStart = mouse.x
                xPrev = mouse.x
                velocity = 0
                tracing = true
                swiping = false
                console.log("delegate item pressed");
            }
            onPositionChanged: {
                if ( !tracing ) return
                swiping = true
                var currVel = (mouse.x-xPrev)
                velocity = (velocity + currVel)/2.0
                xPrev = mouse.x
                console.log("vel:"+velocity)
                if ( velocity < -5 ) {
                    tracing = false
                    console.log("left swipe detected mouse.y:"+mouse.y+" defaultHeight:"+defaultHeight);
                    if(mouse.y<defaultHeight) {
                        changeTransitionDirection(1)
                        root.clicked()
                    }
                }
                if ( velocity > 5 ) {
                    tracing = false
                    console.log("right swipe detected")
                    changeTransitionDirection(-1)
                    catalogDirBack()
                }
            }
            onClicked: {
                console.log("delegate clicked");
                if(!swiping) {
                    changeTransitionDirection(1)
                    root.clicked()
                }
            }
            onReleased: {
                tracing=false
                console.log("delegate released");
            }
            function changeTransitionDirection(direction) {
                if(direction==1) {
                    catalogTestListView.in_x_from= catalogRect.width
                    catalogTestListView.in_x_to= 0
                    catalogTestListView.out_x_from= 0
                    catalogTestListView.out_x_to= -catalogRect.width
                    catalogTestListView.direction=1
                }
                else {
                    catalogTestListView.in_x_from=-catalogRect.width;
                    catalogTestListView.in_x_to=0;
                    catalogTestListView.out_x_from=0;
                    catalogTestListView.out_x_to=catalogRect.width;
                    catalogTestListView.direction=-1;
                }
            }
        }
    }
    

    Best Regards
    Marek


  • Moderators

    Cool, great news. And thanks for sharing the example! :-)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.