Archives For Demo Apps

Swiped Pages App

June 1, 2016 — 4 Comments

Overview

This is part 3 of a series of demo apps helping you to understand Qt 5.7 for x-platform development on Android and iOS.

You should have read the blogs about my first app using Qt 5.7 Material Style and my second app (StackView Example) before reading on.

This time I’ll explain HowTo use SwipeView – one of the new Qt Quick Controls 2.

Coming from BlackBerry10 Cascades ? SwipeView is one of the less things I ever missed at Cascades. In Apps where I had to implement a Wizard going through some Pages back and force I used Container visibility to make this work.

You should have installed Qt 5.7 RC.

New to Qt 5 development ? Please read my blog series from the beginning to learn some basic stuff and to understand this app.

android_wizard_1

From screenshot above you know about the Business Rules for this SwipeView Example App:

  • SwipeView is used to implement a Wizard
  • There are 5 Pages the User must go through from 1 –> 2 –> 3 –> 4 –> 5
  • A Page must be valid before going to the next Page
  • Validation can be simulated for each Page using the Floating Action Button (FAB)

Also – same as for StackView Example App –

  • When a Page is loaded: call init()
  • Before destroyed: call cleanup()

The Source

This app can be downloaded as Open Source from  github: https://github.com/ekke/swiped_pages_x

Project structure and .pro

The project structure is similar to my StackView Example App – we’re using also 5 Pages here for SwipeView to make it easy for you to learn about the different ways to navigate through a bunch of Pages. Pages are named same as for the StackView app, but sourcecode is slightly different.

main.cpp and ApplicationUI (C++) are also similar to the other apps (One Page Example App, StackView Example App)

ApplicationWindow (main.qml)

main.qml imports, ApplicationWindow and header are similar to stacked-pages-x app – but header is using another ToolBar from Sources/common/SwipeTextTitle:

header: SwipeTextTitle {
    id: titleBar
    text: navPane.currentItem? navPane.currentItem.title : qsTr("A simple Swiped - Pages APP")
}

SwipeTextTitle’s text is bound to currentItem from SwipeView and text changes if another Page becomes the current page. The ‘BACK’ ToolButton is slightly different to StackTextTitle:

ToolButton {
    enabled: navPane.currentIndex > 0
    focusPolicy: Qt.NoFocus
    Image {
        id: backImageImage
        visible: navPane.currentIndex > 0
        anchors.centerIn: parent
        source: "qrc:/images/"+iconOnPrimaryFolder+"/arrow_back.png"
    }
    onClicked: {
        navPane.onePageBack()
    }
}

BACK Button will only work if currentIndex > 0 – so on the very first Page the Button disappears.

The options menu (three dots at right side from ToolBar) looks same as for StackView Example:

  • switch between dark and light theme
  • select primary color
  • select accent color

android_options_menu

main.qml also contains the Popup to select the colors – already described in one-page-x app.

Four other controls we’ll find in main.qml:

  • SwipeView
  • PageIndicator
  • Floating Action Button (FAB)
  • PopupInfo

ApplicationWindow –> FAB

The FAB always stays on top on a fixed position and is defining in main.qml besides SwipeView.

FloatingActionButton {
    id: fab
    visible: !navPane.pageValidation[navPane.currentIndex]
    property string imageName: "/done.png"
    z: 1
    anchors.margins: 16
    anchors.right: parent.right
    anchors.bottom: parent.bottom
    imageSource: "qrc:/images/"+iconOnPrimaryDarkFolder+imageName
    backgroundColor: primaryDarkColor
    onClicked: {
        // simulating check isValid()
        // only if valid next page can be swiped to
        navPane.pageValidation[navPane.currentIndex] = 1
        navPane.validationChanged()
        visible = false
        // Attention: since 5.7 RC you MUST RESET FOCUS
        // otherwise Shortcuts not working well
        resetFocus()
    }
} // FAB
SwipeView {
    id: navPane
    signal validationChanged()
	// ...
    currentIndex: 0
    // currentIndex is the NEXT index swiped to
    onCurrentIndexChanged: {
        fab.visible = !pageValidation[currentIndex]
	    // ...
    }
}
// we can loose the focus if Menu or Popup is opened
function resetFocus() {
    navPane.focus = true
}

Please notice: if clicked on FAB you must reset the Focus to use Shortcuts properly. This is new with 5.7 RC.

Tapping on the FAB does some tasks:

  • Sets current Page as valid: navPane.pageValidation[navPane.currentIndex] = 1
  • Emits a SIGNAL: validationChanged()
  • Makes FAB invisible for current Page

Please take a look at FAB visibility: Tapping on FAB makes the Button invisible, swiping to another Page changes currentIndex and Visibility will be checked again for current Page.

Explore the sourcecode for the Pages: they’re connected to the SIGNAL to enable/disable Buttons. Here’s from PageTwo.qml:

Connections {
    target: navPane
    onValidationChanged:  {
        button3.enabled = navPane.pageValidation[1]
        button5.enabled = navPane.pageValidation[2] && navPane.pageValidation[3]
    }
}

ApplicationWindow –> PageIndicator

It’s a good idea to show the User the current Page and how many Pages exist.

Simply bind a PageIndicator to your SwipeView:

PageIndicator {
    id: pageIndicator
    count: navPane.count
    currentIndex: navPane.currentIndex
    anchors.bottom: navPane.bottom
    anchors.horizontalCenter: parent.horizontalCenter
}

See PageIndicator and FAB / enabled-disabled Buttons in action:

button_enabled_disabled

 

ApplicationWindow –> SwipeView

Our Example uses 5 Pages:

swiping

In most cases your Pages will be defined as static Pages as described in the SwipeView docs.

I have developed some Business Apps for Enterprise Customers where Wizards are really complex with more or less business logic and many controls inside. My goal is always to develop performant apps, so I’m always looking to do it a dynamic way:

Only instantiate Controls / Components you really need or have to show in UI.

In StackView Example App we have seen that StackView manages this dynamic behavior for you and loads / unloads Pages as they’re pushed / popped.

A SwipeView works different: it’s like placing some sheets of paper side by side and you move the content right/left.

SwipeView Example App uses Loader to be dynamic.

Dynamic SwipeView -> Loader

Here’s a short overview of Dynamic SwipeView using Loader:

dynamic_loading

While swiping through the Pages only the currently visible Page plus one Page back and one Page ahead are loaded. The image above shows a snapshot while Page 3 (index 2) is currentItem: Page 2 and Page 4 are loaded and Page 1 and Page 5 are unloaded.

Swiping in-action:

swiped_back_forward_from_3

Here’s one of the Loader:

loader

Notice: the Page inside a Loader must be accessed via item property if loaded.

Hint: All Properties stored inside a Page will be destroyed if unloaded. That’s the reason why the validation property is stored outside – in this case at StackView:

// 0: validation failed, 1: validation done
property var pageValidation: [0,0,0,0,0]

The Loader for Page 3 (Index 2):

