Layouts, Positioning, and the View Hierarchy

Objective

In this chapter, you will learn how to lay out your user interface components using Titanium's various positioning properties. You will examine the coordinates system used by Titanium, its view hierarchy, and the layering and positioning rules that it follows when rendering your UI.

Contents

The current layout system is product of organic growth and development of our cross-platform layout system. Its specifics and particulars were formalized with the 2.0 SDK release as the "Composite UI Layout Specification." In addition to specifying various behaviors, the spec deprecates some features and sets the stage for the "declarative UI" that is currently being planned for a future release of Titanium. This guide covers the Composite UI spec.

In this guide, we're going to explore the following factors that affect how you position elements within your app's UI:

  • Units
  • The coordinates grid
  • Positioning and the view hierarchy
  • Layout modes
  • zIndex & default stacking order

Units

Placement and dimensions of UI elements are specified using a numeric value plus an implicit or explicit unit of measurement. If you don't specify a unit of measurement, the system unit is assumed. You can also set a default unit of measurement to use in your app by setting a tiapp.xml property.

First, a couple of definitions we'll use in the rest of this guide:

  • dip : Density-independent pixels. A measurement which is translated natively to a corresponding pixel measure using a scale factor based on a platform-specific "default" density, and the device's physical density.
  • System unit : A platform-dependent unit which is the default for how the system presents its view information to the native layout system. On Android this is pixels; on iOS it is dip.

Supported units are:

  • Absolute measurements
    • px : pixels
    • mm : millimeters
    • cm : centimeters
    • in : inches
    • dp/dip : Density-independent pixels (we sometimes call these "points")
      • Android : actual pixels = dip * (screen density) / 160
      • iOS : actual pixels = dip * (screen density) / 163 (effectively 1dip=1px on standard, 1dip=2px on retina)
      • Mobile Web: actual pixels = dip * (screen density) / 96 (effectively 1dip=1px because most browsers scale pages to 96dpi to make them consistent with desktops).
  • Relative measurements
    • % : Percentage of the size of the parent.
      • For x-axis values (width, left, right, center.x) this is relative to the parent's width
      • For y-axis values (height, top, bottom, center.y) this is relative to the parent's height.

You would use these units of measurement like this:

var view = Ti.UI.createView({
	/* You would not normally mix units like this */
	top: '10mm',
	left: '5px',
	width: '30%',
	height: 50 /* default system units are used here */
});

Setting default units in tiapp.xml

You can specify the default unit type to use with untyped values. To the tiapp.xml file, you would add

<property name="ti.ui.defaultunit" type="string">value</property>

Where value is one of px, mm, cm, in, dp, dip, or system. The "system" value corresponds to the platform-dependent system unit as described above.

The coordinates grid

Titanium uses a grid coordinate system for layout. Grid locations are based on the system unit (platform-dependent unit). This means that by default on iOS, elements are positioned on a density-independent grid and on Android on a density-dependent grid. The net result is that on iOS, elements are positioned in visually the same locations regardless of the actual density of the screen. On Android, elements are positioned at the same absolute pixel locations and might lay out differently depending on the device.

  • iPhone with either original or retina display is based on a 320 x 480 dip grid.
  • iPad is based on a 1024 x 768 dip grid.
  • Android device screen sizes vary. Considering these emulator examples:
    • HVGA emulator is 320 x 480 px
    • WVGA800 emulator is 480 x 800 px
    • WVGA854 emulator is 480 x 854 px

Remember that you can specify dp or dip units on Android (and even set an app-level default in tiapp.xml) to achieve the same density-independent grid as offered by default on iOS.

Positioning and dimensions of elements

Elements in Titanium are positioned relative to their parent container, such as the window or a view. Depending on the positioning properties you use, the reference point will be either the parent's top/left or bottom/right corner. We call this the "view hierarchy." Options include:

  • top and left properties, which specify the grid position of the element's top/left corner relative to the parent's top/left corner.
  • bottom and right properties, which specify the grid position of the element's bottom/right corner relative to the parent's bottom/right corner.
  • center property, which species the position of the element's center point relative to the parent's top/left corner.

(The size property provides the rendered size of the view, and thus is only available once both it and its ancestors have been fully drawn. This means it is also a read-only property; a dictionary with two properties, width and height.)

You specify element dimensions by setting the width and height properties. If you omit those properties, but set top and bottom the element's height will be calculated to put its top and bottom edges at those positions relative to the parent. The same is true for left and right.

Each of these attributes accept values with or without units, including percentage-based relative measurements as described above.

In the following example, the red view is positioned at the 20,20 point relative to the window's top/left corner. The yellow view's bottom/right corner is 100 points/pixels from the bottom/right corner of the display. The blue view's center is at 160,240 and given its width of 50, this means its top-left corner would be at 135,215. The green view has a sufficiently negative top value given its width that it is positioned off the top of the screen.

Positioning
var win = Ti.UI.createWindow({
	backgroundColor: '#fff'
});
var redview = Ti.UI.createView({
    top: 20,
	left: 20,
    width: 10,
    height: 10,
    backgroundColor: "red"
});
win.add(redview);
var yellowview = Ti.UI.createView({
    bottom: 100,
	right: 100,
    width: 10,
    height: 10,
    backgroundColor: "yellow"
});
win.add(yellowview);
var blueview = Ti.UI.createView({
	center: {x: 160, y: 240},
	width: 50,
	height: 50,
	backgroundColor: "blue"
});
win.add(blueview);
var greenview = Ti.UI.createView({
    top: -20,
    width: 10,
    height: 10,
    backgroundColor: "green"
});
win.add(greenview);
win.open();

Layout modes

Titanium Windows and Views can employ one of three layout modes by setting its layout property to one of the following values:

  • absolute - This is the default mode that we have discussed to this point. You specify point coordinates on a grid relative to the parent container's top/left or bottom/right corner.
  • vertical - This layout mode stacks child views vertically. The child's top property becomes an offset value. It describes the number of units from its previous sibling's bottom edge where the view will be positioned.
  • horizontal - This layout mode lines up child views horizontally. The child's left property, similar to vertical, becomes an offset. This time, it's the position from the previous sibling's right edge.

Here's an exmaple of these layouts in action:

Layout modes
var win = Ti.UI.createWindow({
	backgroundColor:'#fff'
});
// uses grid-drawing module from https://gist.github.com/1187384
// to draw grid lines every 20 points
var grid = require('gridlines');
grid.drawgrid(20,win);
// draw a view that fills the window and set its layout property
var view = Ti.UI.createView({
	backgroundColor: 'transparent',
	top: 0,
	left: 0,
	width: '100%',
	height: '100%',
	layout: 'vertical'
});
// simple function for making colored boxes
function makeView(color) {
	return Ti.UI.createView({
	    top: 20,
		left: 20,
	    width: 20,
	    height: 20,
	    backgroundColor: color
	});
}
view.add(makeView('red'));
view.add(makeView('yellow'));
view.add(makeView('blue'));
view.add(makeView('green'));

win.add(view);
win.open();

Auto Size Behaviors

Titanium has supported "auto" behaviors for element dimensions. But that behavior is deprecated in Titanium 2.0. In the past, the "auto" behavior applied to height and width dimensions and was supposed to "size the view appropriately given the type of view and its contents." This vague descriptor has led to inconsistent behavior across platforms.

It will be replaced by two specified behaviors: SIZE and FILL. You specify these behaviors using the Ti.UI.SIZE and Ti.UI.FILL constants, which represent explicit "automatic" behaviors. The Ti.UI.SIZE behavior represents constraining a view's size to fit its contents. The Ti.UI.FILL behavior represents growing a view to fill its parent's dimensions. Note that the FILL behavior doesn't take into consideration other sibling elements in the parent. If the parent view has two children, one of which with static dimensions and the other set to FILL, the second view will fill its parent possibly hiding the sibling from view (depending on stacking order).

UI components exhibit default SIZE or FILL behaviors, as listed in this table:

SIZE views

FILL views

Mixed behavior

Button

Window

Toolbar: FILL for width, SIZE for height

Label

View

TableViewRow: FILL for width, SIZE for height

ImageView

TabGroup

Slider: FILL for width, SIZE for height

ProgressBar

TableView

 

Switch

WebView

 

TextArea

ScrollView

 

TextField

ScrollableView

 

Picker

 

 

SearchBar

 

 

ButtonBar

 

 

TableViewSection

 

 

ScrollView Content Sizes

In the case of ScrollView, contentWidth and contentHeight may also be set to "auto" or Ti.UI.SIZE, and in those cases, this is the expected behavior:

  • When all children views have FILL behavior, the content area of the scroll view will be clipped to the physical size of the scroll view
  • Otherwise, the content area will grow according to the bottom offset of the bottom-most View and the right offset of right-most View. In some cases the bottom-most and right-most View may be the same View.

zIndex & default stacking

You can position elements atop one another. By default, as you add views to a parent container, they will overlay any views you previously added (assuming their boundaries overlap). You can control the stacking order by either changing the order you add elements to the container (not always convenient) or by setting the zIndex property. As with HTML elements, zIndex accepts an integer value of zero or greater. The higher the zIndex value, the closer to the top of the stack a view will become.

Hands-on Practice

Goal

In this activity, you will test the position behavior of elements by implementing some of the code examples in this chapter.

Resources

To perform the steps in this activity, you will need the gridlines module from https://gist.github.com/1187384.

Steps

  1. Create a new Titanium Mobile project.
  2. Create a gridlines.js file containing the code shown at the Gist linked to above.
  3. In app.js, remove all of the existing code. Declare a window, require the grid line module, and draw gridlines every 20 points, following the example code as shown in the Gist.
  4. Implement the positioning code shown in the example labeled "Positioning" above. This will draw red, blue, yellow, and green boxes at various positions on the screen.
  5. Build and run the project. Count the gridlines to confirm that elements were placed as described in this chapter.
  6. Adjust the positioning properties of the various boxes to test positioning rules.
  7. Try setting the window's layout property to vertical or horizontal to see the effect on the lines and boxes. Adjust the code so that the boxes are visible.

References and Further Reading

Summary

In this section, you learned how to lay out your user interface components using the various positioning properties. You examined the coordinates system used by Titanium, the view hierarchy, and the layering and positioning rules that Titanium follows when rendering your UI. Next we'll discuss how you can handle user interaction via events.

Related Links