Archives For Demo Apps

Bluetooth LE for mobile Business APPs

As you know I’m developing mobile business apps for Enterprises and SMBs. There are many use-cases where Bluetooth LE (BTLE) devices can help to streamline the workflow. Last years I already have done some BT LE powered APPs on BlackBerry 10.

In the meantime BlackBerry devices are running Android and most of my enterprise customers are moving from BB10 to Android, some to iOS, some both.

Thanks to QtQuickControls2 I found my way into Android and iOS development using Qt.

After some weeks of exploring and developing I’m happy what can be done with Qt for BT LE APPs on Android and iOS. A big thx to all the Qt developers !

There are also some Bluetooth LE Example APPs avilable: http://doc.qt.io/qt-5/qtbluetooth-index.html#examples

What was missed ? A real-life APP built with QtQuickControls2.

To help others and to be able to demo features to customers I decided to develop a BTLE example APP and to make it open source at Github.

The good thing to have this app: it’s now easier for me to test the features inside a special APP instead inside more complex mobile business APPs I’m normaly working on 😉

ekkes BTLE example

  • finds BT LE devices nearby
  • lets you explore Services and Characteristics and try out Read, Write, Notify
  • covers all errors and signals to get a robust APP. if you run into an issue please let me know
  • for known Device Types (Addimat Lock, NFC Reader, HeartRate Monitor)
    • remembers last used device address to auto-connect after restart APP
    • allows to connect to more then one BT LE Devices: per ex. get Notifications from Lock and NFC Reader at the same time
    • provides settings for Battery Level and more
    • enables mapping of users / persons to Lock Key or NFC Tag
    • manages detailed states: connect / all needed services + characteristics available / disconnect
    • easy re-connect

I’ll divide this article into 2 parts:

  1. documentation and screenshots HowTo USE the APP
  2. analyzing APP project structure and src and HowTo port BT functionality into your own app

The 2nd part will take some time, because I’m heavy loaded with customer projects.

Meet ekke at Conferences

I’ll speak about more complex mobile business APPs at Qt World Summit 2017 in Berlin

You are from Germany ? Don’t miss my talk at W-JAX: Bluetooth LE in mobile Business Apps

Thx John and Martin

Before starting with the first part I have to thank John Murray and Martin Woolley. John and Martin helped me to master first steps into Bluetooth and NFC on BlackBerry 10 / Cascades (Qt 4.8).

Download from the Stores

Run on Android 4.3 or greater for Phones and Tablets from Google Play Store:

google-play-badge

Run on iOS 8.2 or greater for iPhone or iPads from Apple App Store:

app_Store_Badge

Run on Amazon Fire OS from Amazon App Store:

amazonappsbadge

Here‘s the link to german Amazon App Store:

ekkes BTLE example APP

This APP uses same customized QtQuickControls2 as my other demo APPs, also same Images and follows same project rules.

 

Besides the Drawer Navigation there’s a Bottom Navigation Bar for easy access to the most important parts of the APP.

Here’s the Drawer:

  • wondering about the gray area where normaly it’s only dimmed so you can see what’s behind ? This is because of workarounding a Qt 5.9 Bug.

