Swipe gestures with QML Stackview
-
So QML StackView provides a way to navigate back by usin the
pop()
function. I am looking for a way to go the previous page by simply swiping to the right with UI elements following the fingers of the user (kind of like aSwipeView
). How can I achieve this? I have looked around and all I found was this https://github.com/alejoasotelo/SwipePageStackWindow which is an old project that does a similar thing with the oldPageStack
.
I think theSwipeView
is not really an alternative since my apps would contain at least 20 layers of "pages" so I would manually create and destroy the items to avoid performance issues. I thought about using aMouseArea
and implement the gesture myself, but what I want the previous "page" to slide from the left as the user is swiping away the current item to the right. I don't understand how can get the previous "page" to do that. DoesStackView
provides a way to do that? -
@raven-worx said in Swipe gestures with QML Stackview:
Thats the nature of a stack view
Yeah I think that is the default behaviour, but I think I found a workaround. In the documentation, it is mentioned:
By default, StackView shows incoming items when the enter transition begins, and hides outgoing items when the exit transition ends. Setting this property explicitly allows the default behavior to be overridden, making it possible to keep items that are below the top-most item visible.
Then I was able to use that property to make the animation correctly. I believe that Qt Quick should have a default behaviour (it is such a common UI pattern nowadays) to achieve this as it is trivial to do and already built in the native development tools for Android and iOS.
Anyway here is the working code:StackView{ id: stack width: window.width height: window.height initialItem: initialComp pushEnter: Transition { id: pushEnter ParallelAnimation { NumberAnimation { property: "x"; from: window.width; to: 0; duration: 400; easing.type: Easing.OutCubic } NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutCubic } } } pushExit: Transition { id: pushExit PropertyAction { property: "x"; value: pushExit.ViewTransition.item.pos } PropertyAction { property: "y"; value: pushExit.ViewTransition.item.pos } } popEnter: Transition { id: popEnter PropertyAction { property: "x"; value: popEnter.ViewTransition.item.pos } PropertyAction { property: "y"; value: popEnter.ViewTransition.item.pos } } } Component{ id: initialComp Rectangle{ color: "purple" width: window.width height: window.height StackView.visible: true MouseArea{ anchors.fill: parent onClicked: { stack.push(pushComponent) } } } } Component{ id: pushComponent SwipeView{ id: swipeView currentIndex: 1 Connections{ target: swipeView.contentItem onMovementEnded:{ if(swipeView.currentIndex == 0) { stack.pop() console.log("popped") } } } Rectangle{ width: window.width height: window.height color: "transparent" } Rectangle{ width: window.width color: "green" } } }
Why do you expect performance issues?
Well I supposed I could make an implementation that is just as fast as the default
StackView
, but the documentation mentions:It is generally not advisable to add excessive amounts of pages to a SwipeView.
I would also have to implement quite a lot of the functionality from
StackView
(like animations), so I am quite happy with this "hack". -
@daljit97
you can use a listview. but anyway if you would "slide" to the last page in a stack view you would also have 20 items in the memory.Alternatively you could lay a swipe view over the stack view and make sure you only have 2 items in the stack view by doing the push/pop calls yourself appropriately.
-
Alternatively you could lay a swipe view over the stack view and make sure you only have 2 items in the stack view by doing the push/pop calls yourself appropriately.
Could you explain that a little bit more?
-
So this is what I could come up with. I created a
SwipeView
which has an empty item and the actual content (left and right respectively), then when the StackView "pushes" to thisSwipeView
, the user is able to swipe to go back. The only problem is that the content "before" does not become visible untilStackView::pop()
doesn't get called. Is there anyway around this? Here is my code:StackView{ id: stack anchors.fill: parent initialItem: initialComp } Component{ id: initialComp Rectangle{ color: "purple" anchors.fill: parent MouseArea{ anchors.fill: parent onClicked: { stack.push(pushComponent) } } } } Component{ id: pushComponent SwipeView{ id: swipeView currentIndex: 1 Connections{ target: swipeView.contentItem onContentXChanged: { swipeView.opacity = swipeView.contentItem.contentX/swipeView.contentItem.contentWidth } onMovementEnded:{ if(swipeView.currentIndex == 0) { stack.pop() } } } Rectangle{ width: window.width height: window.height color: "transparent" onXChanged: console.log("x changed") } Rectangle{ width: window.width color: "green" } // onCurrentIndexChanged: if(currentIndex == 0) stack.pop() } }
-
The only problem is that the content "before" does not become visible until StackView::pop() doesn't get called. Is there anyway around this?
Thats the nature of a stack view
@daljit97 said in Swipe gestures with QML Stackview:
I think the SwipeView is not really an alternative since my apps would contain at least 20 layers of "pages" so I would manually create and destroy the items to avoid performance issues
Why do you expect performance issues?
-
@raven-worx said in Swipe gestures with QML Stackview:
Thats the nature of a stack view
Yeah I think that is the default behaviour, but I think I found a workaround. In the documentation, it is mentioned:
By default, StackView shows incoming items when the enter transition begins, and hides outgoing items when the exit transition ends. Setting this property explicitly allows the default behavior to be overridden, making it possible to keep items that are below the top-most item visible.
Then I was able to use that property to make the animation correctly. I believe that Qt Quick should have a default behaviour (it is such a common UI pattern nowadays) to achieve this as it is trivial to do and already built in the native development tools for Android and iOS.
Anyway here is the working code:StackView{ id: stack width: window.width height: window.height initialItem: initialComp pushEnter: Transition { id: pushEnter ParallelAnimation { NumberAnimation { property: "x"; from: window.width; to: 0; duration: 400; easing.type: Easing.OutCubic } NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutCubic } } } pushExit: Transition { id: pushExit PropertyAction { property: "x"; value: pushExit.ViewTransition.item.pos } PropertyAction { property: "y"; value: pushExit.ViewTransition.item.pos } } popEnter: Transition { id: popEnter PropertyAction { property: "x"; value: popEnter.ViewTransition.item.pos } PropertyAction { property: "y"; value: popEnter.ViewTransition.item.pos } } } Component{ id: initialComp Rectangle{ color: "purple" width: window.width height: window.height StackView.visible: true MouseArea{ anchors.fill: parent onClicked: { stack.push(pushComponent) } } } } Component{ id: pushComponent SwipeView{ id: swipeView currentIndex: 1 Connections{ target: swipeView.contentItem onMovementEnded:{ if(swipeView.currentIndex == 0) { stack.pop() console.log("popped") } } } Rectangle{ width: window.width height: window.height color: "transparent" } Rectangle{ width: window.width color: "green" } } }
Why do you expect performance issues?
Well I supposed I could make an implementation that is just as fast as the default
StackView
, but the documentation mentions:It is generally not advisable to add excessive amounts of pages to a SwipeView.
I would also have to implement quite a lot of the functionality from
StackView
(like animations), so I am quite happy with this "hack". -
@daljit97 said in Swipe gestures with QML Stackview:
I believe that Qt Quick should have a default behaviour (and such a common UI pattern nowadays)
it has... but you just dont like it... like you already mentioned in your first post
@daljit97 said in Swipe gestures with QML Stackview:
Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:
It is generally not advisable to add excessive amounts of pages to a SwipeView.
20 pages are not "excessive", unless they do not show some overloaded pages with tens to hundreds(?) items each.
But you could have simply tried it and check if there are any performance considerations at all
-
@raven-worx said in Swipe gestures with QML Stackview:
@daljit97 said in Swipe gestures with QML Stackview:
I believe that Qt Quick should have a default behaviour (and such a common UI pattern nowadays)
it has... but you just dont like it... like you already mentioned in your first post
@daljit97 said in Swipe gestures with QML Stackview:
Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:
It is generally not advisable to add excessive amounts of pages to a SwipeView.
20 pages are not "excessive", unless they do not show some overloaded pages with tens to hundreds(?) items each.
But you could have simply tried it and check if there are any performance considerations at all
No, the default behaviour needs to be changed or an option needs to be added because on mobile platforms the use of buttons to go back is cumbersome (it is acceptable for Android, but for iOS a gesture based implementation is definitely needed). There is a big need for Qt developers to implement this and I am sure that many people find it odd that Qt hasn't got this. Few people have already asked for something similar to what I asked, for example look here:
https://forum.qt.io/topic/72499/ios-app-in-qml-how-to-navigate-backward-through-stackview-via-swipe-gestures
https://forum.qt.io/topic/56296/stackview-pop-a-page-by-swipingAs for
SwipeView
, I haven't gone through that route because I have someWebView
in some of the pages and thought that could affect performance. I suppose you are right that I could have tested that, but I also needed some functionalities provided byStackView
which I would have had to reimplement.