3

I decided to rewrite this question. I tried to make it clear and short but it didn't work out.

What I am trying to achieve is an object I can put on anything (Rectangle, Image and so on) and make it respond to touch gestures. Unfortunately I ran into a problem I cannot solve. I couldn't find help so I try here.

Here is simple main.qml:

import QtQuick 2.5
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
    title: qsTr("Touch surface test")
    width: 640
    height: 480
    visible: true


    Rectangle {
        width: 200
        height: 60
        color: "blue"

        MyTouchSurface {
            id: myTouchSurface
            anchors.fill: parent
        }
    }

}

and here is MyTouchSurface.qml

import QtQuick 2.5

MultiPointTouchArea {
    id: myTouchSurface

    // this is object I want to respond to touch gestures
    property Item target: parent

    // this is object I want to keep target in. I won't let target get out of it
    property Item container: parent.parent

    property double targetX: target.x
    property double targetY: target.y
    property double centerX: target.width / 2
    property double centerY: target.height / 2
    property double lastTouchX
    property double lastTouchY
    property double lastXDrag
    property double lastYDrag

    // here I calculate received touches and move target
    onPressed: {
        lastTouchX = touchPoints[0].sceneX
        lastTouchY = touchPoints[0].sceneY
        if (slidingAnimation.running)
            slidingAnimation.stop()
    }
    onTouchUpdated: {
        if (touchPoints.length) {
            target.x += touchPoints[0].sceneX - lastTouchX
            target.y += touchPoints[0].sceneY - lastTouchY
            lastXDrag = touchPoints[0].sceneX - lastTouchX
            lastYDrag = touchPoints[0].sceneY - lastTouchY
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
        }
        else
            startSliding()
    }

    // when lifting fingers off screen I want to slide target across it's container
    function startSliding() {
        slidingAnimation.toX = target.x + lastXDrag * 10
        slidingAnimation.toY = target.y + lastYDrag * 10
        slidingAnimation.restart()
    }

    // This is animation responsible for sliding the target
    ParallelAnimation {
        id: slidingAnimation
        property double toX
        property double toY
        property int animationDuration: 250
        NumberAnimation {
            target: myTouchSurface.target
            property: "x"
            to: slidingAnimation.toX
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
        NumberAnimation {
            target: myTouchSurface.target
            property: "y"
            to: slidingAnimation.toY
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
    }

    // this is how I keep target object within container
    onTargetXChanged: {
        if (container != null) {
            if (target.x + centerX < 0)
                target.x = -centerX
            else if (target.x + centerX > container.width)
                target.x = container.width - centerX
        }
    }
    onTargetYChanged: {
        if (container != null) {
            if (target.y + centerY < 0)
                target.y = -centerY
            else if (target.y + centerY > container.height)
                target.y = container.height - centerY
        }
    }
}

I want to have all calculating and functions within MyTouchSurface so it is easy to apply on other object and to use it in another project.

The problem I have is I don't know how to prevent target object from moving out of container. Well, the code here is working very nice but it also generates very ugly errors about binding loop.

What is going on is:

  • I check target's x and y when they changes
  • If the x or y is out of specified area I move them back
  • I receive signal "x changed" or "y changed" again
  • I receive error about loop because my function caused itself to execute again

So I am asking: Is there any other simple way to prevent object from flying off screen and not receive binding loop errors at the same time?

I know I can use Timer to check target's x and y from time to time, and bring them back.

I know I can change the easing and duration of the animation so it stops on the container bounds itself but looks like it was stopped.

Seriously. Is there no simple way?


Things that won't work:

  • stopping animation after object was detected outside container (if it would move really fast it will stop out of the screen )
  • stopping animation and then changing target's x or y

Thanks everyone helping me so far. It is nice to know I am not alone here :)

4

3 回答 3

2

I think your problem is that you update either targetX or target.x (resp. for y) inside of the property update handler (onTargetXChanged()).

This might be picked up as a biding loop by the QML engine as if you change target.x from onTargetXChanged() the signal onTargetXChanged() will trigger again as it's bound to it in property double targetX: target.x.

You can try something like this:

TouchArea {
  property Item target  //  target is the Image object being moved
  property double targetX: target.x
  property double targetY: target.y

  onTargetXChanged: {
    /* if object is out of specified area move x respectively */
    performPropertyUpdate(args)
  }
  onTargetYChanged: {
    /* if object is out of specified area move y respectively */
    performPropertyUpdate(args)
  }

  function performPropertyUpdate(args) {
    /* actual setting of target.x or target.y */
  }
}
于 2015-07-28T08:52:29.253 回答
2

The solution is simply use Connections:

import QtQuick 2.5

MultiPointTouchArea {
    id: myTouchSurface

    // this is object I want to respond to touch gestures
    property Item target: parent

    // this is object I want to keep target in. I won't let target get out of it
    property Item container: parent.parent

    property double targetX: target.x
    property double targetY: target.y
    property double centerX: target.width / 2
    property double centerY: target.height / 2
    property double lastTouchX
    property double lastTouchY
    property double lastXDrag
    property double lastYDrag

    // here I calculate received touches and move target
    onPressed: {
        lastTouchX = touchPoints[0].sceneX
        lastTouchY = touchPoints[0].sceneY
        if (slidingAnimation.running)
            slidingAnimation.stop()
    }
    onTouchUpdated: {
        if (touchPoints.length) {
            target.x += touchPoints[0].sceneX - lastTouchX
            target.y += touchPoints[0].sceneY - lastTouchY
            lastXDrag = touchPoints[0].sceneX - lastTouchX
            lastYDrag = touchPoints[0].sceneY - lastTouchY
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
        }
        else
            startSliding()
    }

    // when lifting fingers off screen I want to slide target across it's container
    function startSliding() {
        slidingAnimation.toX = target.x + lastXDrag * 10
        slidingAnimation.toY = target.y + lastYDrag * 10
        slidingAnimation.restart()
    }

    // This is animation responsible for sliding the target
    ParallelAnimation {
        id: slidingAnimation
        property double toX
        property double toY
        property int animationDuration: 250
        NumberAnimation {
            target: myTouchSurface.target
            property: "x"
            to: slidingAnimation.toX
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
        NumberAnimation {
            target: myTouchSurface.target
            property: "y"
            to: slidingAnimation.toY
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
    }

    Connections{
        target:myTouchSurface.target
        onXChanged:{
            if (container != null) {
                if (target.x + centerX < 0)
                    target.x = -centerX
                else if (target.x + centerX > container.width)
                    target.x = container.width - centerX
            }
        }

        onYChanged:{
            if (container != null) {
                if (target.y + centerY < 0)
                    target.y = -centerY
                else if (target.y + centerY > container.height)
                    target.y = container.height - centerY
            }
        }
    }

    // this is how I keep target object within container
//    onTargetXChanged: {

//    }
//    onTargetYChanged: {

//    }
}
于 2015-07-28T20:25:49.933 回答
1

To solve the issue myX should be an alias of x,

property alias myX: rectangle2.x

You're getting this problem cause myX is changing the value of x and x is changing the value of myX: a loop.

于 2015-07-28T18:06:09.343 回答