APP Features and Devices

  • Scanner (discover devices nearby, explore Services and Characteristics
  • HeartRate (HeartRate Monitor)
  • Lock (Waiter Lock with magentic keys)
  • NFC Tag (NFC Reader via BT LE)
  • Help Page
  • About Page

Main goal of this APP is to help other devs with Qt BT LE for mobile. All sources are available at Github.

ekkes BTLE example APP is also my workbench testing new Device Types and HowTo integrate into my apps.

As first I included a HeartRate Monitor, because I could compare what happens from existing Qt BT LE example apps.

Then I added Addimat Lock

Probably you have seen something like this at Restaurants where waiters Log-In / out at Cash Register using magnetic keys.

Some of my customers are using Addimat Lock for Teams sharing an Android Tablet or iPad.

Each key has a unique ID and is mapped to the User. Now switching between users is fast and secure.

Most Locks are only connected via USB or RS232 – so I was happy to find Addimat with Bluetooth LE Custom Services.

Hint: It’s important to use a Custom Service and not connect as a HID (like BT Keyboards) because then under iOS the virtual keyboard disappears.

More Info on Addimat can be found here.

As next I added a NFC Reader

NFC Tags are used per ex. to identify attendees of Conferences / Events / Trainings:

NFC works well on most Android Smartphones with an integrated NFC chip. But when it comes to Tablets even high end Tablets as Goggle Pixel C miss the NFC Chip. iOS devices have a NFC Chip integrated, but Apple blocked acces for developers in the past. Starting with iOS11 developers get partly access to NFC Tags: you can read NDEF Records etc. But in all business APPs I developed last years where NFC was used to identify persons or locations, only the UUID was used.

So for some customers looking for a way to register from a mobile device perhaps an extra NFC Reader conencted via Bluetooth LE is the only solution.

Finally I found a device with some help from CSG:

NFC Reader can be customized and integrated into custom cases.

 

Android, iOS, Smartphones, Tablets

The APP is running on Android Smartphones and Android Tablets.

Screenshot from Google Pixel C Tablet:

Using NFC Reader you add NFC to high end Google Pixel C Tablet

 

Screenshot Amazon Fire HD8 Tablet:

ekkes BTLE example also is running on iOS.

Screenshot from iPhone:

and Screenshot from iPad Mini2:

Discover and explore BT LE devices

Hint: Beacons are not supported yet – have to learn HowTo deal with Beacons from Qt.

Selecting first time the Scanner, ekkes BTLE example APP discovers all BT LE devices nearby. While searching for devices there’s a progress view. (see Fire HD 8 screenshot above). Your devices isn’t listed ? Switch it off and on – this helps in most cases. Then hit theblue ‘+‘ Button to discover again and add devices currently not listed. Tap on the red ‘x‘ Button to clear the list and start again.

In the list you get: RSSI, name, MAC Address and current state.

From the list you can Connect or Disconnect devices – tapping on a row will give you a Popup Menu:

First option “Select” is only available for known device types (HeartRate, Lock, NFC Reader).

Tapping on “Select” will select this device as your default device, store MAC address in settings, open the special Page and do a Connect.

“Explore Services” opens a detail Page wit all the Services.

“Connect / Disconnect” does exactly this

Explore Services

Here are all Services found for Addimat Lock:

You get the name and Service UUID. Tap on a row to see the Characteristics of this Service.

Characteristics

Battery Level Characteristics from Addimat Lock:

Battery Level Value can be Read.

Characteristics of Addimat Custom Services:

Custom Characteristics can be Read to get the current Value or you can subscribe for notifications.

Tap on a row to see the details or to interact:

Tap on the red “R” Button to read the current value.

Tap on the red “N” Button to subscribe to the values. There’s a switch if all values should be collected or if you only want to see the last one.

ekkes BTLE example APP displays values as String, HEX or INT. Please take a look at the documentation of your device or Bluetooth GATT service. Sometime values are not human readable and must be analyzed by some business logic.

If a Characteristic has “Write” permissions you’ll see a “W” Button. Tapping on the “W” allows you to enter String or HEX values and to write to the Device:

HeartRate Monitor

Selecting a HR Device a special Page opens:

The header shows you name, MAC address and state and you can Connect, Initialize Services or Disconnect from there.

The “Play” Button subscribes Notifications and you’ll see the HR values:

Addimat Lock Manager

Addimat Manager Page works similar.

Attention: Addimat Lock can work as HID or as BT LE with custom Service. iOS doesn’t know about the custom BT LE Service, so every time you connect to Addimat, iOS tries to connect as HID. There’s an easy way to get rid of this message:

First time enter 307630:

Then open Addimat from Settings – Bluetooth and tap in ‘I’:

Then tap on “Ignore Device”:

Now Addimat is only used as a BT LE Device with custom Services and Characteristics.

Here the Notifications about magnetic keys plugged-in or out are already subscribed (Progress Bar is running):

No User was found for this Addimat Key. In a real-life app you would check the ID against a REST service or a local database.

To simulate this you can add the Mapping between Key ID and User.

Please open ‘Mapping’ from “Settings Menu”:

While the Configure – Popup is open, the values from the keys are still received, but now the APP knows that you’re configuring and you can enter the Name. Do this for all your Keys. (In a real life APP this would be done at Server site and the APP does a Login with Key ID as credentials)

Next time you plug-in the Key, ekkes BTLE example APP “knows” the user:

Select “Settings” from Menu above to see the default device address or to change battery levels:

ekkes BTLE example APP notifies you if the device is disconnected:

NFC Reader

Here’s the NFC Reader Page – Notifications are subscribed to get the NFC Tag IDs:

NFC Reader allows you to “Write” values to the buzzer and LED. Commands Buttons “BUZZER” and “LED” lets you test this.

Here’s the Menu you get from “Buzzer”:

and here’s the Menu you get from “LED”.

ekkes BTLE example APP also uses the LED: if a Tag ID was found, the LED becomes green, if not the LED becomes red.

You can map the NFC Tag IDs same way as for the Addimat magnetic Keys:

Holding the NFC Tag on the NFC Reader, ekkes BTLE example APP welcomes the attendee:

 

stay tuned for part 2 of the documentation where I’ll explain project structure and source code.

Open Source

All sources are available at GitHub: https://github.com/ekke/ekkesBTLEexample

 

eclipsecon Europe 2016, Ludwigsburg, Germany

Soon available at the stores:

(can take up to 24 hours)

Android 4.3 or greater for Phones and Tablets from Google Play Store:

google-play-badge

or download the APK from here if you prefer sideloading:

itunesartwork-512x512

iOS 8.2 or greater for iPhone or iPads from Apple App Store:

just approved 2016-10-25 16:27 – can take a moment to be visible in App Store

app_Store_Badge

Amazon Fire OS or BlackBerry10 on Amazon App Store:

amazonappsbadge

Sorry – not enough time yet to also develop a native BlackBerry 10 App – hopefully for the next conference. Native BlackBerry 10 Cascades App loads faster and runs smoother on BlackBerry 10.

Also working on Windows 10 Mobile – runs in development mode on Lumia and others – haven’t finished the work to upload release to windows store yet.

You probably know that last years I always developed the BlackBerry 10 App for Eclipse Conferences. This year first I did it for Android and iOS using Qt 5.7 and brandnew Qt Quick Controls 2. Used Qt Quick Controls 2 and Google Material Style. To make my work easier, Entities (Session, Speaker, Track, Room, Conference, …) are generated using a Xtext / Xtend DSL. Started this DSL 2 years ago for BlackBerry 10 Cascades / Qt 4.8 and now I’m able also to generate code for Qt 5.7 / Qt Quick Controls 2. Hopefully at the end of year mobaDSL will be available Open Source.

Want to know more about HowTo develop Apps using Qt 5.7 for Android, iOS and Windows 10 ? The Sourcecode of my Qt World Summit Conference App is available at Github: https://github.com/ekke/c2gQtWS_x

Here some screenshots of eclipsecon Europe Conference App.

01_home

Schedule

02_schedule

Speakers

03_speaker

Session Detail

04_session_detail

Drawer

o5_drawer

xxx

this article is work-in-progress

QtConAppIcon-180x180

Overview

This is part 8 of a series of  apps helping you to understand Qt 5.7 for x-platform development on Android and iOS. This app is not a Demo App – it’s a real life APP I developed for QtCon 2016 in Berlin. Please read the blogs about my other Examples to understand the concepts and patterns I’m using:

  1. First app with One Page
  2. StackView
  3. SwipeView
  4. TabBar
  5. Bottom Navigation
  6. Drawer Navigation
  7. Business Data APP

Download QtCon 2016 Conference App

The App is available at Google Play Store for Android Devices and from Apple Apptore for iPhones. Also work has started to provide a Windows 10 Mobile App.

google-play-badge

You don’t have a Google Account and want to side-load the App ? Here’s the APK – Download – Link.

 

app_Store_Badge

Out of the Developer Trenches

This blog will give you some insights and explain some parts of the code.

I’ll also speak at Qt World Summit about my experiences while developing this app and show what happens behind the scenes.

Here’s a short overview what I’m using to create the UI of my Qt 5.7 – QtQuickControls2 Business Apps:

qt-app

There are also StackViews on top of a single Page from SwipeView:

Conference Schedule is a SwipeView, each Day is a Page with ListView for all Sessions of the Day. From the List you can open SessionDetailPage (pushing on Stack) and so on.

Drawer and Bottom Bar Navigation

Main Navigation is done by a Drawer.

qtcon_android_02_drawer

Now Google Material Style also supports Bottom Navigation I added a Bottom Navigation Bar to make it easier to navigate. To get the Drawer opened you must swipe from the left site or tap on the Menu Button on Top Left. The Menu Button on Top Left isn’t always visible: if pushing Pages on a Stack, the Button is replaced by a Back Arrow to navigate back – so in this case you must use the gesture from left side. Not so easy if you have a large display size or like to use your phone with one hand. The Bottom Navigation Bar enables you to go through the Pages faster without moving fingers a long distance.

qtcon_android_01_home__

There are buttons for Home, Schedule, Speaker and Tracks inside the Bottom Navigation Bar. You can reach these Buttons easy. There’s also a Menu Button (Hamburger) at Bottom Left position. This Button is always visible, which means you can easy switch between different parts of the App even if there are some Pages on the Stack.

The Bottom Navigation Bar is only visible in Portrait Mode – in Landscape it would eat too much space from available height.

Now there are two Hamburger Menus in Portrait and perhaps you don’t like to have the Menu Button at the bottom ?

No problem – you can customize this. GoTo Settings and select the way you like to navigate.

  • Classic Material Navigation Style ( only the Drawer is used to navigate)
  • Bottom Navigation (Bottom Navigation Bar without Menu Button in Portrait)
  • Comfort (One Hand Use) Navigation (Menu Button in Bottom Navigation Bar)
  • Use only One Menu Button (In Portrait there’s no Menu Button in Title Bar, only in Bottom Navigation

I implemented these options to demonstrate how easy it is to customize your QuickControls2 Apps.

BTW: Coming from BlackBerry 10 Cascades ? Then you already know about the Menu Button at Bottom left. So using Bottom Navigation with Menu Button there could also help to get same Navigation Workflow on different platforms. Will help you if developing Enterprise Business Apps as I’m doing.

Speed Stack Navigation

Sometimes you pushed some Pages on top of a StackView. per ex. Schedule -> Session Detail -> Speaker Detail -> Room Info.

To push Pages on stack you tapped on Buttons to navigate “forward” to the next detail you’r interested in.

To go back to the Schedule – Sessions List you must tap on the Back Button multiple times. On Android you can also use the OS Back Button at Bottom Left, but on iOS you always have to move your finger to Top Left Back Button.

To speed up this kind of Navigation I implemented “Speed Stack Navigation”: the FAB (Floating Action Button) is used to navigate back: with one Tap you jump back to the last used List below. The FAB is always at Bottom Right position so always reachable even with one-hand-use.

Here’s an example from Session Details Page:

qtcon_android_05_session_detail_b

Themes and Colors

From Settings page you can also customize the Colors and dark / light theme:

qtcon_android_10_settings_

here are screenshots using a dark theme:

qtcon_android_10_settings_dark_

qtcon_android_10_settings_dark_schedule

Dynamic Loading

There are many StackViews, SwipeViews, Pages, ListViews …

To provide a performant App it’s a good think to think about dynamic creation of Components. QtCon Conference App uses many Loaders. A Loader is only a placeholder and you must set the ‘active’ property to start Object creation.

Inspired by BlackBerry 10 Cascades TabbedPane delegateActivationPolicy I have implemented different Activation Policies:

  • Immediately
  • Lazy
  • When Active

Immediately means that the Loader is active from beginning and will remain until the end. This is used for HomePage – the first page a user will see.

Lazy means the first time a user wants to see this Page, the Loader will become active and then wil remain.

When Active means the Loader will only be active as long as the Page is in use. Closing the page will set the Loader to inactive and the Controls will be destroyed.

But that’s not the complete story – to speed up the Startup Time I’m doing it this way:

C++ classes like DataManager, DataServer, DataUtil are only created at startup – no init() is done – so it’s really fast.

In QML no Drawer and Bottom Navigation is created, because I don’t set a model. The initial Item of the root StackView (used by Drawer) is a special Page only containing a Busy Indicator:

qtcon_android_00_startup_progress

As soon as this initial Page is created I start a Timer with 300 ms delay and from the Timer I’m calling all the C++ init() methods, all the data was read from cache and if all is done I’m setting the Drawer model. Now the Pages with Immediate – Activation – Policy are created and I’m swapping the initial Page and the Home Page is visible to the user.

In most cases the Busy Indicator will only be visible for some ms.

Caching and Data Binding

For this App I’m using a JSON File Cache.

A Conference App displays Sessions, Speaker, Rooms and this kind of data was read from Cache and QObject* are created – so the complete data was in-memory for fast access.

This data wasn’t changed from normal app – use. The only data a user enters is setting favorite Sessions to create the Personal Schedule. To avoid saving all the Sessions, I’m using an extra Class to store the ID’s of Sessions marked as Favorite. Caching this as JSON will only write a File with some Bytes.

At startup I’m looping through the Favorites and set a Property for these Sessions, so in Lists and Pages I’m getting the Favorite info directly from Session Class. This Property is a transient Property for Sessions.

Closing the App or sending to background I’m looping through the Sessions and create the Favorites. Then this small file was written to cache.

Using QObject* DataBinding to QML UI is easy – most difficult part is creating all the code for Q_PROPERTY. This is done by a Code Generator – all these C++ Classes you’ll find under src/gen. This Code Generator is based on Eclipse Xtext and I’m generating different Code for BlackBerry10 / Qt 4.8 and Qt 5.7 Android/iOS. The CodeGenerator will be Open Source – expect this by the end of 2016.

Caching and Data Binding is a complex story – will blog in an extra article about this.

Screenshots

Find some more Screenshots from the Conference App below.

Some Screenshots are outdated – per ex. the Icons to swap between Personal Schedule and Conference Schedule were changed because of Beta testers Feedback.

Stay tuned for more blog articles and videos explaining patterns and concepts.

There’s an Option Menu available at Startpage. All these Options are also part of the Drawer, but to help users first time using a Drawer I placed this Options Menu to get easy access to Help and Settings.

qtcon_android_01_home__options

QtCon Conference App works in Portrait and Landscape. There are some situations where rotating back from Landscape to Portrait doesn’t work as expected. Will report this and more via Bugreports.

qtcon_android_01_home_landscape

Conference Schedule is a SwipeView with pages for each Conference Day. The List of Sessions of a Day can be long, so there’s a ‘GoTo’ Button to jump to a specific Time Slot.

qtcon_android_03_schedule_

TimePicker is a customized UI Control I created with some help from Mitch Curtis.

Use this TimePicker to jump to Sessions starting at a specific time:

qtcon_android_03_schedule_timepicker

Personal Schedule (lists only marked Sessions). Easy toggling between Personal and Conference Schedule

qtcon_android_04_my_schedule

Session Details:

qtcon_android_05_session_detail_a

Room Floorplan – you can Zoom-In / Zoom-Out or Fit-To-Window:

qtcon_android_05_session_room_

List of Speakers:

qtcon_android_06_speaker_list

GoTo Speaker where Name starts with a specific Letter:

qtcon_android_06_speaker_picker

Speaker details

qtcon_android_07_speaker_detail

List of Tracks – in the meantime Tracks are colored to match UI from Web Browser.

qtcon_android_08_tracks_list

Sessions per Track

qtcon_android_08_tracks_sessions

Venue Info

qtcon_android_09_venue_

Venue Rooms List. Tap on a Room to see all Sessions or on the Thumbnail to see the Floorplan.

qtcon_android_09_venue_room_list

Sessions per Room

qtcon_android_09_venue_room_sessions

Help Page

qtcon_android_11_help

About Page

qtcon_android_12_about

 

Sources

This Application is Open Source and available at Github: https://github.com/ekke/c2gQtCon

Bugreports

As a good Open Source Project Citizen I always report Bugs and Requests.

Here are some of the Bugs I ran into while developing this APP:

TBD

Summary

I’m waiting for Feedback from QtCon in Berlin to see what could be done better.

Next Conference App will be for Qt World Summit 2016 in San Francisco.

My personal wishlist for upcoming Qt Conference Apps:

  • Integration with Twitter, Browser, Map
  • Having Bookmarks and Favorites
  • Personal Schedule Collision Detection
  • From Session –> what’s running at Same Time
  • Fulltext Search for Sessions and Speakers
  • Update from Conference Schedule via Push
  • Update Info (what’s new, removed, timeslots-rooms changed …
  • Better Highlighting of List rows
  • List of Sponsors
  • Shortcuts for BlackBerry PRIV
  • Jump to NOW while Conference running
  • Remove short Flicker at Android Startup
  • Pinch && Zoom Overview as I did for Cascades
  • TimePicker with AM PM support
  • TimePicker better resizing small screens
  • Translate into more languages
  • Support of Android Tablets and iPads

Stay tuned for more Apps – next one will be for Qt World Summit 2016 in San Francisco.

qtws16


← Back (Business Data APP)

→ Next Article (Qt World Summit Conference APP-TBD-)

⇐ Home (Overview / Topics)

Business Data App

July 14, 2016 — 21 Comments

Work in Progress – wait for Tweet @ekkescorner

Overview

This is part 7 of a series of demo apps helping you to understand Qt 5.7 for x-platform development on Android and iOS. This app demonstrates some aspects of a Business App. Please read the blogs abvout my other Examples before:

  1. First app with One Page
  2. StackView
  3. SwipeView
  4. TabBar
  5. Bottom Navigation
  6. Drawer Navigation

xxxxxxxx

orderList

xxxxxxx

Sources

as usual at Github: https://github.com/ekke/biz_data_x

Summary

You learned ….xxxxxxx

Stay tuned for the next Example Apps …


← Back (Drawer Navigation App)

→ Next Article (Qt Con Conference APP)

⇐ Home (Overview / Topics)

Drawer Navigation App

June 20, 2016 — 26 Comments

Overview

This is part 6 of a series of demo apps helping you to understand Qt 5.7 for x-platform development on Android and iOS. This app demonstrates using a Drawer to navigate and contains some concepts explained before. Please read the blogs abvout my other Examples before:

  1. First app with One Page
  2. StackView
  3. SwipeView
  4. TabBar
  5. Bottom Navigation

Bottom Navigation is good for Apps with 3 – 5 different areas. If your app covers more aspects, then a Drawer is the way to go. Read at Google Material Style Guide about Drawer Navigation and take a look at Qt documentation on Drawer – a Navigation Control from Qt Quick Controls 2.

I tried to implement different functionality into the Drawer:

  • Header with Logo and Text
  • Items with Icon + Text
  • Items with Text-only (typically About, Help, Info)
  • Dividers to separate different areas
  • Subtitles
  • Showing a Counter (per ex. Number of emails, Orders, …)
  • Showing a colored Marker
  • Highlighting current Selection

See this Screenshot:

drawer2

As you know, I’m developing crossplatform Business Apps for BlackBerry10, Android, iOS. One of the most important requirements is a fast and smooth workflow through an App which includes short ways for the fingers to tap on buttons. To open a Drawer there are two ways:

  • Swipe from the left site to see the destinations
  • Tap on Menu Button (top-left)

drawer-menu-button

Both actions are not easy for one-hand-use. That’s why I liked the introduction of Bottom Navigation by Google. HowTo use this with Qt 5.7 we talked about in my previous example app.

Why not combining both ? Drawer and Bottom Navigation ? From Settings you can switch Bottom Navigation for Favorites on:

settings

As you can see I set Home, Car, Flight, Settings as ‘Favorite Destinations’. In this example app this decision was made fix – in a real life app the user should select favorites. Please note, that the left most Button is the Menu Button to open the Drawer.

Now it’s easy to open most-used Buttons or the Drawer from Menu at Bottom Navigation.

Favorites as Bottom Navigation will only be there if APP is in Portrait Mode.

Coming from BlackBerry 10 Cascades ? This looks similar to TabbedPane, where Tabs are shown on ActionBar. Makes it easy to have consistant workflow x-platform.

From Settings you also can Hide the TitleBar and decide if active selection should be highlighted.

Navigation Model

Items in Drawer and Buttons in Bottom Navigation are drawn by Repeater from Navigation Model, which is an array of:

[ 
	{ "type": "../navigation/DrawerNavigationButton.qml", 
	"name": "Home", 
	"icon": "home.png", 
	"source": "../navigation/HomeNavigation.qml", 
	"showCounter":false, 
	"showMarker":false, 
	"a_p":1},
	{...}
]

‘type’ can be:

  • DrawerNavigationButton.qml
  • DrawerDivider.qml
  • DrawerSubtitle.qml

‘name’, ‘icon’, ‘source’ are used to generate NavigationButtons

‘showCounter’, ‘showMarker’ adds this behavior to a NavigationButton

‘a_p’ means activationPolicy

From this NavigationModel the Drawer can be created easy. If Bottom navigation is in-use, same Model is used from a mapping:

property var favoritesModel: [
        0, 3, 7, 9
    ]

Be Dynamic (ActivationPolicy)

Coming from BlackBerry10 Cascades ? TabbedPane is similar to Drawer and TabbedPane has a TabDelegateActivationPolicy for dynamic loading of Tabs. I implemented similar policies for Draqwer Navigation.

I’m using Loaders to create Destinations. There are three Activation Policies:

  • IMMEDIATELY
  • LAZY
  • WHILE_CURRENT

IMMEDIATELY: .active = true for the Loader, so the Item was always loaded at the beginning and stays loaded.

LAZY: .active = false for the Loader. First time the Item inside the Loader  will be used, activate is set to true and will stay true. LAZY is good for performance at the beginning and will be used for items user normaly will access meny times.

WHILE_CURRENT: .active = false for the Loader. .active will only set to true WHILE the Item is the currentItem. If user switches to another node, the Loader will set .active = false. Next time same node is used, Item will created again. This option is good for memory-hungry complex Pages, the User normaly only will use seldom. Attention: As soon as the Item was unloaded, you lost your state. If you want to remember last sate, you must store the values anywhere.

For this Example APP here are the Activation Policies used to demonstrate how it’s done:

  • IMMEDIATELY: Home Page, Flight Page
  • LAZY: Car, Bus, Truck, Color Schema Pages
  • WHILE_CURRENT: Subway Page, Settings, About

Starting the App, all Destinations will be created from Repeater:

Repeater {
    id: destinations
    model: navigationModel
    // Destination encapsulates Loader
    // depends from activationPolicy how to load dynamically
    Destination {
	id: destinationLoader
    }
    Component.onCompleted: {
	// all destinations (Loader) created
	// all destinatation items w activationPolicy IMMEDIATELY activated
	// now show first destination (should always be IMMEDIATELY)
	rootPane.activateDestination(0)
    }
}

All marked as IMMEDIATELY will become active and loaded. When all is done, the first one will replace the initial item on StackView. Drawer is using a StackView where the root Item always will be replaced by the Item selected from Drawer or Bottom Navigation. In a real life App perhaps it could take some time before all is created, so there’s an InitialItemPage showing a BusyIndicator.

busy_indicator

Chances are great that you never will see this Page from Example App, because it’s only a demo app with less logic or data-loading behind.

Here’s the Destination.qml (Loader):

Loader {
    id: pageLoader
    property int pageActivationPolicy: modelData.a_p
    active: pageActivationPolicy == activationPolicy.IMMEDIATELY
    visible: false
    source: modelData.source
    onLoaded: {
        item.init()
        if(pageActivationPolicy != activationPolicy.IMMEDIATELY) {
            rootPane.replaceDestination(pageLoader)
        }
    }
}

There are two functions – activateDestinations() and replaceDestination() – managing the load / unload and refresh() on StackView. You can study this from sources.

Pages, StackView, SwipeView / TabBar

It depends from your use-cases what happens if User selects a destination. You can use a Page if there’s only one Page needed to show the content. You can use a SwipeView with TabBar and/or PageIndicator to provide sub-navigation as done for the ColorSchema where you can swipe through 3 Pages to select Primary Color, Accent Color and Theme or you can use a StackView.

The concept is the same as already described in Bottom Navigation APP – Activation Policies are first time used here in this App.

If using some nodes with StackViews where the User wants to jump between and expects to get the same leaf, don’t use Activation Policy WHILE_SELECTED.

Take a look at the sources to see How I implemented all this stuff. As in previous Examples there’s a init() function and cleanup() function to simulate some biz logic.

Coming from BlackBerry10 Cascades ? Here’s (from a 4-years-old Blog 😉 How a typical Cascades App can be structured:

cascades-app

Compare this with a Qt 5.7 App designed with Material Style for Android and iOS as done for this Example, you’ll recognize a similar architecture. This is great for x-platform app development and why I prefer to use Qt 5.7 for Android / iOS Apps: much stuff from C++ is same, Event handling using SIGNAL / SLOT is similar and also UI Architecture as we have seen now.

qt-app

 

Providing same Workflow for mobile Business Apps for all target Platforms is important – esp. in Enterprise.

Sources

as usual at Github: https://github.com/ekke/drawer_nav_x

Summary

You learned HowTo use Qt Quick Controls 2 to implement Drawer Navigation.

Stay tuned for the next Example Apps …


← Back (Bottom Navigation App)

→ Next Article (Business Data App)

⇐ Home (Overview / Topics)

 

Bottom Navigation APP

June 14, 2016 — 2 Comments

Overview

This is part 5 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, my second app (StackView Example) the third app (SwipeView Example) and 4th app (Tab Bar Example) before reading on.

All the navigation through your APP was done using a single root object:

  • Page
  • StackView
  • SwipeView
  • TabBar (with SwipeView)

What if an app will become more complex, where you need some tabs at root, where each tab can itself point to a Page, StackView, SwipeView or TabBar ?

For a long time there was only the Drawer sliding in from the left side. (per ex. used by Google Play APP)

Fortunately Google just introduced a new navigation component: Bottom Navigation. Bottom Navigation uses a ToolBar at the bottom with minimum 3 Buttons and maximum 5 Buttons. You only have two areas ? Use a TabBar, you have more then 5 areas ? Use a Drawer.

There’s a ToolBar with ToolButtons as part of new Qt Quick Controls 2, but no Bottom Navigation Bar. Don’t panic ! It’s easy to customize Qt Quick Controls to have your own Bottom Navigation Control.

Coming from BlackBerry10 Cascades ? TabbedPane  is similar to the Drawer. Having only some Tabs, you can set showTabsOnActionBar: true to get a Bottom Navigation. Coming from iOS ? A Bottom Navigation Bar is used as the default navigation, so it could be the way for x-platform development with Bottom Navigation if 5 Buttons are enough.

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.

From Google Material Style Guide you know that there are many ways to style the Navigation Bar. I have tried to implement all these ways in this example APP. Here’s a screenshot using a (with Primary Color) colored Bottom Bar, where Buttons have white or black Icons. (Depends from Primary Color: Open Options Menu and select Yellow as primary Color: all Icons will become black)

 

android_portrait

Notice that the inactive Buttons are dimmed.

Having 4 or 5 Buttons, only the active Button will show the Label, having 3 Buttons will always show the Label where the Fontsize for inactive Buttons is smaller.

android_all_labels

On iOS most APPs don’t colorize the Bottom Navigation Bar, you can configure this:

ios_all_labels

Without using a Primary Color Background where Icons are white, now the active Button is colored with Primary Color, inactive are black or white (depends from Theme) with reduced Opacity.

Settings and Toast (Popups)

Tapping on the FAB (Floating Action Button) a Popup with some Settings appears:

android_settings

Try out the different configurations. How this kind of stuff works, I already have explained in previous Example APPs.

Settings uses ‘OK’ as default action – even if you tap outside the Popup to close. It’s up to you how you’ll implement such behavior – depends from use-case or customer requirements. One of my Enterprise customers has the requirement, that all Popups, Dialogs, … have a default key which will be executed if closed or auto-closed after a specified timeout. So I wanted to try out this, too and implemented a ‘Toast‘. Toast is used in Android to let the user know that something happened and Toast is closed automatically.

here’s the source /popups/PopupToast.qml:

Popup {
    id: popup
    closePolicy: Popup.NoAutoClose
    bottomMargin: isLandscape? 24 : 80
    x: (appWindow.width - width) / 2
    y: (appWindow.height - height)
    background: Rectangle{
        color: toastColor
        radius: 24
        opacity: toastOpacity
    }
    Timer {
        id: toastTimer
        interval: 3000
        repeat: false
        onTriggered: {
            popup.close()
        }
    } // toastTimer
    Label {
        id: toastLabel
        leftPadding: 16
        rightPadding: 16
        font.pixelSize: 16
        color: "white"
    } // toastLabel
    onAboutToShow: {
        toastTimer.start()
    }
    function start(toastText) {
        toastLabel.text = toastText
        if(!toastTimer.running) {
            open()
        } else {
            toastTimer.restart()
        }
    } // function start
} // popup toastPopup

A Timer is used to close the Toast. If a new message should be displayed before first timeout reached, a ‘restart()‘ is done. It’s important to use restart() instead of start() to be sure that timeout is reset.

Using a Toast is easy done. From Settings – if something was modified – a Toast is shown:

    PopupSettings {
        id: popupSettings
        onAboutToHide: {
            if(popupSettings.update()) {
                popupToast.start(qsTr("Settings modified"))
            } else {
                resetFocus()
            }
        }
    } // popupSettings

    // PopupToast
    PopupToast {
        id: popupToast
        onAboutToHide: {
            resetFocus()
        }
    }

Here’s the Toast:

android_toast

SideBar Navigation

Using a Bottom Navigation Bar in Landscape occupies worthful space and doesn’t really look good with only 3 – 5 Buttons.

For Tablets and Desktop Google recommends to use a SideBar – so I implented a SideBar which will be shown automagically in Landscape.

Android:

android_landscape

 

iOS:

ios_landscape

In Landscape the TitleBar is hidden completely or floating. How a floating TitleBar is implemented I explained in previous Example.

Bottom Navigation

How’s the Bottom Navigation done ?

bottom_nav

This is the data model:

 property var navigationModel: 
    [{"name": "Car", "icon": "car.png", "source": "../pages/PageOne.qml"},
     {"name": "Bus", "icon": "bus.png", "source": "../pages/PageTwo.qml"},
     {"name": "Subway", "icon": "subway.png", "source": "../pages/PageThree.qml"},
     {"name": "Truck", "icon": "truck.png", "source": "../pages/PageFour.qml"},
     {"name": "Flight", "icon": "flight.png", "source": "../pages/PageFive.qml"}]
 property int navigationIndex: 0
 onNavigationIndexChanged: {
     rootPane.activeDestination(navigationIndex)
 }

From this data model the ToolBar can be created:

// The Bar
Pane {
    id: myBar
    z: 1
	// ...
    height: 56
    background: Rectangle {
        color: primaryColor
    }
    RowLayout {
        focus: false
        anchors.left: parent.left
        anchors.right: parent.right
        spacing: 0
        Repeater {
            model: navigationModel
            NavigationButton {
                id: myButton
                isColored: false
            }
        } // repeater
    } // RowLayout
} // bottomNavigationBar
// The Buttons
ToolButton {
    id: myButton
    // ...
    property bool isActive: index == navigationIndex
    // ...
    height: 56
    width: myBar.width / navigationModel.length
    Column {
        spacing: 0
        topPadding: myButton.isActive || !suppressInactiveLabels? 0 : 6
        anchors.horizontalCenter: parent.horizontalCenter
        Item {
            anchors.horizontalCenter: parent.horizontalCenter
            width: 24
            height: 24
            Image {
                id: contentImage
                width: 24
                height: 24
                verticalAlignment: Image.AlignTop
                anchors.horizontalCenter: parent.horizontalCenter
                source: "qrc:/images/"+myIconFolder+"/"+modelData.icon
                opacity: isActive? myBar.activeOpacity : myBar.inactiveOpacity
            }
            ColorOverlay {
                id: colorOverlay
                visible: myButton.isColored && myButton.isActive
                anchors.fill: contentImage
                source: contentImage
                color: primaryColor
            }
        } // image and coloroverlay
        Label {
            visible: myButton.isActive || !suppressInactiveLabels
            anchors.horizontalCenter: parent.horizontalCenter
            text: modelData.name
            opacity: isColored? (isActive? 1.0 : 0.7) : (isActive? myBar.activeOpacity : myBar.inactiveOpacity)
            color: isColored? (isActive? primaryColor : flatButtonTextColor) : textOnPrimary
            font.pixelSize: myButton.isActive? fontSizeActiveNavigationButton : fontSizeInactiveNavigationButton
        } // label
    } // column
    onClicked: {
        navigationIndex = index
    }
} // myButton

To show the destination from Button clicked, a StackView is used. This StackView always only shows one Control, which will be replaced if ToolButton changes index. In this Example APP we only use Pages – in a real-life-app you’ll replace StackView root object with StackView, SwipeView, TabBar etc.

Please note: There’s a InitialItemPage – this Page will show a BusyIndicator to be sure that there’s something visible at startup. Probably you won’t see this Page because the first Page will be loaded fast enough, but in real-life-apps it could take some time to instantiate a complex node.

The StackView:

    StackView {
        id: rootPane
        focus: true
        // anchors......
		
        initialItem: InitialItemPage{}

        replaceEnter: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 0
                to:1
                duration: 300
            }
        }
        replaceExit: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 1
                to:0
                duration: 300
            }
        }

        // support of BACK key
		// ... see sourcecode
		
		// some Shortcuts
		// see sourcecode

        Repeater {
            id: destinations
            model: navigationModel
            Destination {
                id: destinationLoader
            }
            Component.onCompleted: {
                destinations.itemAt(0).active = true
            }
        }
        function firstDestinationLoaded() {
            fab.visible = true
        }

        function activeDestination(navigationIndex) {
            if(destinations.itemAt(navigationIndex).status == Loader.Ready) {
                rootPane.replace(destinations.itemAt(navigationIndex).item)
            } else {
                destinations.itemAt(navigationIndex).active = true
            }
        }

    } // rootPane

