×
Namespaces

Variants
Actions
Revision as of 07:29, 30 January 2013 by hamishwillee (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Building a CoverFlow component with QML

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata
Code ExampleCompatibility
Platform(s):
Symbian
Article
Created: jappit (21 Feb 2011)
Last edited: hamishwillee (30 Jan 2013)

This article shows how to build a Cover Flow UI component (with flipable elements) using Qt Quick.


Wiki n8 qml coverflow front.png

Contents

Using PathView

A CoverFlow component is composed of multiple items placed on a (typically straight) path, with variable size and angle depending on their position on the path itself. Specifically:

  • items closer to the path center are bigger in size, and with angle closer to zero
  • items closer to the path boundaries are smaller in size, and the item face is oriented towards the path center

The component is contained into a root Rectangle defined as follows. It also defines three properties:

  • itemWidth and itemHeight, that define the width and height of a single item of the CoverFlow
  • listModel, that defines the model of the component's PathView
Rectangle {
id: coverFlow
 
property int itemWidth: 100
property int itemHeight: 100
 
property ListModel listModel
}

Within the root Rectangle, a PathView is defined, containing a Path composed of two straight PathLines.

The two PathLine's start from the component's left/right boundaries and end at its center.

Appropriate PathAttributes are used to correctly place and resize the items on the PathView. The following attributes are defined:

  • angle - is +/- 60 at the Path boundaries, to orient the items towards the Path center, and is zero at the Path center
  • iconScale - is 0.5 at the Path boundaries, to scale down icons, and is 1.0 at the Path center
  • z - to place items at the boundaries below items closer to the center, a lower value is used at the boundaries, and a higher value is used at the center
PathView {
id: myPathView
 
anchors.fill: parent
 
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
 
focus: true
interactive: true
model: listModel
 
path: Path {
startX: 0
startY: coverFlow.height / 2
PathAttribute { name: "z"; value: 0 }
PathAttribute { name: "angle"; value: 60 }
PathAttribute { name: "iconScale"; value: 0.5 }
PathLine { x: coverFlow.width / 2; y: coverFlow.height / 2; }
PathAttribute { name: "z"; value: 100 }
PathAttribute { name: "angle"; value: 0 }
PathAttribute { name: "iconScale"; value: 1.0 }
PathLine { x: coverFlow.width; y: coverFlow.height / 2; }
PathAttribute { name: "z"; value: 0 }
PathAttribute { name: "angle"; value: -60 }
PathAttribute { name: "iconScale"; value: 0.5 }
}
}

To handle key navigation, the Keys onRightPressed and onLeftPressed handlers are defined as follows:

PathView {
id: myPathView
 
Keys.onRightPressed: if (!moving && interactive) incrementCurrentIndex()
Keys.onLeftPressed: if (!moving && interactive) decrementCurrentIndex()
 
[...]
}

The ListModel

The base structure of the ListModel used by the PathView delegate is defined as follows. Each ListElement has two properties:

  • icon - is the image displayed in the PathView item
  • name - will be used in the detail view of the PathView item
ListModel {
 
ListElement { name: "Google"; icon: "pics/0.png" }
ListElement { name: "YouTube"; icon: "pics/1.png" }
ListElement { name: "Facebook"; icon: "pics/2.png" }
 
[...]
}

The PathView delegate

Each item on the PathView consists of a Flipable object. The front side of the Flipable represents the item in its "normal" state, during user interaction with the PathView, while the back side will be used to display detail information about the single item, as shown in the image below.

Wiki n8 qml coverflow back small.png

The Flipable component base structure is defined as follows:

  • width and height properties are bound to the component's itemWidth and itemHeight properties
  • z and scale properties are bound to the Path's z' and iconScale attributes
  • a Rotation element is used to rotate the item accordingly to the Path angle attribute
Component {
id: appDelegate
 
Flipable {
id: myFlipable
 
width: itemWidth; height: itemHeight
z: PathView.z
scale: PathView.iconScale
 
transform: Rotation {
id: rotation
origin.x: myFlipable.width/2
origin.y: myFlipable.height/2
axis.x: 0; axis.y: 1; axis.z: 0
angle: PathView.angle
}
 
front: Rectangle {
smooth: true
width: itemWidth; height: itemHeight
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "black"
border.color: "white"
border.width: 3
 
Image {
id: myIcon
anchors.centerIn: parent
source: icon
smooth: true
}
}
back: Rectangle {
anchors.fill: parent
color: "black"
Text {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
color: "white"
text: "This is the back view for " + name
}
}
}
}

The PathView can be now modified to use this delegate Component:

PathView {
id: myPathView
 
[...]
 
delegate: appDelegate
}

The base CoverFlow component is now complete: sliding it left and right will move the icons with the classic CoverFlow effect. What is left to do is to show the detail view of each item, once it is clicked.

Showing the Flipable back view

First, a flipped bool property is added to the Flipable component: this property will hold the current item state (flipped or not).

property bool flipped: false

Then, a "back" State is declared, that will actually show the Flipable's back view: this is accomplished by modifying the angle of the Flipable rotation, setting it to 180.

The width and height properties are also changed, so that the back view, once shown, will take the whole PathView area.

states: State {
name: "back"
PropertyChanges { target: rotation; angle: 180 }
PropertyChanges {target: myFlipable; width: myPathView.width; height: myPathView.height }
when: myFlipable.flipped
}

To add a nice Animation during the state changes, a Transition consisting of two Animations is used:

transitions: Transition {
ParallelAnimation {
NumberAnimation { target: rotation; property: "angle"; duration: 250 }
NumberAnimation {target: myFlipable; properties: "height,width"; duration: 250}
}
}

The JavaScript logic

The state changes are performed by a JavaScript function, that does the following:

  • if the clicked item is now the PathView's current item, then set it to be the current item
  • if the clicked item is the PathView's current item, then:
    • switch the flipable property
    • if the item is flipped (so, the back view is displayed) then set the PathView interactive to false, otherwise set it to true
function itemClicked()
{
if(PathView.isCurrentItem) {
myFlipable.flipped = !myFlipable.flipped
myPathView.interactive = !myFlipable.flipped
}
else if(myPathView.interactive) {
myPathView.currentIndex = index
}
}

The itemClicked() function must be called when the Flipable is clicked and, to handle key interaction, when the Return key is pressed. So, the Flipable is modified as follows:

Flipable {
id: myFlipable
 
[...]
 
Keys.onReturnPressed: itemClicked()
 
MouseArea {
anchors.fill: parent
onClicked: itemClicked()
}
}

Adding a signal to the component

It would be useful, for a QML application using the CoverFlow component defined above, to know when the PathView current item has changed. In order to do this, a new signal is defined by the component: indexChanged(int index). This signal must be called when the PathView currentIndex property changes: for this reason, it is enough to call it when the currentIndexChanged signal is called.

Rectangle {
id: coverFlow
 
[...]
 
signal indexChanged(int index)
 
Component.onCompleted: {
myPathView.currentIndexChanged.connect(function(){
indexChanged(myPathView.currentIndex);
})
}
}

How to use the CoverFlow

The following video shows the CoverFlow component in action on a Nokia N8 device:

The media player is loading...

The code below shows how to use the CoverFlow component. The following steps are performed:

  • a ListModel is defined, to be used by the PathView
  • the indexChanged signal is used to show the index of the currently selected item
Rectangle {
width: 400; height: 240
 
ListModel {
id: appModel
ListElement { name: "Google"; icon: "pics/0.png" }
ListElement { name: "YouTube"; icon: "pics/1.png" }
ListElement { name: "Facebook"; icon: "pics/2.png" }
ListElement { name: "MySpace"; icon: "pics/3.png" }
ListElement { name: "Blogger"; icon: "pics/4.png" }
ListElement { name: "Flickr"; icon: "pics/5.png" }
ListElement { name: "WordPress"; icon: "pics/6.png" }
ListElement { name: "Technorati"; icon: "pics/7.png" }
ListElement { name: "Heart"; icon: "pics/8.png" }
ListElement { name: "Twitter"; icon: "pics/9.png" }
ListElement { name: "Yahoo"; icon: "pics/10.png" }
ListElement { name: "DesignFloat"; icon: "pics/11.png" }
ListElement { name: "Reddit"; icon: "pics/12.png" }
ListElement { name: "Stumbleupon"; icon: "pics/13.png" }
ListElement { name: "Delicious"; icon: "pics/14.png" }
ListElement { name: "Digg"; icon: "pics/15.png" }
ListElement { name: "RSS"; icon: "pics/16.png" }
}
 
Text {
id: myText
anchors.bottom: parent.bottom
text: "current"
anchors.horizontalCenter: parent.horizontalCenter
}
 
CoverFlow {
listModel: appModel
 
width: parent.width
anchors.top: parent.top
anchors.bottom: myText.top
 
onIndexChanged: {
myText.text = "Current index: " + index
}
 
itemWidth: 120
itemHeight: 120
 
color: "lightblue"
}
}

Related content

The full source code presented in this article is available here, as a complete Qt Creator project: File:CoverFlowQuickApp.zip

This page was last modified on 30 January 2013, at 07:29.
235 page views in the last 30 days.
×