Loader {
    // index 2
    id: pageThreeLoader
    property string title: active? item.title:"..."
    active: navPane.currentIndex == 1 || navPane.currentIndex == 2 || navPane.currentIndex == 3
    source: "pages/PageThree.qml"
    onLoaded: item.init()
}

active‘ is bound to currentIndex and – in this case – is active when index 1, 2 or 3 (Page 2, 3 or 4)

As soon as a Page gets loaded, the init() function was called.

Unfortunately there’s no ‘aboutToUnload’ or so to call the cleanup() function. Instead I’m using Component.onDestruction inside the Page:

Component.onDestruction: {
    cleanup()
}

SwipeView – Navigation Back and Forward

Similar to StackView Example you can go different ways to navigate through the Pages:

  • click on a (Raised or Flat) Button to go to next or previous Page
  • click on ‘Back’ from ToolBar to go one Page back
  • click on Android System ‘Back’ key to go one Page back
  • swipe using your fingers to go back or forward
  • use Shortcuts (see StackView example about Shortcuts)

Unfortunately there’s no SIGNAL or so to know that User reached first or last Page. (Will do Bugreport)

Using Android BACK, ToolBar BACK or Shortcuts I implemented a Popup to let the user know that first or last püage reached:

android_first_page_reached

ApplicationWindow –> PopupInfo

PopupInfo is defined in main.qml:

PopupInfo {
    id: popupInfo
    onAboutToHide: {
        popupInfo.stopTimer()
        resetFocus()
    }
} // popupInfo

You’ll find PopupInfo under /common/PopupInfo.qml:

Popup {
    id: popup
    property alias text: popupLabel.text
    property alias buttonText: okButton.text
	// ... the Controls
    onAboutToHide: {
        stopTimer()
    }
    onAboutToShow: {
        closeTimer.start()
    }
    Timer {
        id: closeTimer
        interval: 3000
        repeat: false
        onTriggered: {
            popup.close()
        }
    }
    function stopTimer() {
        closeTimer.stop()
    }
} // popup

PopupInfo uses a Timer to auto-hide if User doesn’t tap on the Popup or outside.

Coming from BlackBerry10 Cascades ? Timer (QTimer) is available as a Type without any extra declaration. Also there’s no attachedObjects[].

PopupInfo also is used while swiping if next Page isn’t valid:

onCurrentIndexChanged: {
    fab.visible = !pageValidation[currentIndex]
    if (lastIndex == currentIndex) {
        return
    }
    if (lastIndex > currentIndex) {
        // swiping back
        lastIndex = currentIndex
        return
    }
    // swiping forward
    for (var i = lastIndex; i <= currentIndex; i++) {
        if(!pageValidation[i]) {
            if(i < currentIndex) {
                pageNotValid(i+1)
                lastIndex = i
                currentIndex = i
                return
            }
        }
    } // for
    lastIndex = currentIndex
}
		// ...
function pageNotValid(pageNumber) {
    popupInfo.text = qsTr("Page %1 not valid.\nPlease tap 'Done' Button","").arg(pageNumber)
    popupInfo.buttonText = qsTr("So Long, and Thx For All The Fish")
    popupInfo.open()
}

popup_invalid

Please explore the sources – this blog article is only a snapshot.

Summary

This app is only using some of the powerful features SwipeView provides to you for navigation through some pages. Remember: this is not a production-ready app.

When to use a SwipeView and when to use a StackView ?

SwipeViews are great if you’re following a well-defined path from Page-to-Page as explained for a Wizard here.

StackView is great when user decides wich Page should be pushed on Top or removed from.

Using a StackView or SwipeView is not the only ways to navigate – stay tuned for the next Example App using a TabBar.


← Back (Stacked Pages App)

→ Next Article (Tab Bar App)

⇐ Home (Overview / Topics)

Stacked Pages App

May 27, 2016 — 6 Comments

Edited 2016-06-06: Buttons with focusPoliy: Qt.NoFocus

Edited 2016-06-05: Updated for Qt 5.7 RC

Overview

You should have read the blog about my first app using Qt 5.7 Material Style and also explored the sources before reading on.

This is part 2 of a series of demo apps helping you to understand Qt 5.7 for x-platform development on Android and iOS.

This time I’ll explain HowTo use StackView – one of the new Qt Quick Controls 2. This app will also use Buttons as ‘Raised Buttons‘ and ‘Flat Buttons‘.

Coming from BlackBerry10 Cascades ? StackView is similar to Cascades NavigationPane: you can push() pages on top and pop() pages to remove them from the stack.

You should have installed Qt 5.7 RC.

New to Qt 5 development ? Please read my blog series from the beginning to learn some basic stuff and to understand this app.

android_page_02

The Source

This app can be downloaded as Open Source from  github: https://github.com/ekke/stacked_pages_x

Project structure and .pro

The project structure is similar to demo-app-one-page-x.

proj_structure_stack

There’s a new folder Sources/pages where the qml files of pages 1 to 5 are stored. PageOne is the root page and PageTwo … PageFive can be pushed on the stack of pages.

main.cpp and ApplicationUI (C++) are also similar to the one-page-x-app.

UI Constants (C++) / Buttons (QML)

A new property was added to UI Constants ThemePalette:

  • flatButtonTextColor

Using Flat Buttons uncolored, the text color is different for dark / light theme. Here are Flat Buttons from PageThree: the left one colored with accentColor, the right one uncolored automatically changing color if theme changes:

android_page_03_flat

Raised Buttons have a colored background and text color depends from background color (accent, primary, primary light, primary dark) Here are Raised Buttons with accent color (‘POP’ Button) and primary color (‘PUSH 3’, ‘GOTO 5’ Button) from PageTwo:

android_page_02_buttons

At Bottom right position the Floating Action Button (FAB) is placed.

Attention: If Raised Buttons are colored with primaryColor, you should use a different color for the FAB. FAB’s stay at a fixed position and can overlap other parts while scrolling. You can try this out changing the orientation to Landscape. Here’s PageTwo in Landscape:

android_page_02_landscape

stacked-pages-x app is using primary dark for FAB’s.

Custom Controls: Raised and Flat Buttons

Qt 5.7 provides a new Button Control. stacked-pages app uses two customized Buttons: a Raised one and a Flat one. Both are found under Sources/common. Read more about Material Raised and Flat Buttons here from Google Material Design Guide.

ButtonRaised.qml:

Button {
    id: button
    // default: textOnPrimary
    property alias textColor: buttonText.color
    // default: primaryColor
    property alias buttonColor: buttonBackground.color
    focusPolicy: Qt.NoFocus
    Layout.fillWidth: true
    Layout.preferredWidth : 1
    leftPadding: 6
    rightPadding: 6
    contentItem: Text {
        id: buttonText
        text: button.text
        opacity: enabled ? 1.0 : 0.3
        color: textOnPrimary
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        elide: Text.ElideRight
        font.capitalization: Font.AllUppercase
    }
    background:
        Rectangle {
        id: buttonBackground
        implicitHeight: 48
        color: primaryColor
        radius: 2
        opacity: button.pressed ? 0.75 : 1.0
        layer.enabled: true
        layer.effect: DropShadow {
            verticalOffset: 2
            horizontalOffset: 1
            color: dropShadow
            samples: button.pressed ? 20 : 10
            spread: 0.5
        }
    } // background
} // button