StackView creates Destinations – a Destination is a Loader.

Loader {
    id: pageLoader
    active: false
    source: modelData.source
    onLoaded: {
        item.init()
        rootPane.replace(item)
        if(index == 0) {
            rootPane.firstDestinationLoaded()
        }
    }
}

All Destinations are inactive at the beginning. As soon as the Repeater has created all Destinations, the first one will become active: true, which will cause the source to be instantiated. From signal loaded() the Item will replace the current root item from StackView.

In this Example APP all Destinations are following same policy and created lazy the first time used and then will stay instantiated whole app lifecycle. Later in more complex Drawer Example we’ll use different policies.

StackView has no currentIndex. To manage Destinations from Button clicked we set navigationIndex – so it’s easy to know if a Button is active: index == navigationIndex, where index is automatically provided from Repeater.

Summary

You learned HowTo customize Qt Quick Controls 2 to implement a Bootom Navigation Bar or Side Navigation Bar.

Using Bottom Navigation, a TabBar, a StackView or a SwipeView are not the only ways to navigate – stay tuned for the next Example App using a sliding Drawer together with Pages, StackView, SwipeView and TabBar.


← Back (Tab Bar APP)

→ Next Article (Drawer Navigation App)

⇐ Home (Overview / Topics)

 

Overview

