This post is about how to design your Qt app or game to support multiple screen sizes and screen densities. This also includes how to write your application to support multiple aspect ratios. There is little information about this topic online yet, so let’s summarize why it is important…
To create a game or app for smartphones, tablets and desktop with a single code base, you probably ask yourself these questions:
- How big shall I make buttons and other UI elements so they look good on all devices?
- How big shall the Text elements be?
- How big shall I make my images?
In the next sections we’ll give an answer to all these questions, so read on. 🙂
NOTE: Scroll down to download the slides of our presentation at Qt Developer Days Europe in October 2014, which had a similar topic: How to Develop with Qt for Multiple Screen Resolutions and Increase Your User Retention (of Cross-Platform Apps and Games)
Adaptive Layout & Scalable Design
Let’s start with answering the question: How big shall I make buttons and other UI elements so they look good on all devices?
Scalable Layout in Qt
The simplest approach is to just set the size of elements based on the ApplicationWindow or GameWindow size. For example this code:
ApplicationWindow {
width: 800
height: 600
Rectangle {
height: parent.height*0.2
width: parent.width
color: "blue"
}
Rectangle {
anchors.bottom: parent.bottom
height: parent.height*0.3
width: parent.width
color: "lightgrey"
Image {
source: "../assets/qt-partner-logo.png"
height: parent.height * 0.8
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
}
}
Text {
anchors.right: parent.right
anchors.rightMargin: parent.width*0.05
text: "Scaled Text"
font.pixelSize: parent.height*0.1
}
}
Looks like that in in landscape mode (left) and in portrait mode (right):
This can be sufficient for most use cases. But for devices that have a more “square” aspect ratio, like the iPad with 1024×768 pixels (a 4:3 ratio), the elements will appear higher than for example on a 16:9 device. It gets even more extreme in a portrait resolution (most of the screen is covered with the header and footer not with the white content area).
Probably not what you want, right? And definitely not the best use for the available screen space. Also, the image in the bottom and Text on the top will be much bigger on an iPad in physical size.
Density-Independent Pixels
If you run the previous code on an iPad, the physical size of the blue rectangle and the image will be much bigger than on an iPhone 4. The reason is that the physical dimensions (in millimeters or inches) of the whole screen is much bigger on the tablet than on phone. so you get a height of about 3cm for the blue rectangle on iPad, versus only 2cm on the phone.
In most UI driven apps or games, you’d like to have exact same physical size for UI elements. So a button should be as big that it is touchable with a finger. Not bigger to waste space, and definitely not smaller that makes it untouchable on smaller devices.
That’s why we added support for density independence in Qt to the newest Felgo update 2.2.0!
With the new properties, you can define the physical size of buttons, Rectangles or any QML Items.
Density Independence Support: dp, sp, pixelToInches, tablet, orientation
You can use these new Felgo properties & methods for both GameWindow and Scene:
- dp() – A density-independent pixel; 160 dp at a 160 dpi screen equals 1 inch
- sp() – A scale-independent pixel; same as dp() but use it for Text elements with font.pixelSize
- pixelToInches() – Convert the pixel value to inches
- tablet – Gets true if the display’s diameter is bigger than 5 inches
- portrait / landscape – Either one of these is true, depending if the device is held portrait or landscape
Example of Adaptive Layout
As you can see on this image, the physical size of one element in the ListView is the same across devices, although they have different screen resolutions, aspect ratios and screen densities!
So on tablets, there are more rows shown than on the phone.
In contrast, with the fixed percentage approach, the same amount of rows would be shown and it gets scaled to fit the screen:
This makes the elements quite big on tablets. It is up to the requirements of your app or game, if this is still okay.
You can have a look at the code of this app here. It is also part of the Felgo SDK, available in the folder <QtSDK>/Examples/Felgo/examples/densityIndependence.
Responsive Screen Design with Qt
A responsive app makes best use of the available screen size. So on a large display like tablets, more information can be displayed than on smartphones.
You can achieve that by using the adaptive layout functions provided by Felgo. For example dp() to get density-independent units. In addition, you can control what to show on different screens to make the best use of the available screen space. For example:
- On a Tablet in landscape mode, always show a side bar. The content area can be more detailed
- On a Tablet in portrait mode, make the side bar slideable but do not show it constantly. The content are can be more detailed.
- On a phone in landscape mode, always show a side bar. The content area shall be less detailed.
- On a phone in portrait mode, the side bar slideable but do not show it constantly. The content area shall be less detailed.
You can implement that example with a Loader component and the properties provided by Felgo tablet, portrait and landscape:
import Felgo 3.0
import QtQuick 2.0
GameWindow {
id: gameWindow
Scene {
id: scene
Loader {
id: sideBarLoader
// only show in landscape mode
active: gameWindow.landscape
sourceComponent: sideBarComponent
// hide the loader if not in landscape
width: gameWindow.landscape ? scene.dp(120) : 0
// fill the whole screen height
anchors.top: scene.gameWindowAnchorItem.top
height: scene.gameWindowAnchorItem.height
anchors.left: scene.gameWindowAnchorItem.left
}// SideBarLoader
Component {
id: sideBarComponent
Rectangle {
color: "blue"
// ... content of SideBar here ...
}
}// SideBarComponent
ListView {
anchors.left: sideBarLoader.right
anchors.right: scene.gameWindowAnchorItem.right
height: scene.gameWindowAnchorItem.height
delegate: listViewDelegate
spacing: scene.dp(2)
model: [ "", "", "", ] // some test data to display the rows
}// ContentListView
Component {
id: listViewDelegate
Rectangle {
// make a row bigger if the device is a tablet
height: gameWindow.tablet ? scene.dp(80) : scene.dp(48)
width: parent.width
color: "grey"
// ... content of ListView row here ...
}
}// ListViewDelegate
}
}
Content Scaling alias Virtual Sizes
In most apps and especially games, having different physical sizes for UI elements is not that much of an issue. However, using multiplications with parent.height or parent.width like explained in the Scalable Layout in Qt section messes up the code and can break your layout as you have seen above.
That’s why we added a concept called content scaling to Felgo. It works like that:
1.) Use a GameWindow as the root element for your app.
2.) Define a virtual size of a Scene (480x320px by default for landscape), which is then visible on all screens & aspect ratios. This Scene is also called the safe zone.
3.) Set the size of this Scene’s children relative to the base size. For example a button with size 48×32 logical pixels is 10% the height and width of the window.
This is how the code looks like:
import Felgo 3.0
import QtQuick 2.0
GameWindow {
id: gameWindow
// the default Window size at app start on Desktop
// change it to your preferred start size during development
// on mobile devices, the app is shown fullscreen
width: 960
height: 640
Scene {
id: scene
// the default logical size is 480x320
// use 320x480 for portrait apps & games
Rectangle {
width: 48
height: 32
Text {
font.pixelSize: 14
text: "14px content-scaled"
}
}// Rectangle
Rectangle {
// like that you can anchor to the GameWindow (the screen)
anchors.bottom: scene.gameWindowAnchorItem.bottom
anchors.left: scene.gameWindowAnchorItem.left
}
}// Scene
}
Internally, we then calculate the scale factor based on the GameWindow size and the Scene size. For example a 960x640px screen like the iPhone 4S, has a xScale and yScale factor set to 2. Based on the scaleMode, you can adjust what happens if the xScale and yScale is not be the same (a non-uniform scale), like with an iPad with 1024x736px resolution.
The default setting is letterbox, which guarantees your Scene is always visible on all devices. If the device then has an aspect ratio of 4:3 like the iPad for example, you have additional space on the top and bottom which you can fill with a bigger background to make it look good.
You can download a Photoshop template for your background and the safe zone here. This is how it looks like:
Having the same virtual size has a couple of advantages, especially for games:
- Allows to position your game entities on a logical scene for equal and fair game experience across all platforms.
- Easy positioning of UI elements based on the screen and not based on the logical scene. See here for a tutorial.
- Easily define physics objects across devices. Without this addition, each physics object would behave differently because its dimensions would depend on the physical screen size.
- Keep entity positions in levels consistent across screen resolutions: If one level is saved on one resolution and then played on another device with a different resolution, the level would look different.
Content scaling can be used with the MultiResolutionImage, AnimatedSpriteVPlay and SpriteSequenceVPlay components. For a tutorial & more information, see How to create mobile games for different screen sizes and resolutions.
How to Test Your Application on Multiple Screens
With Felgo, you can easily test how your app will look like on multiple devices:
Just press Ctrl (or Cmd on Mac) + the number keys [1-7] to switch between the most used resolutions for devices, including iPhone 4, iPhone 5, iPad and Android’s Nexus device resolutions. That allows you to quickly test how your app looks like on different aspect ratios!
For testing the real physical sizes, deploy your app to a phone as early as possible. Ideally to a phone and a tablet, so you get the best feel for the user experience.
Dynamic Image Switching alias Retina Image Support in Qt
So, the initial question was: How big shall I make my images? Well, let’s see…
There are devices with very different screen resolutions ranging from 480×320 pixels up to 2048×1536 or even more pixels. What quality shall your images have to look good on all of these devices?
If you make your graphics for a very high resolution, the performance will be bad on low-end devices. And if you choose a low resolution, your image will look blurry on the high-resolution devices.
The solution for this problem, is to provide multiple versions of the same image. That means, you create 2 or 3 different versions of your image. Apple uses a file suffix to identify such retina images – so if you have an image called standard.png with the resolution of 100×50 px, you would create a 200x100px and name it standard@2x.png. The same applies for a @3x image. Qt is supporting this on iOS & Mac platforms beginning with Qt 5.4.
However, the Apple approach has its disadvantages if you want to go cross-platform. First, it is not supported on Android. Second, for the wide range of Android devices & available displays, this separation is often not enough.
In most cases, you’d like to choose the retina images if the device has an almost retina like display.
In Felgo, you can do that with the MultiResolutionImage component! We already added the screen detection to Felgo and automatically choose the best image version for each screen.
In order for this to work, provide three different images:
- imageSwitching.png (e.g. 50×20 px)
- +hd/imageSwitching.png (e.g. 100×40 px)
- +hd2/imageSwitching.png (e.g. 200×800 px)
You can use this MultiResolutionImage like a normal Image component in QML. The code snippet below selects the best image automatically, and it will always have a reported (logical) size of 50 x 20 with the image from the example above:
This approach is similar to Android, where you can add different images per density bucket ldpi (ld), mdpi (sd), hdpi (hd) and xhdpi (hd2) in separate directories. The + in front of the directory name allows us to use the Qt File Selectors internally, which are implemented in C++ and thus really fast.
In addition, you can customize the file selectors to match your game requirements best. For example, if you target a lot of low-end devices: simply add a +ld directory and put the image with 75% of the standard size there. You can then set your custom scale sizes with the VPApplication::setContentScaleThresholds() method.
To summarize, you get the following benefits with dynamic image switching:
- Save graphics memory: based on the screen size, the best matching image is selected to avoid upscaled and blurry images, and to save memory compared to an approach where always the highest resolution images are used.
- Improve loading times: as the best-sized image is selected, you get an ideal loading time of the used image.
- Work with the logical size of the image in your GameWindow and Scene to simplify multi-resolution positioning and to use Content Scaling.
For more information on Dynamic Image Switching and Felgo File Selectors, see here.
Note: The disadvantage of this approach is that your app size gets bigger, because you add each image multiple times (with different resolution). If you have a strong concern about app size, you could provide a normal version of your app with only the sd images. And additionally a “Pro”, “Tablet” or “Retina” version of your app binary including the hd and hd2 images. Alternatively, you can use our DownloadablePackage component, and download higher resolution images from within the app if the device has a retina screen. You can trigger this download as you like: for example at first app start, from a button within your app or game, or when a user has spent a while in your app. This helps to keep the download package of the app small, while your users get the best images for their screens.
Download Qt Developer Days Presentation “How to Develop with Qt for Multiple Screen Resolutions”
Just recently, we did a 1-hour presentation at Qt Developer Days Europe 2014 about this topic. You can download and view the slides here:
Summary & Conclusion
Was this a bit overwhelming? And do you ask yourself “So when should I use which of these techniques?”
We recommend the following:
- For games and most apps, use the content scaling approach. So add a GameWindow and Scene component to your game and use the Scene::gameWindowAnchorItem to use the whole screen. Especially for games, this will save you time & headaches in the long run.
- You can still use adaptive layout and density independence with the content scaling approach. In that case, use scene.dp() for example for the same physical sizes of rows in a ListView.
- Use font.pixelSize for Text elements and put them as children or sub-children of a Scene with the content scaling approach. If you use the adaptive layout approach, use scene.sp() for font.pixelSize values of Text elements.
- Always use MultiResolutionImage to get sharp images. Create your images for a hd2 resolution initially (where the logical size of the screen is 1920×1280). For example if you want your UI element to be 100x50px in your logical scene size of 480×320, make it 4x as big (400x200px) and then scale it down to half (hd) and quarter (sd) of the size. That way, you have 3 images: the sd version with 100x50px in the root folder, and one in the +hd and +hd2 folders. Download our Photoshop template here for a hd2 resolution template of the background image.
- The recommended button height is 48dp. Or 48 logical pixels when you use content scaling with the GameWindow & Scene components. Keep that in mind when designing your UI.
Another great resource for learning these concepts are the sample games we provide with the Felgo SDK. You can find them after downloading Felgo here: <QtSDK>/examples/Felgo/demos. For a summary of these published sample games, see here.
References
Here is a list of interesting articles about this topic on the web:
Guide for Supporting Multiple Screen Sizes & Screen Densities with Qt & Felgo (Felgo Doc)
Metrics & Grids Guide for 48dp button height (Android)
Multi-Pane Layouts (Android)
Supporting Different Screen Sizes (Android)
Supporting Different Densities (Android)
Scalability tips (Qt Doc)
Web tool to find out your development PC dpi and device-pixel-ratio