I have this ListView:
ListView {
id: topList
focus: true
width: parent.width
height: parent.height
preferredHighlightBegin: height - 70
preferredHighlightEnd: height
highlightMoveSpeed: 150
highlight: Rectangle { width: theListView.width; height: 22; color: "yellow" }
highlightRangeMode: ListView.StrictlyEnforceRange
}
Now, when I use up or down arrow the list moves nice and fine. However, when the movement is done, I would like to trigger another animation. Have tried listening on onMovementEnded and onFlickEnded but neither of them seems to be triggered when the animation is done.
The QtQuick examples include an example of rolling your own: http://doc.qt.nokia.com/4.7-snapshot/declarative-modelviews-listview-highlight-qml.html
The basic idea is to prevent the view from moving the highlight itself:
highlightFollowsCurrentItem: false
and use an animation to make the highlight follow the currentItem. You will probably want to stick to SmoothedAnimation or SpringAnimation since they handle the target position being moved before the animation is complete.
I believe it is not possible. highlightPosAnimator and highlightSizeAnimator are part of QDeclarativeListView private implementation, and not accesible by "user" code.
If you need custom/combined highlight animation, looks like you have to roll your own.
Related
I tried to play with cacheBuffer, but it's only help me to increase count of cached delegates, when I want to disable caching at all.
Now with zero caching buffer my example (only one item stretched on all ListView) behaves like this:
At the start ListView creates two delegates: currently visible and
next one.
When I scrolling list forward it creates and keep up to 4 delegates without beginning destroying them.
When I start scrolling list backward it begin immediately destroying delegates without looking on cacheBuffer.
If you replace "height: root.height" to "height: listView.height", it will create delegates for all model items at the start.
Is this behaviour normal? Can I change it some way?
You can tried it yourself:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Window 2.11
import Qt.labs.calendar 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView {
id: listView
anchors.fill: parent
snapMode: ListView.SnapOneItem
cacheBuffer: 0
model: 10
delegate: Rectangle {
width: parent.width
height: root.height
// height: listView.height
border.color: "black"
Text {
anchors.centerIn: parent
text: modelData
}
Component.onCompleted: {
console.log("Delegate completed")
}
Component.onDestruction: {
console.log("Delegate destruction")
}
}
}
}
Replace
delegate: MyVeryComplexDelegate {
}
by
delegate: Loader {
width: expectedDelegateWidth
height: expectedDelegateHeight // Otherwise you might create all...
sourceComponent: MyVeryComplexDelegate {
}
active: someCriteriaYouFeelGoodAbout()
}
Now you will only have simple Loaders in your cache and you can decide which ones of those in the cache are active.
Probably better: Have parts of the MyVeryComplexDelegate loaded as the ListView wants, and just hide the most complex parts behind a Loader that turns active only if you really need the full complexity.
On your strange findings as far as I can explain them:
Regarding the difference between root.height and listView.height, the explanation is an issue that is subject to many questions:
While root.height references the property height of the window, which you have explicitly set, listView.height is determined by anchors.fill: parent, which results in setting the height to root.contentItem.height - and that is initially 0. Therefore the delegates, initially all have a height of 0, all of them would fit in the view and therefor have to be created, even if you load as lazy as possible. Later they will resize together with the root.contentItem and some will be destroyed again.
You can see that, when monitoring the height changes of your delegates and your ListView
The next thing is, that even if the delegate really fills the ListView from the beginning, a second delegate is instantiated. The reason for that is, the condition used by the ListView, when to create new delegates. For that the sum of heights - the displacement of the first has to be larger than the ListView. That is not fulfilled when it is equal to the height.
Increase the height of your delegate by a fraction of a pixel, and you are good.
height: root.height + 0.0001
Is there a way to hide a particular item on some event in a ListView?
So far I can do it by setting visible to false and height to zero of a delegate.
But If i have spacing in a listView set to 2 for example it appears that this solution is broken.
I think the proper way is to use a proxy model that filters out the elements that should not be displayed.
You can use a QSortFilterProxyModel or implement your own QAbstractProxyModel.
With that it is even possible to animate the removal and addition of the elements.
Or use SortFilterProxyModel if you don't wanna touch C++ and performance is not a problem
A hack around this could be to set the spacing of the ListView to 0 and implement it in the delegate itself. Something like this:
ListView{
id: listView
spacing: 0
delegate: Item{
id: itemDelegate
width: parent.width; height: spacingRect.height + actualDelegate.height
Item {id: actualDelegate;} // your actual delegate
Rectangle{ id: spacingRect; height: 2; width: parent.width; color: "transparent"; anchors.top: actualDelegate.bottom}
}
}
In this way when you hide the delegate the spacing will also be hidden
Hope this makes some sense as a question. In my app, I have a DragArea defined which I use to start dragging things over top of various Rectangles that each contain a DropArea. Everything is working fine in my code except for a cosmetic effect that I would like to change.
In QML, when you start dragging from a DragArea and eventually drop, the animation effect is such that the thing you're dragging animates (while fading out) back to the spot from which you started dragging. This happens even when you drop over a DropArea that successfully captures the drop.
What I would like to do is have the drop effect animate towards the DropArea that received the drop - so that it appears I am dragging-and-dropping things into the Rectangle. Is there any way to do this?
I'm guessing that this in some way involves the .source and .target properties of these areas, but no luck so far in having any effect on where the drop animation goes.
By default, QML will give you no cosmetic behavior for drag and drop whatsoever. The drag target will begin at the drag start location, and will end wherever it is dropped, regardless of whether the drag is accepted or not.
Thus I assume the behavior you describe is implemented in your user code, which you have not disclosed. Regardless, what you want to do is quite easy, it involves tracking the position the drag originates at and it ends at, so you can use the two coordinates to animate the position.
In the following example the red rectangle can be dragged, and if dropped outside of a drop area it will animate from its current to its initial position, whereas if dropped in the yellow rectangle, it will animate from its initial to its drop position.
Window {
width: 600
height: 600
visible: true
Rectangle {
width: 200
height: 200
color: "yellow"
DropArea {
anchors.fill: parent
onEntered: drag.source.accepted = true
onExited: drag.source.accepted = false
}
}
Rectangle {
id: rect
width: 50
height: 50
color: "red"
x: parent.width * 0.5
y: parent.height * 0.5
Drag.active: mouseArea.drag.active
property point begin
property point end
property bool accepted : false
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
onPressed: rect.begin = Qt.point(rect.x, rect.y)
onReleased: {
rect.end = Qt.point(rect.x, rect.y)
aX.from = rect.accepted ? rect.begin.x : rect.end.x
aX.to = rect.accepted ? rect.end.x : rect.begin.x
aY.from = rect.accepted ? rect.begin.y : rect.end.y
aY.to = rect.accepted ? rect.end.y : rect.begin.y
anim.start()
}
ParallelAnimation {
id: anim
NumberAnimation { id: aX; target: rect; property: "x"; duration: 200 }
NumberAnimation { id: aY; target: rect; property: "y"; duration: 200 }
}
}
}
}
I have several QML Items that all have Mouse Areas.
What I want to achieve is:
Click one of the items and start tracking the mouse
Add every other Item that the mouse enters into a list
End the tracking once the mouse is released
Sample Code:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width:500; height: 200;
Rectangle{
anchors.left: parent.left
color: 'red'
width: 200; height: 200;
MouseArea {
anchors.fill: parent
hoverEnabled: true
onReleased: console.log('onReleased red')
onEntered: console.log('onEntered red')
onPressed: {
console.log('onPressed red')
mouse.accepted = false
}
}
}
Rectangle{
anchors.right: parent.right
color: 'blue'
width: 200; height: 200;
MouseArea {
anchors.fill: parent
hoverEnabled: true
onReleased: console.log('onReleased blue')
onEntered: console.log('onEntered blue')
onPressed: console.log('onPressed blue')
}
}
}
Expected behaviour:
Click one Rectangle
Get on entered event if I enter the other element
Get the Released event
The sample code has both Version I tried, with and without accepting the mousePressed event.
What happens is:
If I press the mouse over one rectangle I do not get the onEnter event for all my other rectangles.
If I do not accept the onPressed event, I get the onEnter Events but not the onReleased event.
Note:
I already found this Answer which uses a DropArea as workaround which is not what I want to use if there is any other solution.
Even though the Example may look like Drag&Drop it is not what I want.
Please see the "What I want to achieve" at the top of this Question.
You will not be able to achieve what you want just using standart MouseArea components. Standart QML components are kinda limited in their functionality.
What you have to do is to create your own MouseArea component through QML extension.
In our project we also encountered lots of problems with mouse handling, so how we managed to do it was:
Subclassed QQuickItem, and inside of this class we just tracked the mouse movement, and mouse buttons states. One important thing wass to install EventFilters defined by this class.
In QML, created a Simple Component that Checks if mouse is inside of currrent component.
If you need, i could post also a code here so you have an idea.
The implementation is not the prettiest, but it works
I've tried to implement drag and drop in Qt 5.3 using the new QML types Drag, DragEvent and DropArea. This is the original example from the documentation of the QML Drag type with some small modifications:
import QtQuick 2.2
Item {
width: 800; height: 600
DropArea {
width: 100; height: 100; anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: parent.containsDrag ? "red" : "green"
}
onEntered: print("entered");
onExited: print("exited");
onDropped: print("dropped");
}
Rectangle {
x: 15; y: 15; width: 30; height: 30; color: "blue"
Drag.active: dragArea.drag.active
// Drag.dragType: Drag.Automatic
Drag.onDragStarted: print("drag started");
Drag.onDragFinished: print("drag finished");
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
}
Expected behaviour: The small blue rectangle (drag target) can be dragged around with the mouse. If dragged over the larger green rectangle in the center of the window, this rectangle turns red and back to green when leaving. In addition, the signals dragStarted, entered, exited, dropped and dragFinished are emitted in time and the corresponding signal handlers print out their messages.
Experienced behaviour:
Depends on Drag.dragType (see commented line above):
Drag.dragType is NOT set (default is Drag.Internal):
Drag and drop works as described, but only the signals entered and exited are emitted. The other signals (dragStarted, dragFinished and dropped) are suppressed. So there is no way to react to the drop in the DropArea.
Drag.dragType is set to Drag.Automatic:
All of the signals are emitted now, but the blue rectangle (drag target) does not move with the mouse. Instead, the mouse cursor changes its shape to visualize possible drop targets. After the mouse has been released, the blue rectangle jumps to the latest mouse position.
Neither of these two variants are pleasing. How can I get all signals and still be able to drag around the drag target? Unfortunately the documentation is everything but clear about drag-and-drop in QML, especially about the ominous Drag.dragType.
If you open the QQuickDrag source code and look at the differences between start(), which is used by Drag.Internal, and startDrag() which is used by Drag.Automatic, the difference is pretty obvious. start() sets up an event change listener, which it then uses to update the position of the attached object. startDrag() doesn't do this.
Why does it work this way? I have no idea! The QtQuick 2 drag and drop documentation certainly has room for improvement here.
There is a fairly simple workaround: take the best from both worlds. Use Drag.Automatic, but instead of setting Drag.active, call start() and drop() manually. It won't invoke Drag.onDragStarted() and Drag.onDragFinished() but you essentially get those for free anyway by listening for a change in the MouseArea's drag.active.
Here's the concept in action:
import QtQuick 2.0
Item {
width: 800; height: 600
DropArea {
width: 100; height: 100; anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: parent.containsDrag ? "red" : "green"
}
onEntered: print("entered");
onExited: print("exited");
onDropped: print("dropped");
}
Rectangle {
x: 15; y: 15; width: 30; height: 30; color: "blue"
// I've added this property for simplicity's sake.
property bool dragActive: dragArea.drag.active
// This can be used to get event info for drag starts and
// stops instead of onDragStarted/onDragFinished, since
// those will neer be called if we don't use Drag.active
onDragActiveChanged: {
if (dragActive) {
print("drag started")
Drag.start();
} else {
print("drag finished")
Drag.drop();
}
}
Drag.dragType: Drag.Automatic
// These are now handled above.
//Drag.onDragStarted: print("drag started");
//Drag.onDragFinished: print("drag finished");
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
}
I realize it's not a completely satisfying solution, but it does match your expected behavior.
This solution offers:
Notifications for all of the desired events: drag started, drag finished, enter drag area, exit drag area, and dropped in drag area.
The drag animation is automatically handled by QtQuick. The square doesn't freeze in place like it does when running the sample code with Drag.Automatic.
What it doesn't offer:
An explanation as to why QtQuick's drag and drop functionality works this way, or whether it's even the intended behavior by the developers. The current documentation seems ambiguous.
Just ran into this myself (using Qt 5.2, but the same problem exists there). I've got a 'slider box' on the X-axis and just wanted to know when the drag was finished... instead of responding to every position change along the way. My workaround involved hacking the states/transitions, with a ScriptAction to provide the logic. This is the simplified version for mimicking a response to the "onDragFinished" signal. So while it doesn't cover all your drag/drop signals, it might get you pointed in the right direction.
Rectangle {
id: sliderControl
height: coordinates.height
width: 80
color: "#F78181"
border.color: "#FE2E2E"
border.width: 1
opacity: 0.4
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: sliderControl
drag.axis: Drag.XAxis
drag.minimumX: 0
drag.maximumX: view.width - sliderControl.width
hoverEnabled: true
}
states: [
State {
name: "dragging"
when: mouseArea.drag.active
},
State {
name: "finished_dragging"
when: !mouseArea.drag.active
}
]
transitions: [
Transition {
from: "dragging"
to: "finished_dragging"
ScriptAction {
script: console.log("finished dragging script");
}
}
]
}
ps - I know that such a 'workaround' doesn't qualify for the bounty parameters, but I was pretty bummed to find only your question (no solutions) when I searched for help on the issue. Hopefully anyone else stumbling down this path will find this useful. Unfortunately, I've got no clue what's going on with QML's Drag.dragType either.