×
Namespaces

Variants
Actions

Layout-awareness challenges in custom UIs

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata
Compatibility
Platform(s):
Symbian
Article
Created: mopius (07 Jun 2007)
Last edited: hamishwillee (27 Jul 2012)

Contents

Screen Layout Aware Application

Especially regarding the Symbian Signed criteria, it’s very important for your application to adapt to various screen sizes and layouts. Especially if you have your own custom layout (e.g. in a game) and don’t use the standard Symbian OS system components, there are many things you have to take care of. These code snippets will help you with some of the most common tasks you will encounter when making your application dynamically screen-aware. This mainly includes two things:

1. You will most likely have a background graphic. This should always fill the whole screen, so that you don’t have a border of some color around it. As the aspect ratio of the various screen resolutions differ, you either have to design an own graphic for each one of them, or resize it with or without keeping the aspect ratio. Of course, only keeping the aspect ratio leads to optically pleasing results. This is done by the function provided below. You will loose some part of the image on the right hand side or the bottom, but this is something you just have to live with.

2. The softkey labels will most likely be drawn by you, as the standard system softkey labels won’t fit to your own layout. However, the position and size of them can change depending on the current phone configuration. For example, in portrait mode they are at the bottom, in landscape mode they are on the right hand side (usually). There are some things you have to be aware of when querying the softkey label positions, the example code below shows you how to do it.

Landscape Softkeys.png

Background graphics

Depending on how your application is built, you have to resize an .svg-image in the background so that it fills the whole screen – of course while still keeping the aspect ratio. If you want this to work, you have to put all the important information into the upper left corner, as you cannot be sure how much of the rest will be actually visible on different screen resolutions / orientations. The following utility function returns the size required to fill the whole screen size (specified via a parameter) with an .svg-bitmap:

TSize CJourneyCMenu::SetSizeToFullScreen(TSize aScreenSize, CFbsBitmap* aBitmap)
{
if (!aBitmap)
return TSize(0, 0);
 
// To prevent re-loading of the bitmap from the storage medium
AknIconUtils::PreserveIconData(aBitmap);
 
// Get the dimension of the svg file, as it is defined in the file.
TSize bmpSize;
AknIconUtils::GetContentDimensions(aBitmap, bmpSize);
 
// Check the screen size, if it's not valid, return the size
// of the svg as it would normally be
if (aScreenSize.iWidth == 0 || aScreenSize.iHeight == 0)
return bmpSize;
 
// Calculate the aspect ratio of the scrren and the bitmap
const TReal aspectScreen = (TReal)aScreenSize.iWidth /
(TReal)aScreenSize.iHeight;
const TReal aspectBmp = (TReal)bmpSize.iWidth / (TReal)bmpSize.iHeight;
TSize finalSize;
 
// Set the size of the bitmap so that it fills the whole screen.
// If the aspect ratio is different, this method ensures that the whole
// screen is filled and the svg is clipped -> some parts of the svg
// won't be visible, but the screen size bitmap won't have empty
// spots and the aspect ratio of the bitmap won't change.
if (aspectScreen <= aspectBmp)
{
finalSize.iHeight = aScreenSize.iHeight;
finalSize.iWidth = (TInt)((TReal)aScreenSize.iHeight *
aspectBmp);
}
else
{
finalSize.iWidth = aScreenSize.iWidth;
finalSize.iHeight = (TInt)((TReal)aScreenSize.iWidth /
aspectBmp);
}
return finalSize;
}

The code first retrieves the aspect ratio of the bitmap and the screen size (= parameter). In the following if-statement, it checks whether it has to make the width of the bitmap equal to the screen width (losing some of the bottom of the bitmap image) or whether it has to fix the height to the screen height (therefore losing some of the right of the bitmap image).

The calculated size is returned, which is larger than the screen size – except if the bitmap had exactly the same aspect ratio as the screen.

Get informed about the new Layout

