Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
scalabilityintro.qdoc
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5 \title Scalability
6 \page scalability.html
7 \brief How to develop applications that scale well on devices with different
8 screen configurations and UI conventions.
9 \ingroup explanations-accessibility
10
11 When you develop applications for several different mobile device platforms,
12 you face the following challenges:
13
14 \list
15 \li Mobile device platforms support devices with varying screen
16 configurations: size, aspect ratio, orientation, and density.
17 \li Different platforms have different UI conventions and you need to
18 meet the users' expectations on each platform.
19 \endlist
20
21 Qt Quick enables you to develop applications that can run on different types
22 of devices, such as tablets and handsets. In particular, they can cope
23 with different screen configurations. However, there is always a certain
24 amount of fixing and polishing needed to create an optimal user experience
25 for each target platform.
26
27 You need to consider scalability when:
28
29 \list
30 \li You want to deploy your application to more than one device
31 platform, such as Android and iOS, or more than one
32 device screen configuration.
33 \li Your want to be prepared for new devices that might appear on the
34 market after your initial deployment.
35 \endlist
36
37 To implement scalable applications using \l{Qt Quick}:
38
39 \list
40 \li Design UIs using \e {Qt Quick Controls} that provide sets of UI controls.
41 \li Define layouts using \e {Qt Quick Layouts}, which can resize their
42 items.
43 \li Use \e {property binding} to implement use cases
44 not covered by the layouts. For example, to display alternative
45 versions of images on screens with low and high pixel density or
46 automatically adapt view contents according to the current screen
47 orientation.
48 \li Select a reference device and calculate a \e {scaling ratio} for
49 adjusting image and font sizes and margins to the actual screen
50 size.
51 \li Load platform-specific assets using \e {file selectors}.
52 \li Load components on demand by using a \e {Loader}.
53 \endlist
54
55 Consider the following patterns when designing your application:
56
57 \list
58 \li The contents of a view might be quite similar on all
59 screen sizes, but with an expanded content area. If you use the
60 ApplicationWindow QML type from Qt Quick Controls, it will
61 automatically calculate the window size based on the sizes of its
62 content items. If you use Qt Quick Layouts to position the content
63 items, they will automatically resize the items pushed to them.
64 \li The contents of an entire page in a smaller
65 device could form a component element of a layout in a
66 larger device. Therefore, consider making that a separate
67 component (that is, defined in a separate QML file), and in the
68 smaller device, the view will simply contain an instance of
69 that component. On the larger device, there may be enough
70 space to use loaders to show additional items. For example, in an
71 email viewer, if the screen is large enough, it may be possible to
72 show the email list view, and the email reader view side by
73 side.
74 \li For games, you would typically want to create a game board that does not
75 scale, so as not to provide an unfair advantage to players on larger
76 screens. One solution is to define a \e {safe zone} that fits the screen
77 with the smallest supported aspect ratio (usually, 3:2), and add
78 decorative-only content in the space that will be hidden on a 4:3 or
79 16:9 screen.
80 \endlist
81
82 \section1 Resizing Application Windows Dynamically
83
84 \l{Qt Quick Controls} provide a set of UI controls to create user interfaces
85 in Qt Quick. Typically, you declare an ApplicationWindow control as the root
86 item of your application. The ApplicationWindow adds convenience for
87 positioning other controls, such as MenuBar, ToolBar, and StatusBar in a
88 platform independent manner. The ApplicationWindow uses the size constraints
89 of the content items as input when calculating the effective size
90 constraints of the actual window.
91
92 In addition to controls that define standard parts of application windows,
93 controls are provided for creating views and menus, as well as presenting or
94 receiving input from users. You can use \l {Using Styles in Qt Quick Controls}{Qt Quick Controls Styles} to
95 apply custom styling to the predefined controls.
96
97 Qt Quick Controls, such as the ToolBar, do not provide a layout
98 of their own, but require you to position their contents. For this, you can
99 use Qt Quick Layouts.
100
101 \section1 Laying Out Screen Controls Dynamically
102
103 \l{Qt Quick Layouts} provide ways of laying out screen controls in a row,
104 column, or grid, using the RowLayout, ColumnLayout, and GridLayout QML
105 types. The properties for these QML types hold their layout direction and
106 spacing between the cells.
107
108 You can use the \l{Qt Quick Layouts} QML types to attach additional properties to the
109 items pushed to the layouts. For example, you can specify minimum, maximum,
110 and preferred values for item height, width, and size.
111
112 The layouts ensure that your UIs are scaled properly when windows and
113 screens are resized and always use the maximum amount of space available.
114
115 A specific use case for the GridLayout type is to use it as a row or a
116 column depending on the screen orientation.
117
118 \image scalability-gridlayout.png
119
120 The following code snippet uses
121 the \c flow property to set the flow of the grid from left to right (as a
122 row) when the screen width is greater than the screen height and from top to
123 bottom (as a column) otherwise:
124
125 \code
126 ApplicationWindow {
127 id: root
128 visible: true
129 width: 480
130 height: 620
131
132 GridLayout {
133 anchors.fill: parent
134 anchors.margins: 20
135 rowSpacing: 20
136 columnSpacing: 20
137 flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
138 Rectangle {
139 Layout.fillWidth: true
140 Layout.fillHeight: true
141 color: "#5d5b59"
142 Label {
143 anchors.centerIn: parent
144 text: "Top or left"
145 color: "white"
146 }
147 }
148 Rectangle {
149 Layout.fillWidth: true
150 Layout.fillHeight: true
151 color: "#1e1b18"
152 Label {
153 anchors.centerIn: parent
154 text: "Bottom or right"
155 color: "white"
156 }
157 }
158 }
159 }
160 \endcode
161
162 Constantly resizing and recalculating screens comes with a performance cost.
163 Mobile and embedded devices might not have the power required to recalculate
164 the size and position of animated objects for every frame, for example. If
165 you run into performance problems when using layouts, consider using some
166 other methods, such as bindings, instead.
167
168 Here are some things not to do with layouts:
169
170 \list
171
172 \li Do not have bindings to the x, y, width, or height properties of items
173 in a Layout, since this would conflict with the goal of the Layout, and
174 also cause binding loops.
175 \li Do not define complex JavaScript functions that are regularly
176 evaluated. This will cause poor performance, particularly
177 during animated transitions.
178 \li Do not make assumptions about the container size, or about
179 the size of child items. Try to make flexible layout
180 definitions that can absorb changes in the available space.
181 \li Do not use layouts if you want the design to be pixel perfect. Content
182 items will be automatically resized and positioned depending on the
183 space available.
184 \endlist
185
186 \section1 Using Bindings
187
188 If Qt Quick Layouts do not fit your needs, you can fall back to using
189 \l{Property Binding}{property binding}. Binding enables objects to
190 automatically update their properties in response to changing attributes in
191 other objects or the occurrence of some external event.
192
193 When an object's property is assigned a value, it can either be assigned a
194 static value, or bound to a JavaScript expression. In the former case, the
195 property's value will not change unless a new value is assigned to the
196 property. In the latter case, a property binding is created and the
197 property's value is automatically updated by the QML engine whenever the
198 value of the evaluated expression changes.
199
200 This type of positioning is the most highly dynamic. However, constantly
201 evaluating JavaScript expressions comes with a performance cost.
202
203 You can use bindings to handle low and high pixel density on platforms that
204 do not have automatic support for it (like Android, \macos and iOS do).
205 The following code snippet uses the \l{Screen}{Screen.pixelDensity}
206 attached property to specify different images to display on screens with
207 low, high, or normal pixel density:
208
209 \code
210 Image {
211 source: {
212 if (Screen.pixelDensity < 40)
213 "image_low_dpi.png"
214 else if (Screen.pixelDensity > 300)
215 "image_high_dpi.png"
216 else
217 "image.png"
218 }
219 }
220 \endcode
221
222 On Android, \macos and iOS, you can provide alternative resources with higher
223 resolutions by using the corresponding identifier (e.g. \e @2x, \e @3x,
224 or \e @4x) for icons and images, and place them in the resource file. The
225 version that matches the pixel density of the screen is automatically selected
226 for use.
227
228 For example, the following code snippet will try to load \e artwork@2x.png
229 on Retina displays:
230
231 \code
232 Image {
233 source: "artwork.png"
234 }
235 \endcode
236
237 \section1 Handling Pixel Density
238
239 Some QML types, such as \l Image, BorderImage, and \l Text, are
240 automatically scaled according to the properties specified for them.
241 If the width and height of an Image are not specified, it automatically uses
242 the size of the source image, specified using the \c source property. By
243 default, specifying the width and height causes the image to be scaled to
244 that size. This behavior can be changed by setting the \c fillMode property,
245 allowing the image to be stretched and tiled instead. However, the original
246 image size might appear too small on high DPI displays.
247
248 A BorderImage is used to create borders out of images by scaling or tiling
249 parts of each image. It breaks a source image into 9 regions that are scaled
250 or tiled according to property values. However, the corners are not scaled
251 at all, which can make the results less than optimal on high DPI displays.
252
253 A \l Text QML type attempts to determine how much room is needed and set the
254 \c width and \c height properties accordingly, unless they are explicitly
255 set. The \c fontPointSize property sets the point size in a
256 device-independent manner. However, specifying fonts in points and other
257 sizes in pixels causes problems, because points are independent of the
258 display density. A frame around a string that looks correct on low DPI
259 displays is likely to become too small on high DPI displays, causing the
260 text to be clipped.
261
262 The level of high DPI support and the techniques used by the supported
263 platforms varies from platform to platform. The following sections describe
264 different approaches to scaling screen contents on high DPI displays.
265
266 For more information about high DPI support in Qt and the supported
267 platforms, see \l{High DPI}.
268
269 \section2 High DPI Scaling
270
271 If a target device supports high DPI scaling, the operating system provides
272 Qt with a scaling ratio that is used to scale graphics output.
273
274 The advantage of this approach is that vector graphics and fonts scale
275 automatically and existing applications tend to work unmodified. For raster
276 content, high-resolution alternative resources are needed, however.
277
278 Scaling is implemented for the Qt Quick and Qt Widgets stacks, as well as
279 general support in Qt Gui.
280
281 Low level graphics APIs operate in device pixels. This includes code which
282 uses the OpenGL API, and code which uses the QRhi API. For example, this
283 means that a QWindow with a size() of 1280x720 and a
284 QWindow::devicePixelRatio() of 2 has a render target (swapchain) with a
285 device pixel size of 2560x1440.
286
287 The OS scales window, event, and desktop geometry. The Cocoa platform plugin
288 sets the scaling ratio as QWindow::devicePixelRatio() or
289 QScreen::devicePixelRatio(), as well as on the backing store.
290
291 For Qt Widgets, QPainter picks up \c devicePixelRatio() from the backing
292 store and interprets it as a scaling ratio.
293
294 However, in OpenGL, pixels are always device pixels. For example, geometry
295 passed to glViewport() needs to be scaled by devicePixelRatio().
296
297 The specified font sizes (in points or pixels) do not change and strings
298 retain their relative size compared to the rest of the UI. Fonts are
299 scaled as a part of painting, so that a size 12 font effectively becomes a
300 size 24 font with 2x scaling, regardless of whether it is specified in
301 points or in pixels. The \e px unit is interpreted as device independent
302 pixels to ensure that fonts do not appear smaller on a high DPI display.
303
304 \section2 Calculating Scaling Ratio
305
306 You can select one high DPI device as a reference device and calculate
307 a scaling ratio for adjusting image and font sizes and margins to the actual
308 screen size.
309
310 The following code snippet uses reference values for DPI, height, and
311 width from the Nexus 5 Android device, the actual screen size returned by
312 the QRect class, and the logical DPI value of the screen returned by the
313 \c qApp global pointer to calculate a scaling ratio for image sizes and
314 margins (\c m_ratio) and another for font sizes (\c m_ratioFont):
315
316 \code
317 qreal refDpi = 216.;
318 qreal refHeight = 1776.;
319 qreal refWidth = 1080.;
320 QRect rect = QGuiApplication::primaryScreen()->geometry();
321 qreal height = qMax(rect.width(), rect.height());
322 qreal width = qMin(rect.width(), rect.height());
323 qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
324 m_ratio = qMin(height/refHeight, width/refWidth);
325 m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));
326 \endcode
327
328 For a reasonable scaling ratio, the height and width values must be set
329 according to the default orientation of the reference device, which in this
330 case is the portrait orientation.
331
332 The following code snippet sets the font scaling ratio to \c 1 if it would
333 be less than one and thus cause the font sizes to become too small:
334
335 \code
336 int tempTimeColumnWidth = 600;
337 int tempTrackHeaderWidth = 270;
338 if (m_ratioFont < 1.) {
339 m_ratioFont = 1;
340 \endcode
341
342 You should experiment with the target devices to find edge cases that
343 require additional calculations. Some screens might just be too short or
344 narrow to fit all the planned content and thus require their own layout. For
345 example, you might need to hide or replace some content on screens with
346 atypical aspect ratios, such as 1:1.
347
348 The scaling ratio can be applied to all sizes in a QQmlPropertyMap to
349 scale images, fonts, and margins:
350
351 \code
352 m_sizes = new QQmlPropertyMap(this);
353 m_sizes->insert(QLatin1String("trackHeaderHeight"), QVariant(applyRatio(270)));
354 m_sizes->insert(QLatin1String("trackHeaderWidth"), QVariant(applyRatio(tempTrackHeaderWidth)));
355 m_sizes->insert(QLatin1String("timeColumnWidth"), QVariant(applyRatio(tempTimeColumnWidth)));
356 m_sizes->insert(QLatin1String("conferenceHeaderHeight"), QVariant(applyRatio(158)));
357 m_sizes->insert(QLatin1String("dayWidth"), QVariant(applyRatio(150)));
358 m_sizes->insert(QLatin1String("favoriteImageHeight"), QVariant(applyRatio(76)));
359 m_sizes->insert(QLatin1String("favoriteImageWidth"), QVariant(applyRatio(80)));
360 m_sizes->insert(QLatin1String("titleHeight"), QVariant(applyRatio(60)));
361 m_sizes->insert(QLatin1String("backHeight"), QVariant(applyRatio(74)));
362 m_sizes->insert(QLatin1String("backWidth"), QVariant(applyRatio(42)));
363 m_sizes->insert(QLatin1String("logoHeight"), QVariant(applyRatio(100)));
364 m_sizes->insert(QLatin1String("logoWidth"), QVariant(applyRatio(286)));
365
366 m_fonts = new QQmlPropertyMap(this);
367 m_fonts->insert(QLatin1String("six_pt"), QVariant(applyFontRatio(9)));
368 m_fonts->insert(QLatin1String("seven_pt"), QVariant(applyFontRatio(10)));
369 m_fonts->insert(QLatin1String("eight_pt"), QVariant(applyFontRatio(12)));
370 m_fonts->insert(QLatin1String("ten_pt"), QVariant(applyFontRatio(14)));
371 m_fonts->insert(QLatin1String("twelve_pt"), QVariant(applyFontRatio(16)));
372
373 m_margins = new QQmlPropertyMap(this);
374 m_margins->insert(QLatin1String("five"), QVariant(applyRatio(5)));
375 m_margins->insert(QLatin1String("seven"), QVariant(applyRatio(7)));
376 m_margins->insert(QLatin1String("ten"), QVariant(applyRatio(10)));
377 m_margins->insert(QLatin1String("fifteen"), QVariant(applyRatio(15)));
378 m_margins->insert(QLatin1String("twenty"), QVariant(applyRatio(20)));
379 m_margins->insert(QLatin1String("thirty"), QVariant(applyRatio(30)));
380 \endcode
381
382 The functions in the following code snippet apply the scaling ratio to
383 fonts, images, and margins:
384
385 \code
386 int Theme::applyFontRatio(const int value)
387 {
388 return int(value * m_ratioFont);
389 }
390
391 int Theme::applyRatio(const int value)
392 {
393 return qMax(2, int(value * m_ratio));
394 }
395 \endcode
396
397 This technique gives you reasonable results when the screen sizes of the
398 target devices do not differ too much. If the differences are huge, consider
399 creating several different layouts with different reference values.
400
401 \section1 Loading Files Depending on Platform
402
403 You can use the QQmlFileSelector to apply a QFileSelector to QML file
404 loading. This enables you to load alternative resources depending on the
405 platform on which the application is run. For example, you can use the
406 \c +android file selector to load different image files
407 when run on Android devices.
408
409 You can use file selectors together with singleton objects to access a
410 single instance of an object on a particular platform.
411
412 File selectors are static and enforce a file structure where
413 platform-specific files are stored in subfolders named after the platform.
414 If you need a more dynamic solution for loading parts of your UI on demand,
415 you can use a Loader.
416
417 The target platforms might automate the loading of alternative resources for
418 different display densities in various ways. On Android and iOS, the \e @2x
419 filename suffix is used to indicate high DPI versions of images. The \l Image
420 QML type and the QIcon class automatically load @2x versions of images and
421 icons if they are provided. The QImage and QPixmap classes automatically set
422 the \c devicePixelRatio of @2x versions of images to \c 2, but you need to
423 add code to actually use the @2x versions:
424
425 \code
426 if ( QGuiApplication::primaryScreen()->devicePixelRatio() >= 2 ) {
427 imageVariant = "@2x";
428 } else {
429 imageVariant = "";
430 }
431 \endcode
432
433 Android defines generalized screen sizes (small, normal, large, xlarge) and
434 densities (ldpi, mdpi, hdpi, xhdpi, xxhdpi, and xxxhdpi) for
435 which you can create alternative resources. Android detects the current
436 device configuration at runtime and loads the appropriate resources for your
437 application. However, beginning with Android 3.2 (API level 13), these size
438 groups are deprecated in favor of a new technique for managing screen sizes
439 based on the available screen width.
440
441 \section1 Loading Components on Demand
442
443 A \l{Loader} can load a QML file (using the \c source property) or a Component
444 object (using the \c sourceComponent property). It is useful for delaying the
445 creation of a component until it is required. For example, when a component
446 should be created on demand, or when a component should not be created
447 unnecessarily for performance reasons.
448
449 You can also use loaders to react to situations where parts of your UI are
450 not needed on a particular platform, because the platform does not support
451 some functionality. Instead of displaying a view that is not needed
452 on the device the application is running on, you can determine that the
453 view is hidden and use loaders to display something else in its place.
454
455 \section1 Switching Orientation
456
457 The \l{Screen}{Screen.orientation} attached property contains the current
458 orientation of the screen, from the accelerometer (if available). On a
459 desktop computer, this value typically does not change.
460
461 If \c primaryOrientation follows \c orientation, it means that the screen
462 automatically rotates all content that is displayed, depending on how you
463 hold the device. If orientation changes even though \c primaryOrientation
464 does not change, the device might not rotate its own display. In that case,
465 you may need to use \l{QtQuick::Item::rotation}{Item.rotation} or
466 \l{QtQuick::Item::transform}{Item.transform} to rotate your content.
467
468 Application top-level page definitions and reusable component
469 definitions should use one QML layout definition for the layout
470 structure. This single definition should include the layout design
471 for separate device orientations and aspect ratios. The reason for
472 this is that performance during an orientation switch is critical,
473 and it is therefore a good idea to ensure that all of the
474 components needed by both orientations are loaded when the
475 orientation changes.
476
477 On the contrary, you should perform thorough tests if you choose
478 to use a \l{Loader} to load additional QML that is needed in separate
479 orientations, as this will affect the performance of the
480 orientation change.
481
482 In order to enable layout animations between the orientations, the
483 anchor definitions must reside within the same containing
484 component. Therefore the structure of a page or a component
485 should consist of a common set of child components, a common set
486 of anchor definitions, and a collection of states (defined in a
487 StateGroup) representing the different aspect ratios supported by
488 the component.
489
490 If a component contained within a page needs to be
491 hosted in numerous different form factor definitions, then the
492 layout states of the view should depend on the aspect ratio of the
493 page (its immediate container). Similarly, different instances of
494 a component might be situated within numerous different containers
495 in a UI, and so its layout states should be determined by the
496 aspect ratio of its parent. The conclusion is that layout states
497 should always follow the aspect ratio of the direct container (not
498 the "orientation" of the current device screen).
499
500 Within each layout \l{State}, you should define the relationships
501 between items using native QML layout definitions. See below for
502 more information. During transitions between the states (triggered
503 by the top level orientation change), in the case of anchor
504 layouts, AnchorAnimation elements can be used to control the
505 transitions. In some cases, you can also use a NumberAnimation on
506 e.g. the width of an item. Remember to avoid complex JavaScript
507 calculations during each frame of animation. Using simple anchor
508 definitions and anchor animations can help with this in the
509 majority of cases.
510
511 There are a few additional cases to consider:
512
513 \list
514 \li What if you have a single page that looks completely
515 different between landscape and portrait, that is, all of the
516 child items are different? For each page, have two child
517 components, with separate layout definitions, and make one
518 or other of the items have zero opacity in each state. You
519 can use a cross-fade animation by simply applying a
520 NumberAnimation transition to the opacity.
521 \li What if you have a single page that shares 30% or more of
522 the same layout contents between portrait and landscape? In
523 that case, consider having one component with landscape and
524 portrait states, and a collection of separate child items
525 whose opacity (or position) depends on the orientation
526 state. This will enable you to use layout animations for the
527 items that are shared between the orientations, whilst the
528 other items are either faded in/out, or animated on/off
529 screen.
530 \li What if you have two pages on a handheld device that need to
531 be on screen at the same time, for example on a larger form
532 factor device? In this case, notice that your view component
533 will no longer be occupying the full screen. Therefore it's
534 important to remember in all components (in particular, list
535 delegate items) should depend on the size of the containing
536 component width, not on the screen width. It may be
537 necessary to set the width in a Component.onCompleted()
538 handler in this case, to ensure that the list item delegate
539 has been constructed before the value is set.
540 \li What if the two orientations take up too much memory to have
541 them both in memory at once? Use a \l{Loader} if necessary, if
542 you cannot keep both versions of the view in memory at once,
543 but beware performance on the cross-fade animation during
544 layout switch. One solution could be to have two "splash
545 screen" items that are children of the Page, then you cross
546 fade between those during rotation. Then you can use a
547 \l{Loader} to load another child component that loads the actual
548 model data to another child Item, and cross-fade to that
549 when the \l{Loader} has completed.
550 \endlist
551
552 \sa {Qt Quick Responsive Layouts}
553
554 */