This post was initially published for AppInventor classic, but currently sources are available also for AI2. However, I have not updated the comments and images because the code is roughly the same.
This post explains a mechanism to resize the visual components when the screen device changes its orientation (i.e. from vertical to horizontal or vice versa). The objective is to avoid that these components get out of view when the screen height or width is shorter than in the original design.
It’s important to remark that the technique explained in this post can be adapted to achieve other purposes such as those mentioned in the Further Ideas chapter:
- Adapt the components size in order to fit in the screen for devices with different size
- Provide multilingual capabilities to an app
What are we going to build
We’ll typically design our app with vertical orientation, so when the orientation changes we need to “flatten” the design. For example, the app we’re going to follow contains a menu screen designed in a vertical layout to show 4 square buttons and a small button below (see image), but when orientation changes this shape provokes the lower buttons to disappear from the screen. Our solution converts these buttons into rectangles in order for them to fit in the screen.
Of course this adaptation is not always necessary or desirable. Actually you’ll probably want to avoid it for textbox components because reducing its original height is not likely to be a good idea.
Instead of resizing components, we could consider the option of moving them (fpr example the central screen in the image would be perfectly valid if we were able to place the small red button in the empty right side zone). However this would be nearly impossible in AI because the components’ position attribute is not modifiable by code (it’s rather assigned automatically). Maybe a solution based on hiding and showing arrangements could be an idea to explore but I’m not sure this would really work.
How it works
The idea is to divide the screen in an imaginary grid, with each component being fully located in some of the resulting cells and taking into account both orientations. In our example the grid could have a size of 9 x 11 which would give the following appearance in both orientations:
This division allows the algorithm to work out the X and Y position of each component by multiplying the left-top position within the grid by the width of each cell and the same for the Y position.
For example, if our screen size is 440 x 270, the button’s position would be:
|Cell Size||Blue button||Cyan button||Green button||Yellow button|
|Horizontal||30 x 40||(60, 80)||(180, 80)||(60, 240)||(180, 240)|
|Vertical||40 x 30||(80, 60)||(200, 60)||(80, 240)||(200, 240)|
You can try the application by downloading these sources and uploading them into App Inventor. You’ll realise there’s some functionality that is not explained in this tutorial because this is actually a more general application (which is explained in the Login template post). The part that concerns us is just the menu window.
The components are very simple. No secrets with buttons: one for each option and an additional one for “Exit”. Labels are somewhat trickier because they’re used to leave some space between buttons. The TableArrangementMenu1 has 4 columns and 5 rows and the labels are positioned in:
- Label1_3: Row 1, Column 3
- Label_3_1: Row 3, Column 1
- Label_5_3: Row 5, Column 3
To achieve the layout required, you can specify the desired height and width for these labels. However, these attributes will be overridden by code.
- iScreenHeight, iScreenWidth: Store the screen height and width. They are updated at initialization time and each time the screen orientation changes
- iHeight, iWidth: Auxiliary variables
- Screen1.Initialize, Screen1.OrientationsChanged: These are the two events that concern us, as they are the points when the initialization of components size takes place by means of a call to the initMenusize function
- initMenuSize: This is the actual function that places the components according to the screen current size. Since we cannot indicate the position, the secret consists in specifying the height and width for both the buttons and the spacer labels strategically placed between buttons (see above). The calculations should not be difficult to understand if we have the grid image in mind. Note that the example above was simplified so the numbers in the code are slightly different. For example the buttons occupy 3 x 4 cells (rather than 3 x 3) and the screen is divided in 13 x 11 cells (rather than 11 x 9) because we have some extra space occupied by a header bar. I advise you to experiment different combinations to fully understand the algorithm.
The work we’ve done in this tutorial can be a first step to achieve other useful functionalities. In this final chapter I expose a couple of such extensions that I’ve developed and are explained in this tutorial.
Adapt components size to different devices
Supporting different devices without having to create different versions of the app is probably a much more common requirement than adapting to orientation change. The good news is that our technique is quite close to the solution. For example, the menu built in this tutorial would be correctly displayed in a larger device such as a tablet. For the whole app to be smartly presented we must simply adapt the initMenuSize function in order to apply the same resizing technique to all of our compoments. Of course this would be much more tedious, so rather than explicitly indicating each component in the function, we must previously create a list of components, and loop through this list in the initMenuSize function. This is not a five minutes task so please refer to the above mentioned tutorial for the details.
Similarly, if we intend our app to be able to “speak” several languages, we won’t obviously want to rewrite it for each supported language, but rather think of a means to modify the text of captions and labels at initialization time in order to show them in the language chosen by the user. Once again the idea is looping through all components in an initialization function, in order to set the caption corresponding to the current language. The captions mst be obtained from a list populated at design time and stored for example in a TinyWebDB component. Another fascinating project that deserves a tutorial on its own.