Resolution independence

When you develop apps for mobile devices, it's important to consider the screen resolution, density, and size of the devices you're targeting. Many aspects of your UI, such as the arrangement of UI controls and the appearance of image assets, can depend on these factors, and your apps should be able to adapt to them so they always look their best and reach the largest number of users.

These are the resolutions for current BlackBerry 10 devices:

The screen resolution is the total number of pixels in a display. For example, the BlackBerry Q10 has a resolution of 720 x 720.

The screen density is the number of pixels within an area of the screen and is usually measured in dpi (dots per inch).

The screen size is the physical size of the device.

A comparison of device resolutions.

The actual resolution for the BlackBerry Z3 smartphone is 540×960. However, when you run an application on this device, assets that you design for the BlackBerry Z30 are automatically scaled down for the smaller resolution.

Here are some techniques that you can use for creating a UI that works well on all types of screens:

  • Use relative layouts with properties such as margins and space quotas to preserve the spacing between your controls.
  • Use design units instead of pixels so that assigned dimensions can scale for different pixel densities automatically.
  • Create separate sets of assets for different screen layouts and densities and let the static asset selector choose the best set of assets when you run your app.
  • Use nine-slice scaling to scale images that must retain the appearance of their edges and corners.

The UI for your app isn't the only visual component that you need to consider when making your app resolution independent. Like the device screen itself, Active Frames also come in different sizes. For information about the different sizes of Active Frames for each device, see Active Frames in the UI Guidelines.

You must also make sure that your application icons are the correct size for each device that your app targets. To learn more about app icon sizes, see Application icons.

Relative layouts

There are several types of layouts that you can use to position your UI controls, and each layout arranges controls in a different way. For example, a stack layout positions controls next to each other, either vertically or horizontally. An absolute layout places controls in exact positions that you specify.

Where possible, you should try to use a relative layout ( StackLayout, DockLayout, or GridLayout), instead of an AbsoluteLayout. As the name suggests, a relative layout arranges controls relative to each other and the boundaries of the container. If you use a relative layout and the screen size or resolution changes, your controls still maintain their relative positions. If you use an absolute layout and specify the positions of your controls yourself, you might find that the controls aren't in the right places when your app is viewed in a different resolution.

For example, consider the layout that's shown in the image on the right. This layout is designed for the BlackBerry Z30, which has a resolution of 720 x 1280, and includes three Button controls in various positions.

There are several ways that you could achieve this layout. You could use an AbsoluteLayout and specify the pixel positions of each button. In this approach, you need to calculate the correct pixel positions for each button.

You could also use a DockLayout and use alignment properties to position each button relative to the edges of the screen. A DockLayout supports alignments such as left, right, and center, and you can use combinations of horizontal and vertical alignments to position your controls.

Screen showing a layout that you can create using either a dock layout or absolute layout.

Here's how to use an AbsoluteLayout to create this layout in QML:

// This is the WRONG way to lay out your app

import bb.cascades 1.0

Page {
    Container {
        background: Color.create(0.86, 0.86, 0.9)
        layout: AbsoluteLayout {}
        
        Button {
            layoutProperties: AbsoluteLayoutProperties {
                positionX: 200
                positionY: 0
            }
            text: "Top button"
        }
        Button {
            layoutProperties: AbsoluteLayoutProperties {
                positionX: 412
                positionY: 580
            }
            text: "Left button"
        }
        Button {
            layoutProperties: AbsoluteLayoutProperties {
                positionX: 200
                positionY: 1215
            }
            text: "Bottom button"
        }
    }
}

Alternatively, here's how to use a DockLayout to create the same layout:

// This is the RIGHT way to lay out your app

import bb.cascades 1.0

Page {
    Container {
        background: Color.create(0.86, 0.86, 0.9)
        layout: DockLayout {}
            
        Button {
            horizontalAlignment: HorizontalAlignment.Right
            verticalAlignment: VerticalAlignment.Center
            text: "Right button"
        }
        
        Button {
            horizontalAlignment: HorizontalAlignment.Center
            text: "Top button"
        }
        Button {
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            text: "Bottom button"
        }
    }
}

