Tab Pages APP (TabBar)

June 7, 2016 — 5 Comments

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)

5 responses to Tab Pages APP (TabBar)

  1. 

    Very nice work Ekke. New to .qml and have been fiddling with the titebar so the text updates with each page, similar to your stacked_pages_x. If you had any ideas not sure how to get something similar to {text: navPane.currentItem? navPane.currentItem.title : qsTr(“”)} working within tab_pages_x app, {property string title: qsTr(“”)} within each page’s Flickable seems to be ignored ?

    Greaty work again love the style and setting abilities.

    Regards

  2. 

    Thank for good article. I’m finding how to create tab application on QT android.

  3. 

    In landscape mode, set header to NULL , and the loader with fixed anchors position, then scrolling up, the titlebar will be overlapped. Why? The effect looks like they are in different z axis but the code is not.

    • 

      sorry – until end of year I’ll have no time to answer questions because of heavy-load of customer projects.

Leave a reply to R.Chatsiri (@rchatsiri) Cancel reply