Weekend Coder: Handling orientation changes

By Brian Scheirer on 10 Nov 2013 07:34 am EST
2
loading...
0
loading...
41
loading...

While designing my first app that is intended for media consumption (rather than my usual puzzle games), I quickly realized that it is necessary to to take into account orientation changes. The orientation is the direction the device is being held, either landscape or portrait. The idea of properly handling orientation is to effectively use screen real estate for both use cases.

Luckily, like many other things in Cascades, dealing with orientation changes is very easy. Prior to changing any code, if you go to the “Application” tab of the bar-descriptor.xml you will see an orientation dropdown. If it is set to auto-orient, Cascades will know when the orientation changes and attempt to reposition your objects to display properly. Usually this is not ideal, but it's worth taking a look to see how your app looks with Cascades adjusting auto-magically.
Assuming the auto adjust from Cascades is not what you’d like for your app, you can also easily adjust and move around components manually. Let’s take a look at two scenarios/methods for handling orientation changes.

Adjusting Component Properties

This first method will take advantage of adjusting component properties. Take a look at the two screenshots below.


The portrait view has 6 components (only 3 visible), stack one on top of each other in a ScrollView that scrolls vertically. The landscape view has the same 6 components, however they are now stacked left to right and the ScrollView scrolls horizontally.


To control these properties we need to attach the OrientationHandler to our Page. Then within that, the signal onOrientationAboutToChange is fired when the user changes the orientation of the device. So we could have code that looks like:

import bb.cascades 1.0 Page { attachedObjects: [ OrientationHandler { onOrientationAboutToChange: { if (orientation == UIOrientation.Landscape) { scrollContainer.layout.orientation = LayoutOrientation.LeftToRight; scrollMain.scrollViewProperties.scrollMode = ScrollMode.Horizontal; } else { scrollContainer.layout.orientation = LayoutOrientation.TopToBottom; scrollMain.scrollViewProperties.scrollMode = ScrollMode.Vertical; } } } ] //... }

Using Custom Components

Much like I used a custom component in my Asset Selector example, custom components can be used in the Orientation handler. Take a look at these two screenshots for portrait and landscape.


They each contain the same 9 components, however they are arranged so differently that it would be a pain to account for adjusting all the properties. Therefore it is best to define separate user interfaces for each. So I created PortView.qml and LandView.qml files that contained the UI for each view. Then I used a ControlDelgate in conjunction with the onOrientationAboutToChange signal to dynamically load each UI depending on the orientation. The code would look something like this:

import bb.cascades 1.0 Page { attachedObjects: [ OrientationHandler { onOrientationAboutToChange: { if (orientation == UIOrientation.Landscape) { myDelegate.source = "LandView.qml" } else { myDelegate.source = "PortView.qml" } } } ] Container { //Todo: fill me with QML Label{ text: "AWESOME TITLE!" textStyle.fontSize: FontSize.XLarge textStyle.fontWeight: FontWeight.W100 } ControlDelegate { id: myDelegate source: "PortView.qml" horizontalAlignment: HorizontalAlignment.Center } } }

That about does it for handling orientation changes. One more thing to note, Q series (720x720) devices will ignore this code always displaying the portrait UI. Check out the full source code for these two samples from the link below and sound off in the comments with any thoughts!

Reader comments

Weekend Coder: Handling orientation changes

23 Comments

Hey, if I got this right, you don't need to make sure the app state remains the same? On Android you need manage all background processes and data when orientation changes because the activity gets destroyed and re-created. That's not the case here?

Posted via CB10

So what do I have to do when I do a lazy loading of list data and the orientation gets changed? how do I associate the data with the new view?

Posted via CB10

If you set the datamodel to the list, it is still there, so you just need to change the orientation of the list in the onOrientationAboutToChange.

Sry for commenting again.
This is something one should definitely highlight when it comes to developing for BB10. Really. Android developers will know.

Posted via CB10

you have to manage the state of your Pages if you swap between qml pages depending on orientation.
Bound List data is there without to do anything, but if you have set properties to store some state, want to scroll to same row as before, ... then you have to set this in the swapped page.
So it's much easier then using Android and all is under your control, but think about persisting your state if changing complete QML pages on orientation change.

The source code for the orientation examples are on my github: https://github.com/bcs925/CrackBerry/tree/master/FlipView main.qml has the first example and main2.qml (as well as the two additional qml files) are the second example.
 

I hope devs read this because I'm sick of apps that don't support rotation!!!

-STV on Z10STL100-3/10.1.0.4780 TMO US

Bezel Swipe (or even just clicking) in the lower right corner still works. However, it only works if you are in an app that supports multiple orientations...

Depending on your app you may also want to invert the logic and do this in a more "QML-friendly" way: declare a property which is updated when orientation is about to change and use bindings in places where it's needed, something like this:
Page {
id: page

property var orientation: OrientationSupport.displayOrientation

ScrollView {
layout: StackLayout {
orientation:
orientation == UIOrientation.Landscape ? LayoutOrientation.LeftToRight : LayoutOrientation.TopToBottom
}
}

attachedObjects: [
OrientationHandler {
onOrientationAboutToChange: {
page.orientation = orientation
}
}
]
//...
}

Or if you want to be fancy:

Page {
id: page

property string orientation;

Container {
layout: StackLayout {
orientation:
orientation == "landscape" ? LayoutOrientation.LeftToRight ? LayoutOrientation.TopToBottom
}
ImageView {
// automatically picks proper image
imageSource: "asset://image_" + page.orientation + ".png"
}
}

attachedObjects: [
OrientationHandler {
onOrientationAboutToChange: {
updateOrientation(orientation)
}
}
]

function updateOrientation(newOrientation) {
orientation = newOrientation == UIOrientation.Landscape ? "landscape" ? "portrait"
}

onCreationCompleted: {
updateOrientation(OrientationSupport.orientation)
}
}

It's more friendly if you were to change the id of your control, or remove the control all together. Say in a future update you no longer need one of the scroll views, in your code you would need to update the orientation handler, instead of simply removing the control.

Thanks for sharing this. I will soon start developing applications for Blackberry :) Need to brush up little tutes for that.