Now, consider what this layout looks like on a BlackBerry Q10, which has a resolution of 720 x 720. The image on the left uses an AbsoluteLayout, and the image on the right uses a DockLayout:

 
Screen shot showing how to incorrectly lay out a screen
Screen shot showing how to correctly lay out a screen.
 

In the version that uses an AbsoluteLayout, the top and right buttons are visible but the bottom button isn't displayed. Because the buttons in this version are positioned using pixel coordinates, the bottom button is actually placed outside the visible area of the screen.

In the version that uses a DockLayout, the buttons maintain their positions relative to the edges of the screen, regardless of the size of the device. In general, relative layouts are always better than absolute layouts at preserving the appearance of your UIs.

Space quotas

A common way to specify the size of your controls is to use the preferredWidth and  preferredHeight properties. However, this approach might not work well on devices with different screen resolutions or if you change the structure of your UI. Instead, you can consider using space quotas to assign sizes to your controls. A space quota determines how much a control should shrink or expand to fill the available space within its parent container. Using space quotas is a great way to ensure that control sizes remain relative to each other across different sizes of devices.

Consider the following scenario: you have a Button and TextField that you want to place side-by-side on a screen, and you want the TextField to take up 2/3 of the width of the screen. If you write your app with a specific device resolution in mind (for example, 720 x 720 pixels), you might be tempted to provide explicit pixel widths for your controls (240 pixels for the button and 480 pixels for the label). However, you can also achieve the same look by using space quotas.

The image on the left uses pixels and the one on the right uses space quotas.

An image that shows how a pixel layout and a space-quota layout look on 720 x 720 screens.

Both methods are fine for this size of screen, but if you try to run the app on a screen with a different resolution, problems might arise. Here's what both screens look like running on a device with a resolution of 1440x1440. The image on the left uses pixels and the one on the right uses space quotas. As you can see, the image that uses space quotas keeps the relative size of its controls regardless of the size of the screen.

An image that demonstrates how a pixel layout does not work on a 1440x1440 screen.

Setting space quotas

Space quotas apply only to controls in a stack layout and are set by using the  spaceQuota property. Values can be positive or negative:

  • A negative spaceQuota value indicates that the control uses as much space as necessary to appear correctly in the layout, but no more. The control doesn't expand or shrink to fill any available space in the layout. This behavior is the default if you don't specify a value for spaceQuota.
  • A positive spaceQuota value indicates that the control uses as much space as is available in the container, and the control expands and shrinks depending on the available space. The amount that the control expands or shrinks depends on the value of spaceQuota. A control with a space quota of 2 expands to use twice as much space as a control with a space quota of 1. Similarly, a control with a space quota of 2 shrinks twice as much as a control with a space quota of 1.

You can think of positive space quotas as forming a ratio that represents the relative sizes of the controls. A control with a space quota of 3 and a control with a space quota of 2 form a ratio of 3:2, and the sizes of the controls always remains in that ratio. Space quotas of 3 and 2 are equivalent to space quotas of 6 and 4 (or 27 and 18) because the ratio is still 3:2.

The following image demonstrates how different space quotas determine the sizes of controls in a layout. Each row of buttons represents a container with a left-to-right stack layout, and the numbers on each button indicate the value of the button's spaceQuota property:


Diagram showing the use of the spaceQuota property on a series of Button controls.

In the image above, the last row of buttons contains a button with a negative space quota. When combined with controls that have positive space quotas, controls with negative space quotas take as much space as the control needs (the default height or width of the control). Any remaining space is divided between the controls with positive space quotas, depending on their specific space quota values.

The following code sample creates a left-to-right stack layout with three buttons. Each button has a different space quota that determines its relative size.


Diagram showing three Button controls in a left-to-right layout orientation.

