问题
I have a scene "editor" flickable on the bottom, and docked at its right side, a scene "outliner" flickable on top of it, which shows a tree of the scene structure.
Flickable {
id: editor
anchors.fill: parent
contentWidth: 5000
contentHeight: 5000
Repeater {
model: 500
delegate: Rectangle {
width: Math.random() * 200 + 50
height: width
x: Math.random() * editor.contentWidth
y: Math.random() * editor.contentHeight
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
}
}
}
Flickable {
id: outliner
anchors.right: editor.right
width: contentWidth
height: parent.height
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
Column {
Repeater {
model: 500
delegate: Rectangle {
width: Math.random() * 200 + 50
x: outliner.contentWidth - width
height: 50
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
}
}
}
}
Naturally, I want to be able to flick both to navigate both, since they both do get larger than the available screen real estate.
The problem is that the outliner flickable will simply block the editor flickable, even if I flick from a position that the outliner items do not occupy. I don't want that, what I want is to flick the outliner only if the flick happens on top of an outliner item, if not I want to skip through to the editor, so I can click and flick it from the area to the right.
I haven't been able to do it, due to the way Flickable
works. It will come in first to check for a drag, and only if the click doesn't exceed the drag threshold will it let the click down to underlying elements. So I can't think of a way to flick only if there is an object in that position.
Is there any way I can get the event handling to do what I want it to?
回答1:
You could do that by checking on each press in the scene if there is a outliner's delegate below the press. If not, mark the outliner
as not interactive. Re enable it just after when there is movement in the editor
.
Like so :
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
ApplicationWindow {
id: app
visible: true
width: 600
height: 480
Item { // a common parent not containing the MouseArea to be able to call childAt on press
id: flickablesParent
anchors.fill: parent
Flickable {
id: editor
anchors.fill: parent
contentWidth: 5000
contentHeight: 5000
onMovementStarted: outliner.interactive = true //re-enable the outliner user interaction so it can be flicked again.
Repeater {
model: 500
delegate: Rectangle {
width: Math.random() * 200 + 50
height: width
x: Math.random() * editor.contentWidth
y: Math.random() * editor.contentHeight
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
}
}
}
Flickable {
id: outliner
anchors.right: editor.right
width: contentWidth
height: parent.height
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
Column {
id: column // set an id so it can be referenced in the MouseArea
Repeater {
model: 500
delegate: Rectangle {
width: Math.random() * 200 + 50
x: outliner.contentWidth - width
height: 50
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
}
}
}
}
}
MouseArea {
id: dispatcher
anchors.fill: parent
onPressed: {
var flickable = flickablesParent.childAt(mouse.x, mouse.y); //find out on what flickable we pressed (the check could be ommited but I guess it's more performant with)
if (flickable === outliner) {
var mappedPos = mapToItem(column, mouse.x, mouse.y);
var delegate = column.childAt(mappedPos.x, mappedPos.y);
if (!delegate)
outliner.interactive = false; //if there's no delegate where we pressed in the column, we disable the interactions of the outliner flickable.
}
mouse.accepted = false; // let the underlying Flickable deal with this event
}
}
}
EDIT: You don't really need to reenable the outliner in onMovementStarted
, you could do it in the MouseArea onPressed
too.
like
onPressed: {
var flickable = flickablesParent.childAt(mouse.x, mouse.y); //find out on what flickable we pressed (the check could be ommited but I guess it's more performant with)
if (flickable === outliner) {
var mappedPos = mapToItem(column, mouse.x, mouse.y);
var delegate = column.childAt(mappedPos.x, mappedPos.y);
outliner.interactive = delegate ? true : false;
}
mouse.accepted = false; // let the underlying Flickable deal with this event
}
回答2:
Mkay, I think I did it, the key was to disable the interactivity for the outliner, then intercept input via a MouseArea
in the delegate, then carefully time and measure the manual dragging delta and if fast enough, schedule a manual flick on release.
Getting it to both stop an ongoing flick on press and subsequently launching another flick from the next drag turned out to be problematic after a flick has just been cancelled, but by delaying each flick to the next event loop cycle I got it working.
I also added wheel support and such.
If anyone experiences problems with it on any platform, do let me know in the comments.
// the editor code is the same from the OP
Flickable {
id: outliner
anchors.right: editor.right
width: contentWidth
height: parent.height
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
interactive: false
property int ly: 0
property real lt: 0
property int dy: 0
function go(yy) {
dy = contentY - (contentY -= yy - ly)
ly = yy
lt = Date.now()
}
Timer {
id: trigger
interval: 1
onTriggered: outliner.flick(0, outliner.dy * 150)
}
Column {
Repeater {
model: 500
delegate: Rectangle {
width: Math.random() * 200 + 50
x: outliner.contentWidth - width
height: 50
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
MouseArea {
anchors.fill: parent
Drag.active: drag.active
drag.target: Item { id: dummy }
onPressed: {
dummy.y = 0
if (outliner.flicking) outliner.cancelFlick()
}
onWheel: outliner.flick(0, wheel.angleDelta.y * 10)
onPositionChanged: {
if (drag.active) outliner.go(dummy.y)
}
onReleased: {
if (Date.now() - outliner.lt < 50) trigger.start()
outliner.ly = 0
outliner.returnToBounds()
}
}
}
}
}
}
来源:https://stackoverflow.com/questions/45503483/click-flick-through-emptry-area-of-a-flickable-that-partially-overlaps-another