When the size or the orientation of the screen is changed (e.g. due to the user switching to landscape mode), your application is automatically notified if your container class overrides the HandleResourceChange()-function. As we want to continue using full screen mode, the best thing to do is to simply call the SetExtentToWholeScreen()-function in case the event was a layout switch.

void CJourneyCMenu::HandleResourceChange(TInt aType)
{
CCoeControl::HandleResourceChange(aType);
if (aType == KEikDynamicLayoutVariantSwitch)
{
// User switched the layout configuration
// or the screen resolution
// -> we have to recreate the layout
SetExtentToWholeScreen(); // Results in a call
// of SizeChanged()
}
}

Calling this function makes the framework start SizeChanged(), which is again a function that you have to override from the base class of your container. There, we will put the code to handle the new layout and size of the control.

Adapting the Softkey Positions

In the SizeChanged() function, you have to query the new screen size and do two main things:

  1. Adapt the background .svg-graphic (iBmpIntroScreen) so that it still fills the whole screen.
  2. Get the position of the softkey labels (different for landscape mode!).

For the following code to work, you will need to declare the following variables as private instance variables in your container class:

	/** Member variable storing the intro screen bitmap. */
CFbsBitmap* iBmpIntroScreen;
 
/** Position and size of the first
(options, ok) softkey. */

TRect iSk1Rect;
/** Text alignment of the first softkey.
Important because of landscape mode. */

CGraphicsContext::TTextAlign iSk1Align;
/** Position and size of the second
(back, cancel) softkey. */

TRect iSk2Rect;
/** Text alignment of the second softkey.
Important because of landscape mode. */

CGraphicsContext::TTextAlign iSk2Align;

Essentially, they contain information on where the softkey labels should be drawn to the screen plus the bitmap class that has to contain the .svg image that is used as the background image. How this image file is loaded is not shown in this tutorial to keep the size short enough.

The interesting part here is the SizeChanged() method, which will be called on every layout change due to the function shown above. It determines the new border that should be displayed around the softkey labels as well as the position and orientation of the softkey labels:

void CJourneyCMenu::SizeChanged()
{
const TSize newScreenSize = this->Size();
 
const CFbsFont* fontNormal = (CFbsFont*)AknLayoutUtils::
LayoutFontFromId(EAknLogicalFontPrimaryFont);
 
// Make sure the whole screen is covered by the background image
AknIconUtils::SetSize(iBmpIntroScreen, SetSizeToFullScreen(newScreenSize,
iBmpIntroScreen));
// Get location of softkeys
AknLayoutUtils::TAknCbaLocation cbaLocation =
AknLayoutUtils::CbaLocation();
 
if (cbaLocation == AknLayoutUtils::EAknCbaLocationBottom)
{
// Portrait mode
AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EControlPane,
iSk1Rect);
iSk2Rect = iSk1Rect;
// Give each softkey only half of the total width
// Otherwise each of those would have the whole area.
// When printing the text with a background color, this would
// cause the second label to overwrite the first.
iSk1Rect.SetWidth(iSk1Rect.Width() / 2);
iSk2Rect.iTl.iX = iSk1Rect.iBr.iX;
iSk1Align = CGraphicsContext::ELeft;
iSk2Align = CGraphicsContext::ERight;
}
else
{
// Landscape mode
AknLayoutUtils::LayoutMetricsRect(
AknLayoutUtils::EControlPaneTop, iSk2Rect);
AknLayoutUtils::LayoutMetricsRect(
AknLayoutUtils::EControlPaneBottom, iSk1Rect);
// Fix for wrong (?) rectangle returned by 3rd Edition SDK
iSk1Rect.iTl.iY = iSk1Rect.iBr.iY - iSk2Rect.Height();
// Check whether the softkey labels are right or left
// of the screen -
// this also influences the alignment of the text labels
if (cbaLocation == AknLayoutUtils::EAknCbaLocationLeft)
{
iSk1Align = iSk2Align = CGraphicsContext::ELeft;
}
else
{
iSk1Align = iSk2Align = CGraphicsContext::ERight;
}
 
// Check if the softkey layout is reversed and the options button
// is on top (e.g. for the E90 communicator)
if (IsOptionsButtonOnTop())
{
// Yes... swap the rectangles of the softkeys.
TRect tmpRect = iSk1Rect;
iSk1Rect = iSk2Rect;
iSk2Rect = tmpRect;
}
}
}
 
TBool CJourneyCMenu::IsOptionsButtonOnTop()
{
CEikButtonGroupContainer* cba = CEikButtonGroupContainer::Current();
if( !cba )
{
return EFalse;
}
 
CCoeControl* options = cba->ControlOrNull( EJourneyCmdMenu );
CCoeControl* exit = cba->ControlOrNull( EJourneyCmdExit );
 
if( options && exit )
{
if( options->Position().iY < exit->Position().iY )
{
return ETrue;
}
}
return EFalse;
}

One of the more tricky parts of this code is that the LayoutMetricsRect() function, which should return the position and size of the softkey labels, returns a very large area for the first softkey. On the vertical axis, it fills the whole screen. As the area for the second softkey is OK, the same height is applied to the height of the first softkey label.

Also, the alignment is different depending on the layout. For portrait, the first softkey is left-aligned and the second one right-aligned. In landscape mode, both orientations are possible. Therefore, the softkeys can either be on the left or the right side. Naturally, the text also has to be printed with the according alignment.

Another thing to be aware of is that some mobile phones might have the softkey layout reversed with the options button on top in landscape mode. Currently, this is the case for the Nokia E90 Communicator. By retrieving the location of the softkey controls, you can find out which one should be higher up on the screen. Adapt the menu commands with your own IDs from the menu definition (.hrh).

Drawing the Softkey Labels

The last part of this tutorial shows you how to use the values calculated above to actually draw the background graphic and the softkey labels to the screen.

void CJourneyCMenu::Draw(const TRect& aRect) const
{
CWindowGc& gc = SystemGc();
 
// Background image
gc.BitBlt(TPoint(0, 0), iBmpIntroScreen);
 
// Set up the drawing style for the softkey labels
gc.SetPenColor(KTextColor);
gc.SetBrushColor(KBgColor);
gc.SetBrushStyle(CGraphicsContext::ESolidBrush);
const CAknLayoutFont* font = AknLayoutUtils::LayoutFontFromId(
EAknLogicalFontPrimaryFont);
gc.UseFont((CFont*) font);
CFbsFont* f = (CFbsFont*) font;
 
TBuf<30> textResource;
 
// Left softkey
CEikonEnv::Static()->ReadResource(textResource, R_MENU_OPTIONS);
gc.DrawText(textResource, iSk1Rect, f->FontMaxAscent(), iSk1Align, 3);
 
// Right softkey
CEikonEnv::Static()->ReadResource(textResource, R_MENU_EXIT);
gc.DrawText(textResource, iSk2Rect, f->FontMaxAscent(), iSk2Align, 3);
 
gc.DiscardFont();
}

Please note that you have to define your own text resources, as your application most likely won’t have R_MENU_OPTIONS and R_MENU_EXIT defined (yet...). The rest of the drawing code is rather straightforward. After retrieving the graphics context of the screen, it first draws the background image with the size that we have already calculated.

Next, the context is set up with the text and background color. Using the rectangles calculated in the SizeChanged()-methods, the text is drawn. Because of the text alignment and using the whole rectangle (which is usually larger than the text would be), the whole area in the back of the text is shaded with the background color, ensuring that it’s readable on the device.

Conclusions

If you’re using your own custom graphics instead of the system UI components, you have to make sure that your application dynamically adapts to screen size and orientation changes. The code of this tutorial shows how to resize an .svg background image while still keeping its aspect ratio as well as how to find out the softkey label locations together with the required text alignments. With just a few modifications, you should be able to integrate the code snippets from above into your own program. Have fun!

This page was last modified on 27 July 2012, at 08:30.
39 page views in the last 30 days.