ButtonFlat.qml:

Button {
    id: button
    // default: flatButtonTextColor
    property alias textColor: buttonText.color
    focusPolicy: Qt.NoFocus
    Layout.fillWidth: true
    Layout.preferredWidth : 1
    leftPadding: 6
    rightPadding: 6
    contentItem: Text {
        id: buttonText
        text: button.text
        opacity: enabled ? 1.0 : 0.3
        color: flatButtonTextColor
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        elide: Text.ElideRight
        font.capitalization: Font.AllUppercase
        font.weight: Font.Medium
    }
    background:
        Rectangle {
        id: buttonBackground
        implicitHeight: 48
        Layout.minimumWidth: 88
        color: button.pressed ? buttonText.color : "transparent"
        radius: 2
        opacity: button.pressed ? 0.12 : 1.0
    } // background
} // button

You see how easy it is to customize Qt Quick Controls 2 Button and it’s also easy to use these customized Controls inside the app. Here are the three Raised Buttons from PageTwo:

RowLayout {
    // implicite fillWidth = true
    spacing: 10
    ButtonRaised {
        text: "Pop"
        buttonColor: accentColor
        onClicked: {
            navPane.popOnePage()
        }
    }
    ButtonRaised {
        text: "Push 3"
        onClicked: {
            navPane.pushOnePage(pageThree)
        }
    }
    ButtonRaised {
        text: "GoTo 5"
        onClicked: {
            navPane.goToPage(5)
        }
    }
} // button row

How the FAB is customized I have described as part of one-page-x app blog article.

ApplicationWindow (main.qml)

main.qml imports, ApplicationWindow and header are similar to one-page-x app – but header is using another ToolBar from Sources/common/StackTextTitle:

header: StackTextTitle {
    id: titleBar
    text: navPane.currentItem? navPane.currentItem.title : qsTr("A simple Stacked - Pages APP")
}

StackTextTitle’s text is bound to currentItem from StackView and text changes if another Page becomes the topmost page – the ‘currentItem’. There’s also a new ToolButton added at left side in ToolBar:

ToolButton {
    enabled: navPane.depth > 1
    focusPolicy: Qt.NoFocus
    Image {
        id: backImageImage
        visible: navPane.depth > 1
        anchors.centerIn: parent
        source: "qrc:/images/"+iconOnPrimaryFolder+"/arrow_back.png"
    }
    onClicked: {
        navPane.popOnePage()
    }
}

You can see that the visibility of this ToolButton is bound to ‘depth’ property from StackView: only if depth > 1 the Button is visible.

Here’s the TitleBar for the first page (depth == 1) and for the second page (depth == 2):

android_page_01_02_titlebar

The options menu (three dots at right side from ToolBar) looks different to the previous app. Now you can:

  • switch between dark and light theme
  • select primary color
  • select accent color

main.qml contains the Popup to select the colors – already described in one-page-x app.

Two other controls we’ll find in main.qml:

  • StackView
  • Floating Action Button (FAB)

ApplicationWindow –> FAB

From screenshots above you have seen that the FAB always stays on top on a fixed position. At first I tried to use the ‘footer’ from ApplicationWindow, but this doesn’t work, because the footer occupies the complete space besides the FAB, where I only want to have the FAB on top without hiding more. Thanks to @jpnurmi I learned that I can place the FAB without using the footer by simply defining the FAB in main.qml besides StackView.

Coming from BlackBerry10 Cascades ? This is more flexible in Qt 5.7. In Cascades there’s only one tree with a Pane as root. In Qt 5.7 you can place different controls onto same space and ‘z’ order decides which will overlap.

FAB (z:1) stays on top of StackView (z:0). PopupPalette is only visible when opened. This is the ApplicationWindow structure:

ApplicationWindow {
	// ...
    FloatingActionButton {
        property string imageName: navPane.depth < 5? "/directions.png" : "/home.png"
        z: 1
        anchors.margins: 16
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        imageSource: "qrc:/images/"+iconOnPrimaryDarkFolder+imageName
        backgroundColor: primaryDarkColor
        onClicked: {
            if(navPane.depth < 5) {
                navPane.pushNextPage()
            } else {
                navPane.goToPage(1)
            }
        }
    } // FAB
	StackView {}
	PopupPalette {}
} // ApplicationWindow

Notice: the Image and onClick behavior are different: On Pages 1 to 4 FAB displays ‘directions.png’ and pushes the next page, where on Page 5 FAB displays ‘home.png’ and provides a short path to jump back to the root (Home):

FABs

ApplicationWindow –> StackView

StackView is one of the new Qt Quick Controls 2 to enable comfortable navigation through mobile apps. Our previous app was a simple one-page app, where all informations was displayed in one page. There are sometimes use-cases where you only need one page (plus Popups), but normaly there are more pages.

per example:

  • List of Orders
  • Tap on a Order to see the Details
  • From Details tap on a Part to see Article Details
  • From Article take a look at Inventory

So you’re opening a page on top of another one or go back to previous level: a typical stack.

Starting the app you want to display first informations, so normaly you’ll show the root object (List of Orders in example above). This first object is the initialItem. To place a page on top, you must push() another one. To go back you have to pop() the topmost Page.

Want to know how many Pages are already on your stack ? depth will let you know this, where a depth of 1 means, there’s only the root Page.

What kind of ‘Pages’ can you place on a StackView ? Item, Component or url.

stacked-pages-app always uses Components. Components are only definitions, no object will be instatiated before you create it and at the end of lifecycle you have to destroy a Component. Components are great for performance and memory – I always try to instatiate objects lazy: only when they’re needed.

StackView manages Components really cool: you don’t have to think about create() or destroy() – all is done by-magic from StackView itself.

By default push(component) will take the Component, create() the object and put it on top of your stack. Going back and pop() will automatically destroy the object and display the underlying page.

Take a look at StackView inside stacked-pages-app:

    StackView {
        id: navPane
        focus: true
        anchors.fill: parent
        initialItem: pageOne
		// ....
	} // navPane
    Component {
        id: pageOne
        PageOne {
        }
    } // pageTwo

    Component {
        id: pageTwo
        PageTwo {
        }
    } // pageTwo
	// ...

StackView will fill the complete space of parent (ApplicationWindow).

‘initialItem’ (pageOne) is a Component defined in Sources/pages/PageOne.qml and will be created automatically at startup.

Doing a push(pageTwo) will create the page from Sources/pages/PageTwo.qml, put it on top and will display that page.

Push() and pop() are done using default Transitions working well in Material styled apps, but you can customize Transitions.

Coming from BlackBerry10 Cascades ? StackView is like Cascades NavigationPane, where you also can push() and pop() pages. But if you’re using Cascades ComponentDefinition you must create() the objects by yourself and also destroy() as soon as pop() was done.

StackView has more power as push() / pop() and is very flexible, so I’m sure it’ll fit with your use-cases. You can push() an array of pages, which by default will mean all components will be put on the stack as placeholders and only the top most page will be created() and displayed.  Setting behavior as StackView.ForceLoad all components inside the array will be instantiated immediately. So it’s up to you what’s the best.

Also pop() can be used in different ways: pop() pops the topmost page, pop(null) goes back to the root page, pop(myPage) does a pop() until myPage will be reached. If pop() reaches a ‘placeholder’ placed by an array-push before, the object will be created automatically and then displayed.

One of my BlackBerry10 Cascades apps had the requirement to jump between some stacks with deep depth and to position to a specific one – having the possibility to push() an array of pages without creating all would have been very helpful.

From time to time you’ll need access to a specific page in your stack, you can get() an Item from  a given index, where the root page has index 0.

Attention: if get(index) points to a non-loaded page you’ll get ‘null’ or you must do a get(index, StackView.ForceLoad) to be sure that the object will be instatiated if not already done.

Pushing pages on top, all Items underneath stay alive. But – if you wish so – there are some tricks to get them unloaded wile push() something on top:

unload_item

You see: you have all the freedom what should be loaded or not and when to unload.

StackView – UI – Business Logic

We have seen that it’s easy to push() or pop() pages and StackView does creation and destruction. But in business apps in many cases there’s more to do if a page will be displayed or closed.

To demonstrate how you can do such kind of stuff, I implemented init() function to be called directly after push() and cleanup() function called when pop() is done for a page.

per ex Sources/pages/PageOne.qml:

Flickable {
    property string name: "PageOne"
    property string title: qsTr("Root Page in Stack of max 5")
	// ...
    Pane {
	    // ... content ...
	} // pane
    ScrollIndicator.vertical: ScrollIndicator { }
	//
    // called immediately after push()
    function init() {
        console.log(qsTr("Init done from One"))
    }
    // called immediately after pop()
    function cleanup() {
        console.log(qsTr("Cleanup done from One"))
    }
} // Flickable

But from where should these functions be called ? How to be sure that init() is called if StackView lazy creates a page from a placeholder (Component)  while doing a pop() ?

Coming from BlackBerry10 Cascades ? NavigationPane gives you a onPopTransitionEnded(page) signal to know that a pop() was called for the page. In Qt 5.7 there’s a Transition for pop(), but no signal when it’s done. Fortunately pop() and push() are giving you the page as return value.

This enables you to do something like this for push()

function pushOnePage(pageComponent) {
    var page = push(pageComponent)
    page.init()
}

Doing a pop() you have cleanup() that page and for the new currentItem you must check if it already was initialized or not:

function popOnePage() {
    if(navPane.depth == 1) {
        return
    }
    // check if target page already is on the stack
    var targetIsUninitialized = false
    if(!navPane.get(navPane.depth-2)) {
        targetIsUninitialized = true
    }
    var page = pop()
    if(targetIsUninitialized) {
        navPane.currentItem.init()
    }
    // do cleanup from previous page
    page.cleanup()
} // popOnePage

Take a look at the other functions I implement to solve jumping back some levels deeper or pushing arrays of pages.

From code above you can see that my pages have two properties added:

  • property string name
  • property string title

‘name’ is used to demonstrate find() and ‘title’ is bound to ToolBar as already described above.

StackView – Navigation Back and Forward

You can go different ways to navigate through the stack:

  • click on a (Raised or Flat) Button to push() or go back – pop()
  • click on ‘Back’ from ToolBar to go one level back – pop()
  • click on Android System ‘Back’ key to go one level back
  • click on FAB to go to the next page – push() – or back to the root

ToolBar, Buttons and FAB we have discussed before – what about the Android System ‘Back’ key ?

android_back

