I need to create a screen where the user will swipe right or left and top or down and on each of these I will present another screen. It would be like a cross where the center and each end is another screen of the system.
I tried using GestureArea, but its only event that worked was onTapAndHold, other events like onSwipe don't run. I later found out that Qt 4.8 has a bug in GestureArea and other events don't work. The problem is that the onTapAndHold event runs on click as well and I just want it to run with the swipe event.
Done!
How I did it:
import QtQuick 1.1
import Qt.labs.gestures 1.0
import "module"
Rectangle {
id: recMainWindow
width: 800
height: 480
color: "#00000000"
property string strActualScreen: "screenCenter"
function funSwipeScreens(swipeArea)
{
if ((!panBottomCenter.running) && (!panCenterBottom.running) &&
(!panCenterLeft.running) && (!panCenterRight.running) &&
(!panCenterTop.running) && (!panLeftCenter.running) &&
(!panRightCenter.running) && (!panTopCenter.running))
{
if (swipeArea == "top")
{
if (strActualScreen == "screenBottom")
{
strActualScreen = "screenCenter";
marLeft.enabled = true;
marRight.enabled = true;
marBottom.enabled = true;
panBottomCenter.start();
}
else if (strActualScreen == "screenCenter")
{
strActualScreen = "screenTop";
marTop.enabled = false;
marLeft.enabled = false;
marRight.enabled = false;
panCenterTop.start();
}
}
else if (swipeArea == "bottom")
{
if (strActualScreen == "screenTop")
{
strActualScreen = "screenCenter";
marTop.enabled = true;
marLeft.enabled = true;
marRight.enabled = true;
panTopCenter.start();
}
else if (strActualScreen == "screenCenter")
{
strActualScreen = "screenBottom";
marLeft.enabled = false;
marRight.enabled = false;
marBottom.enabled = false;
panCenterBottom.start();
}
}
else if (swipeArea == "left")
{
if (strActualScreen == "screenRight")
{
strActualScreen = "screenCenter";
marBottom.enabled = true;
marRight.enabled = true;
marTop.enabled = true;
panRightCenter.start();
}
else if (strActualScreen == "screenCenter")
{
strActualScreen = "screenLeft";
marLeft.enabled = false;
marBottom.enabled = false;
marTop.enabled = false;
panCenterLeft.start();
}
}
else if (swipeArea == "right")
{
if (strActualScreen == "screenLeft")
{
strActualScreen = "screenCenter";
marLeft.enabled = true;
marBottom.enabled = true;
marTop.enabled = true;
panLeftCenter.start();
}
else if (strActualScreen == "screenCenter")
{
strActualScreen = "screenRight";
marBottom.enabled = false;
marRight.enabled = false;
marTop.enabled = false;
panCenterRight.start();
}
}
}
}
Loader {
id: loaCenter
x: 0
y: 0
source: "qrc:/qml/centerScreen"
}
Loader {
id: loaTop
x: 0
y: -480
source: "qrc:/qml/topScreen"
}
Loader {
id: loaBottom
x: 0
y: 480
source: "qrc:/qml/bottomScreen"
}
Loader {
id: loaLeft
x: -800
y: 0
source: "qrc:/qml/leftScreen"
}
Loader {
id: loaRight
x: 800
y: 0
source: "qrc:/qml/rightScreen"
}
GestureArea {
id: marLeft
x: 0
y: 100
width: 100
height: 280
focus: true
onTapAndHold: {
funSwipeScreens("left");
}
}
GestureArea {
id: marRight
x: 700
y: 100
width: 100
height: 280
focus: true
onTapAndHold: {
funSwipeScreens("right");
}
}
GestureArea {
id: marTop
x: 100
y: 0
width: 600
height: 100
focus: true
onTapAndHold: {
funSwipeScreens("top");
}
}
GestureArea {
id: marBottom
x: 100
y: 380
width: 600
height: 100
focus: true
onTapAndHold: {
funSwipeScreens("bottom");
}
}
// TOP ANIMATIONS
ParallelAnimation {
id: panCenterTop
NumberAnimation { target: loaCenter; property: "y"; from: 0; to: 480; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaTop; property: "y"; from: -480; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
ParallelAnimation {
id: panTopCenter
NumberAnimation { target: loaTop; property: "y"; from: 0; to: -480; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaCenter; property: "y"; from: 480; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
// BOTTOM ANIMATIONS
ParallelAnimation {
id: panCenterBottom
NumberAnimation { target: loaCenter; property: "y"; from: 0; to: -480; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaBottom; property: "y"; from: 480; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
ParallelAnimation {
id: panBottomCenter
NumberAnimation { target: loaBottom; property: "y"; from: 0; to: 480; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaCenter; property: "y"; from: -480; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
// LEFT ANIMATIONS
ParallelAnimation {
id: panCenterLeft
NumberAnimation { target: loaCenter; property: "x"; from: 0; to: 800; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaLeft; property: "x"; from: -800; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
ParallelAnimation {
id: panLeftCenter
NumberAnimation { target: loaLeft; property: "x"; from: 0; to: -800; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaCenter; property: "x"; from: 800; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
// RIGHT ANIMATIONS
ParallelAnimation {
id: panCenterRight
NumberAnimation { target: loaCenter; property: "x"; from: 0; to: -800; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaRight; property: "x"; from: 800; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
ParallelAnimation {
id: panRightCenter
NumberAnimation { target: loaRight; property: "x"; from: 0; to: 800; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: loaCenter; property: "x"; from: -800; to: 0; duration: 250; easing.type: Easing.InOutQuad }
}
}
EDIT:
Well ... after checking the wrong behaviors that the GestureArea I used above caused, because they respond to the click on the onTapAndHold event and I just wanted the answer to the swipe event, I decided to remove them and make a swipe simulation using MouseArea. .
The swipe action has greatly improved and the click event issue presented by GestureArea has been resolved but has caused the MouseAreas overlay issue.
This did not exist when using GestureArea as it did not prevent a click on a MouseArea positioned below it and MouseAreas overlap prevents it.
Anyway, the solution for the GestureArea swipe event I describe below:
MouseArea {
anchors.fill: parent
preventStealing: true
property real reaSpeed: 0.0 // calculates drag speed for swipe to run
property int intStartY: 0 // starting point pressed by the user's finger
property bool booTracing: false //controls the analyzed drag length
onPressed: {
intStartY = mouse.y;
reaSpeed = 0;
booTracing = true;
}
onPositionChanged: {
if (!booTracing)
{
return;
}
reaSpeed = (intStartY - mouse.y) / 2.0;
if ((reaSpeed > 50) && (mouse.y < (parent.height * 0.2)))
{
booTracing = false;
if (booIsShowMenu)
{
sigStartHideMenu();
}
funSwipeScreens("bottom");
}
}
}
I am checking a way to handle MouseAreas overlay.
Related
I would like to create two qml list views that can perform two functions:
Drag and drop items within one list to change order of items
Drag and drop items cross lists. Items will remove from one list and add to another
Based on the Drag and Drop example in Qt Documentation, I decided to create two list views that accessing the same list model. Each item in the list model has a key as a bool value. Using DelegateModel with a filter, I can display items with key equals to true in one list and with false in the second list. When dragging an item from first list to second list, the key value will be flipped.
Now, I am facing two problems:
onDropped is not executed
DelegateModel is not refreshed after key value changed
Can anybody help me solve these problems or maybe have a better solution?
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQml.Models 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 300
height: 400
title: qsTr("List View Test")
property bool dropAreaOneEntered: false
property bool dropAreaTwoEntered: false
property int listNumer: 0
Rectangle {
id: root
width: parent.width
height: parent.height
ListView {
id: displayListOne
width: 100; height: parent.height
model: displayListOneModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
ListView {
id: displayListTwo
width: 100; height: parent.height
anchors.right: parent.right
model: displayListTwoModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
// ..........................................................
ListModel {
id: itemsListModel
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
}
// ..........................................................
DelegateModel {
id: displayListOneModel
model: itemsListModel
delegate: MouseArea {
id: mouseOneArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemOneBlock : undefined
drag.axis: Drag.XAndYAxis
property var draggedItem: null
onPressAndHold: {
held = true
listNumer = 1
displayListOne.dragItemIndex = index
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemOneBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseOneArea.held ? 0.8 : 1.0
Drag.active: mouseOneArea.held
Drag.source: mouseOneArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseOneArea.held
ParentChange { target: itemOneBlock; parent: root }
AnchorChanges {
target: itemOneBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listOneDropArea
anchors.fill: parent
property bool flag: false
onExited: dropAreaOneEntered = false
onEntered: {
dropAreaOneEntered = true
if (dropAreaOneEntered & ! dropAreaTwoEntered){
if (listNumer == 1){
displayListOneModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseOneArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("onDropped has been executed!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listOne"
}
]
filterOnGroup: "listOne"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != true)) {
items.removeGroups(i,1,"listOne")
}
}
}
}
DelegateModel {
id: displayListTwoModel
model: itemsListModel
delegate: MouseArea {
id: mouseTwoArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemTwoBlock : undefined
drag.axis: Drag.XAndYAxis
onPressAndHold: {
held = true
listNumer = 2
displayListTwo.dragItemIndex = index
console.log("current index is "+index)
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemTwoBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseTwoArea.held ? 0.8 : 1.0
Drag.active: mouseTwoArea.held
Drag.source: mouseTwoArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseTwoArea.held
ParentChange { target: itemTwoBlock; parent: root }
AnchorChanges {
target: itemTwoBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listTwoDropArea
anchors.fill: parent
onExited: dropAreaTwoEntered = false
onEntered: {
dropAreaTwoEntered = true
if (dropAreaTwoEntered & ! dropAreaOneEntered){
if (listNumer == 2){
displayListTwoModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseTwoArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("Flip the key value here!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listTwo"
}
]
filterOnGroup: "listTwo"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != false)) {
items.removeGroups(i,1,"listTwo")
}
}
}
}
}
}
I have a PathView with Rectangle as the delegate. I change the PathView.path to another path and I want to play animation of moving PathView items to their new positions. Something like this:
PathView {
id: pathView
delegate: Rectangle {
Behavior on x {
NumberAnimation {
duration: 5000
}
}
}
path: path1
}
But it doesn't work. Is it possible to do somehow?
Unfortunately, changing the "PathView.path" to another "Path" will destroy and recreate delegates just like changing a model.
A workaround can be done with "states". You create multiple blank PathLine and set its x and y values from states. You give the animation from "transitions"
This Sample code will initially have 3 data items in the model. In between the animation, it reloads the model with 5 data and still works in a continuous fashion without any glitch to the animation.
PathView {
id: pathView
anchors.fill: parent
anchors.margins: 100
model: 3
path: Path {
id: pathLines
PathLine { id: pl1 }
PathLine { id: pl2 }
PathLine { id: pl3 }
PathLine { id: pl4 }
PathLine { id: pl5 }
}
state: 'zigzag'
states: [
State {
name: "zigzag"
PropertyChanges { target: pathLines; startX: 0; startY: 100; }
PropertyChanges { target: pl1; x: 200; y: 300; }
PropertyChanges { target: pl2; x: 500; y: 100; }
PropertyChanges { target: pl3; x: 600; y: 300; }
PropertyChanges { target: pl4; x: 800; y: 100; }
PropertyChanges { target: pl5; x: 1000; y: 300; }
},
State {
name: "close"
PropertyChanges { target: pathLines; startX: pathView.width/2; startY: pathView.height/2; }
PropertyChanges { target: pl1; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl2; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl3; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl4; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl5; x: (pathView.width/2) + 1; y: pathView.height/2; } // Note: "+1" to fix disappearance bug
},
State {
name: "open"
PropertyChanges { target: pathLines; startX: pathView.width/2; startY: pathView.height/4; }
PropertyChanges { target: pl1; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl2; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl3; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl4; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl5; x: pathView.width/2 + 1; y: pathView.height/4; } // Note: "+1" to fix disappearance bug
},
State {
name: "triangle"
PropertyChanges { target: pathLines; startX: 200; startY: 500; }
PropertyChanges { target: pl1; x: 400; y: 700; }
PropertyChanges { target: pl2; x: 600; y: 500; }
PropertyChanges { target: pl3; x: 350; y: 500; }
PropertyChanges { target: pl4; x: 300; y: 500; }
PropertyChanges { target: pl5; x: 250; y: 500; }
}
]
transitions: [
Transition {
to: 'zigzag'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; onFinished: pathView.state = 'triangle' }
},
Transition {
to: 'triangle'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
},
Transition {
to: 'close'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
onRunningChanged: {
if (!running) {
pathView.model = 5
pathView.state = 'open'
}
}
},
Transition {
from: "close"
to: 'open'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
onRunningChanged: !running ? pathView.state = 'triangle' : ''
}
]
delegate: Rectangle {
width: 20
height: 20
radius: 20
color: 'green'
}
}
Controls.Button {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: 'Start Animation'
onClicked: pathView.state = 'close'
}
The state names like "zigzag" and "triangle" does not resemble its real shape, just some random shape for test purposes.
Example of Qml - Flipable:
import QtQuick 2.0
Flipable {
id: flipable
width: 240
height: 240
property bool flipped: false
front: Rectangle { width: 200; height: 200; color: 'red'; anchors.centerIn: parent }
back: Rectangle { width: 200; height: 200; color: 'blue'; anchors.centerIn: parent }
transform: Rotation {
id: rotation
origin.x: flipable.width/2
origin.y: flipable.height/2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges { target: rotation; angle: 180 }
when: flipable.flipped
}
transitions: Transition {
NumberAnimation { target: rotation; property: "angle"; duration: 4000 }
}
MouseArea {
anchors.fill: parent
onClicked: flipable.flipped = !flipable.flipped
}
}
This example is running good but, if I use this code, Flipable doesn't run:
MouseArea {
anchors.fill: parent
onClicked: {
flipable.flipped = true;
flipable.flipped = false;
}
}
I think the animation conflicts when I first make the flipped property is true then false. Whereas I want flipable open and then close.
The problem is that you set the property flipped back to false before the flip animation even started.
If you want the full open/close animation, you have to wait for the "open" animation to finish before starting the "close" animation:
transitions: Transition {
id: transition
onRunningChanged: {
if (!running && flipable.flipped) {
flipable.flipped = false;
}
}
NumberAnimation { target: rotation; property: "angle"; duration: 4000 }
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!transition.running) {
flipable.flipped = true;
}
}
}
I have created a path view with a delegate and model. Is it possible to change current index of the PathView via mouse wheel instead of mouse click drag?
Following is my PathView code:
PathView {
id: pathView;
anchors.fill: parent;
model: sampleModel;
delegate: delegate;
path: Path {
startX: 45; startY: 100
PathAttribute { name: "iconScale"; value: 0.3 }
PathAttribute { name: "iconOpacity"; value: 0.1 }
PathQuad { x: 45; y: 300; controlX: 45; controlY: 200 }
PathAttribute { name: "iconScale"; value: 1 }
PathAttribute { name: "iconOpacity"; value: 1 }
PathQuad { x: 45; y: 500; controlX: 45; controlY: 400 }
}
}
I had the the same problem. Here is my solution:
Every time a wheel event occurs, I in/decrement a "wheelIndex" and set the view.currentIndex to this value.
To reset the wheelIndex after the wheel usage, I use a timer:
PathView {
id: view
anchors.fill: parent
focus: true
model: monthModel
property int wheelIndex:- 1;
currentIndex: 0;
MouseArea {
anchors.fill: parent
preventStealing: true;
propagateComposedEvents: true
Timer {
id:wheelTimer
interval: 200;
running: false;
repeat: false
onTriggered: {
view.wheelIndex = -1;
}
}
onWheel: {
wheelTimer.start();
if (view.wheelIndex === -1) {
view.wheelIndex = view.currentIndex;
}
if (wheel.angleDelta.y > 0) {
view.wheelIndex++
if (view.wheelIndex > view.model.count) {
view.wheelIndex = 0;
}
view.currentIndex = view.wheelIndex;
} else {
view.wheelIndex--;
if (view.wheelIndex < 0) {
view.wheelIndex = view.model.count - 1;
}
view.currentIndex = view.wheelIndex;
}
}
}
}
by adding a MouseArea as child to your PathView with anchors.fill:parent and implementing the onWheel() event as follow :
PathView{
id:view
......
MouseArea{
id:mouse
anchors.fill: parent
onWheel:
{
if( wheel.angleDelta.y > 0 ) view.incrementCurrentIndex();
else view.decrementCurrentIndex();
}
}
you'll get it working with both ,mouse drag and mouse scroll (wheel).For more info look at QtQuick 5.0: WheelEvent
I have a simple QML script with a four states which you go through a cycle, say the states are one, two, three, four.
The transition from one to two and from three to four is the same.
Is there a simple way to define this without duplicating the code for the transition?
This does not work:
transitions: Transition {
from: "one"; to: "two";
from: "three"; to: "four";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}
From the documentation, it seems there is no way of specifying this but
transitions: [ Transition {
from: "one"; to: "two";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}, Transition {
from: "three"; to: "four";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}]
I had a similar issue on a very simple app, it is not a pretty way to do it but the only way I found is like this:
transitions:
Transition {
from: "two"; to: "three";
//whatever animation you have for this
},
//and obviously any other transitions you may have
//any transition you don't specify will do this:
Transition {
from: "*"; to: "*";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}
You can reuse an animation by giving it an id.
import QtQuick 2.0
import QtQuick.Controls 1.0
Rectangle {
id: root
width: 400; height: width
Column {
spacing: 20
Repeater {
model: ["one", "two", "three", "four"]
Button {
text: modelData
onClicked: rect.state = modelData
}
}
}
Rectangle {
id: rect
x: 200; y: 0
width: 60; height: width
color: "red"
state: "one"
states: [
State { name: "one" },
State { name: "two" },
State { name: "three" },
State { name: "four" }
]
NumberAnimation { id: na1; target: rect; property: "y"; to: 200 }
NumberAnimation { id: na2; target: rect; property: "y"; to: 0 }
transitions: [
Transition { from: "one"; to: "two"; animations: na1 },
Transition { from: "two"; to: "three"; animations: na2 },
Transition { from: "three"; to: "four"; animations: na1 },
Transition { from: "four"; to: "one"; animations: na2 }
]
}
}
I'm wondering if you know the concept of default property. In your code, ParallelAnimation is assigned to the default property animations of the Transition, and you can explicitly assign an animation(by it's id) to this default property.