This is part 4 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, my second app (StackView Example) and my third app (SwipeView Example) before reading on.

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

Coming from BlackBerry10 Cascades ? TabBar is similar to a SegmentedControl.

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_tab_01

TabBar Example App:

  • TabBar allows to easy navigate through content
  • There are 5 Pages the User can access
  • When a Page is loaded: call init()
  • Before a Page is destroyed: call cleanup()
  • TabBar Design can be configured from Settings tapping on Floating Action Button (FAB)

The Source

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

Project structure and .pro

The project structure is similar to my StackView or SwipeView Example Apps – we’re also using 5 Pages here for TabBar to make it easy for you to compare. Pages are named same as for the StackView and SwipeView apps, but sourcecode is slightly different.

The other Apps have used Raised and Flat Buttons inside Pages – this time I’m using a customized 48 dp Image Button:

ButtonIconActive {
    imageName: tabButtonModel[1].icon
    imageSize: 48
    onClicked: {
        navPane.goToPage(1)
    }
}

common/ButtonIconActive.qml:

Button {
    id: button
    // default Image Size 24x24
    property alias imageName: theIcon.imageName
    property alias imageSize: theIcon.imageSize
    focusPolicy: Qt.NoFocus
    contentItem:
        IconActive {
        id: theIcon
    }
    background:
        Rectangle {
        id: buttonBackground
        implicitHeight: imageSize + 24
        implicitWidth: imageSize + 24
        color: button.pressed ? accentColor : "transparent"
        opacity: button.pressed ? 0.12 : 1.0
    } // background
}