This is easy to implement:

    StackView {
        id: navPane
        focus: true
        anchors.fill: parent
        initialItem: pageOne
        // support of BACK key
        Keys.onBackPressed: {
            event.accepted = navPane.depth > 1
            popOnePage()
            if(navPane.depth == 1) {
                // perhaps ask user if app should really quit
                var page = navPane.get(0)
                page.cleanup()
            }
        }
		// ...

StackView gets focus: true and is listening for Keys.onBackPressed. If there are pages on top of root page, pressing the ‘Back’ key does a pop() to go one level back. If root page is reached, pressing Back again the app will be closed on Android. Could be a good idea to ask the user using a Popup if app really should be closed now.

Attention: Tapping on the options menu (three dots) from ToolBar will move the focus and StackView won’t catch the Back key anymore. To solve this, watch for onAboutToHide() from Menu() and reset the focus:

Menu (ToolButton top-right on ToolBar clicked):

Menu {
    id: optionsMenu
    x: parent.width - width
    transformOrigin: Menu.TopRight
    MenuItem {
        text: isDarkTheme? qsTr("Light Theme") : qsTr("Dark Theme")
        onTriggered: {
            themePalette = myApp.swapThemePalette()
        }
    }
    MenuItem {
        text: qsTr("Select Primary Color")
        onTriggered: {
            popup.selectAccentColor = false
            popup.open()
        }
    }
    MenuItem {
        text: qsTr("Select Accent Color")
        onTriggered: {
            popup.selectAccentColor = true
            popup.open()
        }
    }
    onAboutToHide: {
        appWindow.resetFocus()
    }
} // end optionsMenu

ApplicationWindow – resetFocus():

    // we can loose the focus if Menu or Popup is opened
    function resetFocus() {
        navPane.focus = true
    }

Now StackView (navPane) gets the focus back.

StackView – Shortcuts BlackBerry PRIV

There are even more ways to navigate if your device has a physical keyboard connected:

  • BlackBerry PRIV (Android Slider) with hardware keyboard
  • Bluetooth Keyboard attached to Android or iOS device

Here’s the BlackBerry PRIV keyboard:

priv_shortcuts

Attention: with Qt 5.7 RC I had some trouble using Space and Shift+Space – sometimes also the on-clicked from a visible Button was executed. Will do a Bugreport. So I changed to ‘n’ for NEXT and ‘p’ for PREVIOUS

You can use ‘Shortcut‘:

  • Typing 1…5 or equivalent ‘w’ ‘e’ ‘r’ ‘s’ ‘d’ goes to Page 1…5 back or forward
  • Typing ‘SPACE’ or ‘n’ goes to next page (push) until page 5 reached
  • Typing Shift + Spacebar or ‘p’ goes to previous page (pop) until root page reached

Using Shortcut is easy done – here are the Shortcuts for GoTo Page 4 and GoTo Next Page as example:

StackView {
        // ...
        Shortcut {
            sequence: "s"
            onActivated: navPane.goToPage(4)
        }
        Shortcut {
            sequence: "Alt+s" // keyboard sequence for '4'
            onActivated: navPane.goToPage(4)
        }
		// ...
        Shortcut {
            sequence: " "
            onActivated: navPane.pushNextPage()
        }
		// ...

Shortcuts are defined at StackView and because all pages pushed on the stack are owned by StackView, the Shortcuts are always recognized.

For Business apps it’s important to provide smooth workflow to users and using Shortcuts can improve UX much.

StackView – Workflow

The implemented push() – pop() workflow from stacked-pages-app is something like this snippet:

workflow

Feel free to take a look at the Button’s onClicked() functionality to see what happens under the hood.

Also each page has a short description, per ex. here’s page 5:

android_page_05

Summary

This app is only using some of the powerful features StackView provides you for navigation through a stack of pages. Remember: this is not a production-ready app.

StackV iew is not the only way to navigate – another way is using a SwipeView.


← Back (One Page App)

→ Next Article (Swiped Pages App)

⇐ Home (Overview / Topics)

 

Edited: 2016-06-05: Qt 5.7 RC

Overview

This is our first sample app to demonstrate HowTo use Material design and Qt 5.7 for mobile x-platform development. This app is not a real-life-app – there’s nothing about performance, dynamic-loading, data-binding, caching or so. Stay tuned – there will be more sample apps next weeks to demonstrate all what you need to develop x-platform business apps for (BlackBerry 10), Android, iOS, (later also Windows10).

The goal of this app is to demonstrate:

  • new Qt Quick Controls 2 (qt.labs.controls)
  • customizing Controls
  • customer-specific layouting
  • dark and light theme
  • Material colors and fonts
  • Icons for different DPI
  • internationalization
  • tips to structure your project

You should have installed Qt 5.7 Beta.

New to Qt 5 development ? Please read my blog series from the beginning to learn some basic stuff and to understand this app.

This app is a simple one-page app without navigation and is tested on Android (BlackBerry PRIV, Android 6.0.1) and iOS (iPhone 6s, iOS 9.3)

Hint: there are many ways to manage colors, fonts, opacity, images – perhaps this app will help you to find your way.

one_x_a_01

The Sources

This app can be downloaded as Open Source from  github: https://github.com/ekke/one_page_x

Project structure and .pro

Coming from another IDE (like Eclipse Momentics) you’ll soon recognize that Qt Creator projects are not synced with the underlying file system. So there are some more manual steps to have an easy access to your files.

proj_structure

Adding C++ sourcefiles or headers to your project will automatically create an entry under SOURCES or HEADERS in your .pro (right part) and also show this in your project structure (left part). Same for Resource files (*.qrc) – they’re automatically part of your .pro and project structure.

If you already have downloaded the sources from github you can try to open the content tree of the qrc file. It’s not really comfortable for a project with many files and folders.

In this blog entry I described HowTo manage translations. LUPDATE and LRELEASE are looking for translatable strings from C++ and QML. C++ sources are already found in .pro under SOURCES. To make QML files available as SOURCES for translation, the lupdate_only section adds QML files to SOURCES for translation without confusing the compiler. A nice side effect of this is getting the QML files also placed under SOURCES in your project structure. See how using wildcards makes it easy to manage in .pro.

There are some more files you want to have easy access: images, translations, textfiles. Placing these files under OTHER_FILES inside .pro makes them visible at the left side.

Now our project structure inside Qt Creator looks similar to the underlying file system.

Hint: adding new files to resources sometimes doesn’t make them visible at the left side immediately. To trigger the update open the .pro, add a space and save – then it appears – sometimes needs a second or so. (TODO: bugreport)

main.cpp

main.cpp enables High DPI Scaling, Material style and translation:

int main(int argc, char *argv[])
{
    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    qputenv("QT_QUICK_CONTROLS_STYLE", "material");
    QGuiApplication app(argc, argv);
    QTranslator translator;
    if (translator.load(QLocale(), QLatin1String("one_page_x"), QLatin1String("_"), QLatin1String(":/translations"))) {
        app.installTranslator(&translator);
    } else {
        qDebug() << "cannot load translator " << QLocale::system().name() << " check content of translations.qrc"; 
    } 
    ApplicationUI appui; 
    QQmlApplicationEngine engine; 
    QQmlContext* context = engine.rootContext(); 
    context->setContextProperty("myApp", &appui);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

main.cpp also gives access to ApplicationUI from QML via context ‘myApp‘.

Application UI (C++)

Coming from BlackBerry 10 Cascades ? There’s also an ApplicationUI managing all app-specific stuff – so I have named it same here 😉 Let’s take a look at the header, where you’ll find some Q_INVOKABLE methods:

     Q_INVOKABLE
     QStringList swapThemePalette();

     Q_INVOKABLE
     QStringList defaultThemePalette();

     Q_INVOKABLE
     QStringList primaryPalette(const int paletteIndex);

     Q_INVOKABLE
     QStringList accentPalette(const int paletteIndex);

     Q_INVOKABLE
     QStringList defaultPrimaryPalette();

     Q_INVOKABLE
     QStringList defaultAccentPalette();

Q_INVOKABLE marks a method to be invokable from the UI (QML).

Invoke Application UI from QML

A QStringList from C++ is automatically mapped to a JavaScript Array in QML:

ApplicationWindow {
    ....
    property variant primaryPalette: myApp.defaultPrimaryPalette()
    property color primaryLightColor: primaryPalette[0]
    property color primaryColor: primaryPalette[1]
    property color primaryDarkColor: primaryPalette[2]
	....

This app is a playground for Material Colors, Font sizes and more. To make this easy I created some constants you can access via the Q_INVOKABLE methods.

UI Constants (C++)

Google Material Design Guide provides  a default Color Palette.

Qt also recommends to use one of the predefined default colors as the primary color and another one as the accent color.

For each predefined color there are also some predefined shades of these colors. Following the Google Material Design Guide there are some use-cases where it makes sense besides the primary color also to use a something-lighter and a something-darker primary color.

Inspired by this page: MaterialPalette.com I’m using shade 500 as primary color, shade 100 as primary light color and shade 700 as primary dark color, per example for Material.Red:

google_color_red

Qt 5.7 per ex. uses the primary color as background for a ToolBar and (starting with Qt 5.7 RC) also “knows” the correct color for Text (Material.foreground) – in this case: White.

From the screenshot above you can see that using a lighter or darker primary color the foreground color can be different.

But there’s not only Text on primary colors – you can place Icons there.

Google recommends to use white or black images (depending from the used color)  together with a specific opacity for images and text.

To make it easier to manage I created a palette for each predefined Material color:

  • primaryLightColor (shade 100)
  • primaryColor (shade 500)
  • primaryDarkColor (shade 700)
  • textOnPrimaryLight (white or black text color)
  • textOnPrimary (white or black text color)
  • textOnPrimaryDark (white or black text color)
  • iconOnPrimaryLightFolder (images/dark or images/white)
  • iconOnPrimaryFolder (images/dark or images/white)
  • iconOnPrimaryDarkFolder (images/dark or images/white)

For Material.Red here are the values:

    static const QStringList materialRed {
	    "#FFCDD2", "#F44336", "#D32F2F", 
		"#000000", "#FFFFFF", "#FFFFFF", 
		"black", "white", "white"};

Now it’s easy to understand the code above (Invoke Application UI from QML): myApp.defaultPrimaryPalette() gives you an Array and per ex. primaryPalette[2] provides the primary dark color (#D32F2F). You can use these palettes for primary and accent colors.

Qt 5.7 also supports Material Themes dark and light and automagically knows the text color to be used: white on dark theme and black on light theme. But the real-life story is more difficult and there are some situations where you have to set a color or opacity depending from dark or light theme.

So I also created a dark and light palette containing this information:

static const QStringList darkPalette{"#FFFFFF", "#424242", 
    "1.0", "0.70", "0.12",
	"1.0", "0.3", "white", "1"};
static const QStringList lightPalette{"#000000", "#FFFFFF", 
    "0.87", "0.54", "0.12",
	"0.54", "0.26", "black", "0"};

Now it’s easy always to use the correct color, opacity and to know if the icons should be from dark or white images folder.

That’s all from C++ – let’s take a look at the UI side of the app.

ApplicationWindow (main.qml)

The root object of our Qt Quick 2 app is the ApplicationWindow{}. Developing mobile apps you don’t need to set width and height, because the ApplicationWindow always grabs the total available space. We also don’t need to set the title, because we’re using a header containing the ToolBar.

It’s important to set visible: true – otherwise you’ll only get an empty screen.

The structure of our app looks like this:

// imports
ApplicationWindow {
    visible: true
    // properties
    header: {}
    // optional footer {}
    Flickable {}
    // functions
    Popup {}
}

imports:

import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtGraphicalEffects 1.0
import "common"
import "demo"

Taking a look at the imports you’ll notice two project-specific imports: “common” and “demo” – both are folders containing QML files:

  • common: QML files used from some apps – you’ll find them also in our next apps
  • demo: QML files used to demonstrate some use-cases

It’s always a good idea to structure your QML files – this will make it easier to find and to manage them.

properties:

    // primary and accent properties:
    property variant primaryPalette: myApp.defaultPrimaryPalette()
    property color primaryLightColor: primaryPalette[0]
    property color primaryColor: primaryPalette[1]
    property color primaryDarkColor: primaryPalette[2]
    property color textOnPrimaryLight: primaryPalette[3]
	...
    Material.primary: primaryColor
    Material.accent: accentColor
	...
    // theme Dark vs Light properties:
    property variant themePalette: myApp.defaultThemePalette()
    property color dividerColor: themePalette[0]
    property color cardAndDialogBackground: themePalette[1]
    property real primaryTextOpacity: themePalette[2]
	...
    property int isDarkTheme: themePalette[8]
    onIsDarkThemeChanged: {
        if(isDarkTheme == 1) {
            Material.theme = Material.Dark
        } else {
            Material.theme = Material.Light
        }
    }
	...

ApplicationWindow is the root UI Object and will always be “known” from UI Controls down the tree of controls. It makes sense to place properties you need from your UI controls here as part of the ApplicationWindow. You’ll find there properties from primary palette, accent palette, theme palette and more.

If not using the default values, you should also set some Qt 5.7 Material properties:

  • Material.primary
  • Material.accent
  • Material.theme

Hint: There are also other ways to set these values – please take a look at Qt docs: https://doc-snapshots.qt.io/qt5-5.7/qtquickcontrols2-material.html

functions:

    function switchPrimaryPalette(paletteIndex) {
        primaryPalette = myApp.primaryPalette(paletteIndex)
    }
    function switchAccentPalette(paletteIndex) {
        accentPalette = myApp.accentPalette(paletteIndex)
    }

Functions from root (ApplicationWindow) can also be reached from other QML objects created on top. There are only two functions in this app to switch the primary or accent palette.

ApplicationWindow –> header / ToolBar

header‘ can be used for an application-wide TitleBar. For this application I decided only to have a simple header with a title text and an option menu – all placed on primary color. To make it easy to reuse this in other apps I created a SimpleTextTitle.qml in /commons. This makes the header easy to define inside the ApplicationWindow:

    header: SimpleTextTitle {
        text: qsTr("A simple 1 - Page APP")
    }

/commons/SimpleTextTitle.qml:

ToolBar {
    id: titleToolBar
    property alias text: titleLabel.text

    RowLayout {
        focus: false
        spacing: 6
        anchors.fill: parent
        LabelTitle {
            id: titleLabel
            text: "ekke"
            leftPadding: 16
            elide: Label.ElideRight
            horizontalAlignment: Qt.AlignHCenter
            verticalAlignment: Qt.AlignVCenter
            color: textOnPrimary
        }
        ToolButton {
            Image {
                id: buttonImage
                anchors.centerIn: parent
                source: "qrc:/images/"+iconOnPrimaryFolder+"/more_vert.png"
            }
            onClicked: {
                optionsMenu.open()
            }
            Menu {
                id: optionsMenu
                x: parent.width - width
                transformOrigin: Menu.TopRight
                MenuItem {
                    text: isDarkTheme? qsTr("Light Theme") : qsTr("Dark Theme")
                    onTriggered: {
                        themePalette = myApp.swapThemePalette()
                    }
                }
                MenuItem {
                    text: headlineColoredPrimary? qsTr("Headline Accent Color") : qsTr("Headline Primary Color")
                    onTriggered: {
                        headlineColoredPrimary = !headlineColoredPrimary
                    }
                }
            } // end optionsMenu
        } // end ToolButton
    } // end RowLayout
} // end ToolBar

The header in fact is a ToolBar – one of the new Qt Quick Controls 2. A ToolBar can be placed as header or footer, where ToolBar.Header position is default position for Material styled apps. Background of a ToolBar is Material.primary color. To place some controls inside a ToolBar, best way is to use a RowLayout. Our RowLayout contains a Label and a ToolButton filling the parent (ToolBar) completely.

toolbar

ToolButton is a Button to be placed inside a ToolBar. In this case the ToolButton is presented as an Image. Please notice how the ‘source‘ is constructed using the iconOnPrimaryFolder – a property from our root object (ApplicationWindow). From our primary palette we know if the image is placed inside the ‘/black‘ or ‘/white’ folder for the selected primary color. Clicking on the Button opens a Menu:

toolbutton_menu

From this Menu you can switch the Theme between dark and light and you can switch the color for all Headlines between Accent and Primary color. Switching the Theme is done invoking a methode from ‘myApp’ (ApplicationUI.cpp): myApp.swapThemePalette() – the return value (QStringList) is set as a property in ApplicationWindow: themePalette.

The Label itself is a customized Label:

/commons/LabelTitle.qml:

Label {
    Layout.fillWidth: true
    font.pixelSize: fontSizeTitle
    opacity: opacityTitle
}

It’s important to set Layout.fillWidth: true. This will cause the Label to use as much space as possible, so the ToolButton will be placed at the right side. You can test this changing the orientation from Portrait to Landscape and back.

Coming from BlackBerry10 Cascades and looking for a TitleBar ? Qt 5.7 ApplicatrionWindow -> header -> ToolBar is what you need.

Hint: I’m using all the properties from ApplicationWindow without a prefix. This works because in my app the names are unique. If you cannot guarantee this, prefix the porperties, per ex. appWindow.textOnPrimary.

ApplicationWindow –> Popup

Popup is also new from Qt Quick Controls 2. This Popup contains a list of all Material colors and allows you to select another color. Another blog article will take a look at Lists in detail. For now you can take a look at /common/PopupPalette.qml. Inside the ApplicationWindow the Popup is defined this way:

    PopupPalette {
        id: popup
    }

Hint: Coming from BlackBerry 10 Cascades ? To add Controls like Dialogs, Popups, Toasts, … you have to place them inside attachedObjects[] – in Qt 5.7 you don’t have to do this. Qt “knows” that this is a control which must be opened to appear.

ApplicationWindow –> Flickable (the content)

It’s a good idea to use a Flickable as outer Control if you want to flick the content with your fingers. Try it out with or without a Flickable to “feel” the difference. Having an extra Flickable was a new concept for me.

What’s inside our Flickable in this app ?

Flickable {
        id: flickable
        contentHeight: root.implicitHeight
        anchors.fill: parent
        Pane {
            id: root
            anchors.fill: parent
            ColumnLayout {
                anchors.right: parent.right
                anchors.left: parent.left
                // ... more or less complex controls ...
            } // col layout
        } // root
        ScrollIndicator.vertical: ScrollIndicator { }
    } // flickable

For a newcomer it was not easy to figure it out HowTo set correctheight. From the abstract above you’ll see how it works:

  • The Flickable must know the contentHeight
  • ContentHeight will be calculated by the inner Pane’s implicitHeight
  • The Flickable has to fill the parent (ApplicationWindow)
  • The inner Pane also has to fill the parent (Flickable)

Don’t forget to set a vertical ScrollIndicator to make the Flickable scrollable.

The inner control containing all the content is a Pane: a new control from Qt Quick Controls 2. A Pane knows the current Theme and Style and will get the correct background automagically from Qt Material styling. It’s a good idea to use Pane instead of Rectangle if you need a container using correct background.

Hint: Pane has a default padding. If for some reason you have to use a Pane inside a Pane and want to align the controls of both you must set padding to 0 for nested Panes.

Placing controls row by row inside your Pane you should use a ColumnLayout anchored at left and right on parent (Pane). Using a ColumnLayout the content will be rearranged if orientation of your device is changed from Portrait vs Landscape.

If a Row contains some controls side by side, you should use a RowLayout for this.

Hint: Coming from BlackBerry10 Cascades ? ColumnLayout is like StackLayout with orientation TopToBottom and RowLayout is like StackLayout with orientation LeftToRight.

Customer – specific Layouting

I’m developing business apps for SMB’s and Enterprises where many usecases will be to display some data. Data entry will be part of another sample app. Fields should be aligned and automatically rearranged if orientation changes. Let’s imagine we must solve these user-requirements:

Some rows of data containing a Label at the left side, another Label or Switch or Checkbox and at the right side a transparent or colored bar. First Label should occupy 1/3, second control 2/3 of available width automatically adjusted if changing orientation.

biz_row_layout

Here’s how it looks in Portrait:

portrait_fields

and in Landscape:

landscape_fields

All customized controls can be found at /demo/*.qml – here’s the customized control to display two Labels and the colored bar:

/demo/LabelLabelBarRow.qml:

RowLayout {
    id: labelLabelRow
    property alias text1: label1.text
    property alias text2: label2.text
    property alias barColor: rightBar.color
    // implicite fillWidth = true
    spacing: 20
    LabelBodySecondary {
        id: label1
        leftPadding: 10
        Layout.preferredWidth : 1
        wrapMode: Text.WordWrap
        text: ""
    }
    LabelBody {
        id: label2
        leftPadding: 2
        Layout.preferredWidth: 2
        wrapMode: Text.WordWrap
        text: ""
    }
    Rectangle {
        id: rightBar
        anchors.right: parent.right
        anchors.rightMargin: 6
        Layout.fillWidth: true
        Layout.minimumWidth: 10
        Layout.maximumWidth: 10
        implicitHeight: 40
        color: "Transparent"
    }
} // row layout

There are three properties (the API) to set the text of first and second label and also to set the color of the bar. First Label itself is a customized Label from /common styled as ‘secondary text’, second Label uses ‘primary text’. HowTo manage the width relation 1 : 2 ?

Coming from BlackBerry10 Cascades ? There’s a spaceQuota property at StackLayout to divide available space and I figured out that I can do something similar in Qt 5.7 🙂

Please follow these 3 steps:

  1. Use a RowLayout
  2. Set Layout.preferredWidth to ‘1’ for the first Label and ‘2’ for the second Label
  3. The colored or transparent bar gets a fixed size of 10

Now all is done by RowLayout 🙂

fillWidth is true by default, so the Layout will use all available width. Rectangle (colored bar) will be placed at the right side (anchors.right: parent.right) Now the two Labels must fill the remaining space. RowLayout uses the preferredWidth to calculate. Because preferredWidth is small it will be extended and we’ll get the 1: 2 relation.

LabelLabelBarRow can be used this way:

LabelLabelBarRow {
    text1: qsTr("Name ")
    text2: qsTr("Jane Doe")
    barColor: Material.accentColor
}

Please take a look at the other controls from /demo

Internationalization / Quantities

Qt makes is easy to translate your strings. If you haven’t done – please read my article about this topic.

You should always define your strings as translatable as seen here in my code using qsTr(“”)

Here’s how you can translate text with quantities:

LabelSwitchBarRow {
    id: multiSwitch
    property int count: checked ? 2 : 1
    text: qsTr("Translate Multi")
    switchText: qsTr("%1 piece(s)","",count).arg(count)
}

I’m using the Switch to change a property (‘count’) and I’m using this property for translations to get this in german:

quantity_switch

The translation done in Qt Linguist:

linguist_quantities

Show / Hide Fields

Displaying business informations in many cases you have to hide / show some of the content.

show-hide

Here’s one simple way to do this:

LabelSwitchBarRow {
    id: addressSwitch
    text: qsTr("Address")
}
Pane {
    id: addressBlock
    // implicite padding: 6
    leftPadding: 0
    rightPadding: 0
    anchors.left: parent.left
    anchors.right: parent.right
    visible: addressSwitch.checked
    ColumnLayout {
        anchors.right: parent.right
        anchors.left: parent.left
        LabelLabelBarRow {
            text1: qsTr("City")
            text2: qsTr("Munich")
            barColor: "Red"
        }
        LabelLabelBarRow {
            text1: qsTr("Street")
            text2: qsTr("Odeonsplatz")
            barColor: "green"
        }
        LabelLabelBarRow {
            text1: qsTr("Zip")
            text2: qsTr("80000")
            barColor: "transparent"
        }
    } // addressBlock col layout
} // addressBlock

I’m using a nested Pane as a Container for the fields I want to show under specific conditions – in this case if the addressSwitch is ON.

Simply bind the visibility of the Pane to checked stae of the Switch: visible: addressSwitch.checked – that’s all.

Don’t forget to set the Padding to 0 for the nested Pane. Don’t use a Rectangle: you will loose the theme- and style-specific background !

Switching the Theme

Switching the Theme can be done by ToolButton – Menu as described above.

To verify what happened, I’m displaying some of the theme-dependent colors and opacity values:

Light theme vs Dark Theme:

theme_switch

Selecting Primary and Accent Colors

Current selected primary colors (primary light, primary, primary dark) and accent color are displayed here:

primary_accent_colors

Tap on primary to get a list of Material predefined colors:

select primary

Color names are translated.

Tap on accent to get same list of colors, but also the primary color will be displayed to make it easier to select:

select accent

To display the list a Popup is used. (see above: Popup)

To open the Popup we need a MouseArea to detect the “Tap” on a Rectangle:

    Rectangle {
        Layout.fillWidth: true
        width: parent.width
        height: 40
        color: appWindow.primaryColor
        Label {
            leftPadding: 6
            anchors.verticalCenter: parent.verticalCenter
            wrapMode: Text.WordWrap
            text: qsTr("Primary Color: %1 --- Tap to edit","").arg(primaryColor)
            color: appWindow.textOnPrimary
        }
        MouseArea {
            anchors.fill: parent
            onClicked: {
                popup.selectAccentColor = false
                popup.open()
            }
        } // mouse
    }

Fonts, Sizes, Opacity

Google Material Design Guide – Typography – gives you all you need to know about font sizes, color and opacity.

To make it easy to use for your own app I added some properties at ApplicationWindow:

    // font sizes - defaults from Google Material Design Guide
    property int fontSizeDisplay4: 112
    property int fontSizeDisplay3: 56
    property int fontSizeDisplay2: 45
    property int fontSizeDisplay1: 34
    property int fontSizeHeadline: 24
    property int fontSizeTitle: 20
    property int fontSizeSubheading: 16
    property int fontSizeBodyAndButton: 14 // is Default
    property int fontSizeCaption: 12
    // fonts are grouped into primary and secondary with different Opacity
    // to make it easier to get the right property,
    // here's the opacity per size:
    property real opacityDisplay4: secondaryTextOpacity
    property real opacityDisplay3: secondaryTextOpacity
    property real opacityDisplay2: secondaryTextOpacity
    property real opacityDisplay1: secondaryTextOpacity
    property real opacityHeadline: primaryTextOpacity
    property real opacityTitle: primaryTextOpacity
    property real opacitySubheading: primaryTextOpacity
    // body can be both: primary or secondary text
    property real opacityBodyAndButton: primaryTextOpacity
    property real opacityBodySecondary: secondaryTextOpacity
    property real opacityCaption: secondaryTextOpacity

There are also customized Labels at /common, per ex.

/common/LabelCaption.qml:

Label {
    Layout.fillWidth: true
    font.pixelSize: fontSizeCaption
    opacity: opacityCaption
    font.capitalization: Font.AllUppercase
}

Running the app you can see them all to get an impression which to use for a specific use-case:

fonts

Icons – High DPI Support

Please read my article about High DPI support if not already done. My two test devices:

  • Android (PRIV), 540 dpi –> scaling factor: 3.5
  • iOS (iPhone 6S), 326 dpi –> scaling factor: 2.0

Images to support High DPI are named similar to iOS:

image_naming

To verify that Qt detects the correct size, I added some special images:

test@1_4x

HowTo get the Image:

Image {
    opacity: iconActiveOpacity
    source: "qrc:/images/"+iconFolder+"/test.png"
}

Here’s the result for BlackBerry PRIV: scaling factor 3.5 tries to find the @4x.png and for the iPhone 6s: scaling factor 2 tries to find the @2x.png:

icons

Using the app you can test this for your device.

Icons in Material styled apps can have different state: active or inactive and you can use the icons uncolored or colored with primary or accent.

Default Icon size is 24, but 18×18, 36×36 and 48×48 are also common sizes used. To get a feeling about the sizes I added them to this sample app.

Light theme:

icons_light

Dark theme:

icons_dark

I structured the Icon folders into black / white and sizes to make it easy to construct sources:

icon_folders

Take a look at the source to see how Icons are displayed.

Custom Control FAB (Floating Action Button)

Qt Quick Controls 2 and Material style were introduced as TechPreview in Qt 5.6 and now are part of Qt 5.7

Not all UI Controls are provided yet, but it’s easy to customize existing ones. I missed the Floating Action Button – thx @jpnurmi helping me to customze:

common/FloatingActionButton.qml:

Button {
    id: button
    // image should be 24x24
    property alias imageSource: contentImage.source
    // default: primaryColor
    property alias backgroundColor: buttonBackground.color
    property bool showShadow: true
    contentItem:
        Item {
        implicitHeight: 24
        implicitWidth: 24
        Image {
            id: contentImage
            anchors.centerIn: parent
        }
    }
    background:
        Rectangle {
        id: buttonBackground
        implicitWidth: 56
        implicitHeight: 56
        color: primaryColor
        radius: width / 2
        opacity: button.pressed ? 0.75 : 1.0
        layer.enabled: button.showShadow
        layer.effect: DropShadow {
            verticalOffset: 3
            horizontalOffset: 1
            color: dropShadow
            samples: button.pressed ? 20 : 10
            spread: 0.5
        }
    }
}

There’s also a Mini FAB. There’s a property to show the shadow, wich is per default ON for a normal FAB and OFF for a Mini FAB. Setting different colors the FAB looks like this:

fab

Creating the FAB is easy done:

        FloatingActionButton {
            imageSource: "qrc:/images/"+iconOnPrimaryLightFolder+"/person.png"
            backgroundColor: primaryLightColor
        }
        FloatingActionButton {
            imageSource: "qrc:/images/"+iconOnPrimaryFolder+"/person.png"
        }

Notice the use of matching folder names to get a black or white image depending on the color.

Coming from BlackBerry10 Cascades ? A Floating Action Button should be used instead of an SignatureAction in Cascades.

Summary

While exploring this app we learned HowTo use Material colors, fonts and themes, we used some of the new Qt Qucik Controls 2 and did some first customization, did some C++ <-> QML communication (Q_INVOKABLE) and strcutured our assets (images, translations).

Hopefully I could motivate you to try out Qt 5.7 and new Qt Quick Controls 2 for mobile x-platform development.

I’ll go on and next app will demonstrate some first Navigation with Qt Quick Controls 2 StackView.

Step by step next weeks I’ll go through all use-cases needed to be solved for business app development.

All is new to me, too so I’m curious to see how well it will work 😉


← Back (Resolution Independence)

→ Next Article (Stacked Pages APP)

⇐ Home (Overview / Topics)