Solved 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:- 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.
- "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") } } }
-
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 -
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 -
Cool, great news. And thanks for sharing the example! :-)