Popups now are in an extra Sources directory and /tabs contains TabBars and TabButtons (QML):

project_structure_tabbar

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

TabBar Design Choices

Google Material Style Guide gives you choices HowTo design a TabBar, Qt 5.7 enables you to implement your choice:

android_tabbar_design_choices

As you can see, TabBar can be part of your ToolBar or placed below, a TabButton can be done as pure-text Button, Icon-only Button or Icon plus Text. Of course you can use TabBar without a ToolBar, too.

Customizing is easy done – this is the TabButton represented by Icon-only-Design.

tabs/TabButtonIcon.qml:

TabButton {
    property color theButtonColor: accentColor
    property string theIconFolder: iconFolder
    property alias hasOverlay: colorOverlay.visible
    property real theOpacity: 1.0
    focusPolicy: Qt.NoFocus
    height: 48
    contentItem:
        Item {
        Image {
            id: contentImage
            anchors.centerIn: parent
            horizontalAlignment: Image.AlignHCenter
            verticalAlignment: Image.AlignVCenter
            source: "qrc:/images/"+theIconFolder+"/"+modelData.icon
            opacity: colorOverlay.visible? 1.0 : theOpacity
        }
        ColorOverlay {
            id: colorOverlay
            visible: true
            anchors.fill: contentImage
            source: contentImage
            color: index == navPane.currentIndex ? theButtonColor : Qt.lighter(theButtonColor)
        }
    } // item
}