Container {
    layout: StackLayout {
        orientation: LayoutOrientation.LeftToRight
    }
 
    Button {
        layoutProperties: StackLayoutProperties {
            spaceQuota: 1
        }
    }
 
    Button {
        layoutProperties: StackLayoutProperties {
            spaceQuota: 2
        }
    }
 
    Button {
        layoutProperties: StackLayoutProperties {
            spaceQuota: 3
        }
    }
}
// Create the container and layout
Container* myContainer = new Container;
StackLayout* layout = StackLayout::create()
        .orientation(LayoutOrientation::LeftToRight);
myContainer->setLayout(layout);
 
// Create three sets of stack layout properties
StackLayoutProperties* slp1 = StackLayoutProperties::create()
                               .spaceQuota(1);
StackLayoutProperties* slp2 = StackLayoutProperties::create()
                               .spaceQuota(2);
StackLayoutProperties* slp3 = StackLayoutProperties::create()
                               .spaceQuota(3);
 
// Add buttons that use the stack layout properties
myContainer->add(Button::create().layoutProperties(slp1));
myContainer->add(Button::create().layoutProperties(slp2));
myContainer->add(Button::create().layoutProperties(slp3));

Design units 10.3

Using design units (du) is another way that you can create adaptable UIs. Previously, if you wanted to specify a dimension in a Cascades scene (the length of a control, the width of a margin, and so on), you had to specify an explicit pixel value. The problem with using pixel values is that they don't always translate well from one screen density to another. Design units are device-independent values that you use to assign dimensions to components in your UI. When you run the app, the framework converts the design unit value to a pixel value that's optimal for the screen density of that device.

The following table describes how design units convert into pixel values for each device.

Device

Pixels per design unit

Width (du)

Height (du)

BlackBerry Z30 and BlackBerry Z3

8

90

160

BlackBerry Q10 and BlackBerry Q5

9

80

80

BlackBerry Z10

10

76.8

128

BlackBerry Passport

12

120

120

The actual resolution for the BlackBerry Z3 is less than the BlackBerry Z30. However, when you run an application on the BlackBerry Z3, assets that you design for the BlackBerry Z30 are automatically scaled down for the smaller resolution.

The conversion functions for design units are available through the UIObject::ui property. The UIConfig object that this property returns contains three different conversion methods:

du() - design units
Converts a design unit into an explicit pixel value that's optimal for the pixel density of the device.
sdu() - snapped design units
Converts a design unit into an explicit pixel value that's optimal for the pixel density of the device, and rounds the amount to the nearest whole pixel.
px() - pixels
Converts a pixel value into an equivalent pixel value. This function doesn't change the pixel amount; it's a way to explicitly show that a dimension is measured in pixels. Generally, you should always try to use design units or snapped design units instead.

Dynamic design units

Although design units are useful for adapting a UI to different screen sizes and shapes, they do not account for changes to the information density. Changes to information density can occur when the user changes the system font size. To account for these changes, you can incorporate the dduFactor property into your design unit calculations. As the information density changes, dduFactor changes accordingly, allowing you to update dimensions within your app to adapt to the space that's available.

For more information about dynamic design units, see UiConfig::dduFactor.

Why switch to design units?

An app that uses design units is able to retain its relative dimensions regardless of the screen density of the device that it runs on.

Consider the following scenario: you've designed your app UI with only earlier BlackBerry 10 devices in mind (BlackBerry Z10, BlackBerry Z30, BlackBerry Q10, and so on). Although these devices have different screen sizes, shapes, and resolutions, they all have a similar screen density that ranges from 295 dpi to 356 dpi. Because the screen densities are so similar, you can specify dimensions using real pixel amounts and still create a single UI that works well for all devices (if you follow other guidelines for resolution independence such as using relative layouts and space quotas). However, if you run the app on a device that has a much higher screen density, the pixel dimensions don't retain their relative size.