Placed inside ToolBar, ColorOverly isn’t used (invisible)

Pplaced below the ToolBar, ColorOverlay tints the Image with accent color.

TabButtons fixed vs scrolling

Sometimes not all Buttons or Text fits into available width, so it makes sense to use a scrollable TabBar:

TabButton {
    // ...
    width: tabBarIsFixed? myTabBar.width / tabButtonModel.length  
	       : Math.max(112, myTabBar.width / tabButtonModel.length)
}

ios_buttons_fix_scroll

TabBar Settings (Popup)

To configure the TabBar Settings there’s another Popup:

tabbar_settings

Open the Settings Popup from Floating Action Button (FAB):

android_tab_fab

    FloatingActionButton {
        id: fab
        property string imageName: "/settings.png"
        z: 1
        anchors.margins: 16
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        imageSource: "qrc:/images/"+iconOnAccentFolder+imageName
        backgroundColor: accentColor
        onClicked: {
            showSettings()
        }
    } // FAB
// ...
    function showSettings() {
        popupSettings.tabBarIsFixedSettings = tabBarIsFixed
        popupSettings.open()
    }
// ...
    PopupSettings {
        id: popupSettings
        onAboutToHide: {
            popupSettings.update()
            resetFocus()
        }
    } // popupSettings

PopupSettings used some new Controls: RadioButtons inside a Frame:

Frame {
    ColumnLayout {
        anchors.fill: parent
        spacing: 6
        RadioButton {
            id: radioText
            focusPolicy: Qt.NoFocus
            text: qsTr("Buttons Text only")
            checked: tabButtonDesignSettings == 0
            onCheckedChanged: {
                tabButtonDesignSettings = 0
            }
        }
        RadioButton {
            id: radioIcon
            focusPolicy: Qt.NoFocus
            text: qsTr("Buttons Icon only")
            checked: tabButtonDesignSettings == 1
            onCheckedChanged: {
                tabButtonDesignSettings = 1
            }
        }
        RadioButton {
            id: radioTextAndIcon
            focusPolicy: Qt.NoFocus
            text: qsTr("Buttons Icon and Text")
            checked: tabButtonDesignSettings == 2
            onCheckedChanged: {
                tabButtonDesignSettings = 2
            }
        }
    }
}

As already discussed in previous articles: To make Shortcuts work well, we set Qt.NoFocus for RadioButton.

This Popup follows a Default-Button-Concept, where ‘OK‘ is default (colored with Accent Color)

Even if user clicks outside the Popup to close, changed values will be updated:

Popup {
    id: popup
    // ...
    // default behavior for this Popup: OK Button Clicked
    property bool isOk: true
	// ...
    RowLayout {
        ButtonFlat {
            id: cancelButton
            text: qsTr("Cancel")
            textColor: popupTextColor
            opacity: opacityBodySecondary
             onClicked: {
                isOk = false
                popup.close()
            }
        } // cancelButton
        ButtonFlat {
            id: okButton
            text: qsTr("OK")
            textColor: accentColor
            onClicked: {
                isOk = true
                popup.close()
            }
        } // okButton
    } // row button
    // ...
    function update() {
        if(isOk) {
            tabBarIsFixed = tabBarIsFixedSettings
            tabButtonDesign = tabButtonDesignSettings
            tabBarInsideTitleBar = tabBarInsideTitleBarSettings
        }
    }
	// ...
}
// ...
// from main.qml:
// ...
onAboutToHide: {
    popupSettings.update()
}
// ...

ToolTips

You can define ToolTips if you want to use this to guide your users. I have implemented ToolTip on ‘Bus’ Button (First Tab).

    ToolTip.visible: pressed
    ToolTip.delay: 500
    ToolTip.text: qsTr("Take a look at the Bus schedule")

android_tab_01_tooltip

Floating Header

Using a ToolBar together with TabBar as ‘header‘ could occupy too much space in Landscape.

Coming from BlackBerry 10 Cascades ? In Cascades you can configure TitleBarScrollBehavior as NonSticky to make the TitleBar scrollable. I implemented something similar here.

Using a Loader this is easy done:

ApplicationWindow {
    //
    property bool isLandscape: width > height
    // ...
    header: isLandscape? null : titleBar

    Loader {
        id: titleBar
        visible: !isLandscape
        active: !isLandscape
        source: titleAndTabBarSource
        onLoaded: {
            if(item) {
                item.currentIndex = navPane.currentIndex
                item.text = qsTr("HowTo move from A to B")
            }
        }
    }
    Loader {
        id: titleBarFloating
        visible: isLandscape
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        active: isLandscape
        source: titleAndTabBarSource
        onLoaded: {
            if(item) {
                item.currentIndex = navPane.currentIndex
                item.text = qsTr("HowTo move from A to B")
            }
        }
    }
}

Portrait scrolling:

android_scroll_portrait

Landscape scrolling:

android_scroll_landscape

Tandem: TabBar and SwipeView

From Google Material Style Guide: You must be able to Tap on a TabButton or to swipe from content to navigate.

From Qt 5.7 TabBar: Tabs must work together with Controls providing ‘currentIndex’.

So it’s the best way to use a SwipeView to show the content and to bind the currentIndex to TabBar.

We already discussed SwipeView in detail here. SwipeView example app uses Loader to load / unlaod current Page and next / previous Pages.

For the TabBar I did it slightly different using ‘Lazy Loading‘: at Application Start the first two Tabs are pre-loaded. All other Pages will be loaded if needed, and stay loaded while App is running.

Take a look at main.qml to see how all this stuff is implemented.

To define the TabButtons first time a Repeater is used:

Repeater {
    model: tabButtonModel
    TabButton {
        text: modelData.name
        width: ...
    }
} // repeater

Coming from BlackBerry Cascades ? There’s nothing like a Repeater.

Repeater makes it easy to iterate through a data model and create Controls. In this case the tabButtonModel is designed in main.qml:

    property var tabButtonModel: 
	    [{"name": "Car", "icon": "car.png"},
         {"name": "Bus", "icon": "bus.png"},
         {"name": "Subway", "icon": "subway.png"},
         {"name": "Truck", "icon": "truck.png"},
         {"name": "Flight", "icon": "flight.png"}]

Each data object has a ‘name’ and an ‘icon’ property and can be accessed inside TabButton using modelData.name and modelData.icon.

TabBar Navigation

Similar to StackView and SwipeView Examples you can go different ways to navigate between Pages with tabs:

  • click on one of the TabButtons in TabBar
  • click on a Image Button to go to another Page (Tab)
  • click on Android System ‘Back’ key to go one Tab back to the left (this is no default behavior – ony an example what you can do with app-specific navigation)
  • swipe using your fingers to go to next Page (Tab) left or right
  • use Shortcuts (see StackView example about Shortcuts)

tabbar_navigation

Using BlackBerry PRIV – or connected Bluetooth Keyboard Shortcuts making it easy to navigate to Tabs 1 … 5 or next / previous Tab:

priv_shortcuts

Summary

You learned HowTo use a TabBar as another way to navigate through some Pages. It depends from your use-cases what will fit best your requirements.

Using a TabBar, a StackView or a SwipeView are great to navigate through many APPs. What if there are some areas you need to switch between ? Then a sliding Drawer will be the solution to manage even really complex APPs. Before taking a look at Qt Quick Controls 2 ‘Drawer’ – there’s another way if your app can be managed by 3 to 5 buttons: Google introduces Bottom Navigation as part of Material Style. The next example APP will customize Qt Quick Controls to use Bottom Navigation.


← Back (Swiped Pages APP)

→ Next Article (Bottom Navigation APP)

⇐ Home (Overview / Topics)

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)