For example, here's a basic screen that contains two text fields and a text area. The root container for the app has 16 pixels of padding along each edge, and the child controls have a 54 pixel margin between them.

import bb.cascades 1.3
            
Page {
    Container {
        topPadding: 16
        bottomPadding: 16
        rightPadding: 16
        leftPadding: 16

        TextField {
            hintText: "To:"
            bottomMargin: 54
        }
        TextField {
            hintText: "Subject:"
            bottomMargin: 54
        }
        TextArea {
            layoutProperties: StackLayoutProperties {
                spaceQuota: 1
            }
        }
    }
}

Here's what the example above looks like when you run it on two different devices:

A screen shot that shows how pixel layouts appear on two different devices.

The screen shot on the left is from a BlackBerry Q10 (720 x 720 pixels, 330 dpi) and the screen shot on the right is from the BlackBerry Passport (1440 x 1440, 453 dpi). Because the device on the right has a much higher pixel density, the padding and margins appear much smaller than on the BlackBerry Q10.

To make the padding and margins retain their relative size on the BlackBerry Passport, you should use design units instead of pixels.

import bb.cascades 1.3
            
Page {
    Container {
        topPadding: ui.sdu(2)
        bottomPadding: ui.sdu(2)
        rightPadding: ui.sdu(2)
        leftPadding: ui.sdu(2)
            
        TextField {
            hintText: "To:"
            bottomMargin: ui.sdu(6)
        }
        TextField {
            hintText: "Subject:"
            bottomMargin: ui.sdu(6)
        }
        TextArea {
            layoutProperties: StackLayoutProperties {
                spaceQuota: 1
            }
        }
    }
}

Here's what the new app looks like when you run it on the same two devices as the first example:

A screen shot that shows how design unit layouts appear on two different devices.

On the BlackBerry Q10, the app that uses design units looks the same as the app that uses pixels. When the app converts the design units into real pixel amounts, the number of real pixels is equal to the number of pixels that the first app uses (2 design units of padding = 18 real pixels and 6 design units of margin = 54 real pixels).

However, on the BlackBerry Passport, the padding and margins in this app appear larger than in the app that uses pixels. When the app converts the design units into real pixel amounts, the number of real pixels is 50% greater than on the BlackBerry Q10 (2 design units of padding = 27 real pixels and 6 design units of margin = 81 real pixels).

Static asset selection

Cascades includes a static asset selector that automatically chooses the best assets (image assets and .qml files) for a device with a particular resolution or pixel density. You don't even need to rebuild or repackage your app to select the right assets; Cascades automatically selects the best set of assets for a particular device at runtime. This feature lets you create images and QML code that are designed for a specific resolution or pixel density and use them automatically when you build your project. You can also use the static asset selector to choose assets based on the theme (bright or dark) that your target device is using.

To learn more about this feature, see Static asset selection.

Nine-slice scaling

Nine-slice scaling is a technique that lets you create images that can scale uniformly. You can specify the dimensions of the corners and border of the image in a special metadata file (with an .amd extension). When the image is resized (for example, to fit a different screen resolution), only the middle of the image scales; the corners and border stay the same.

By using nine-slice scaling, you can ensure that the images in your apps aren't distorted when they're viewed in different resolutions. For example, look at the image to the right. If this image represents a text area or chat box in your app, you might want it to be able to expand without distorting the width of the borders.

Diagram showing a text area or chat box example.

Here's what the scaled version of this image might look like. The image on the left doesn't use nine-slice scaling, but the image on the right does.

 
Diagram showing an incorrectly scaled version of the example image.
Diagram showing a correctly scaled version of the example image.
 

When you use nine-slice scaled images on devices with different screen densities, the images might still look different from device to device. On devices with higher screen densities, edges appear thinner and corners appear sharper than on lower density devices. Depending on your app, you might want to provide different images for the different screen densities by using the static asset selector.

To learn more about nine-slice scaling and how to apply it in your apps, see Image assets.

Last modified: 2014-11-17



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus