Contents
Overview
Titanium's UI behavior has remained unspecified and fragmented across platforms, which makes cross-platform development difficult for users and designing tests to evaluate UI impractical. With proposed changes to the layout system, and more advanced testing frameworks, it has become necessary to explicitly define UI behavior in as many situations as possible.
This document is a codification of UI behavior (the "Composite" layout) for both Android and iOS platforms, including explicit specifications for unit types, order in which layout properties are valuated, "auto" behavior, and dealing with unset values.
The behavior for "Horizontal" and "Vertical" layouts will be specified in another document.
Backwards Compatibility and Deprecation
In Titanium Mobile 2.0, the value of any layout parameter on a View will
always maintain that value, making it "static". The properties
size
, and rect
, will now serve as the "dynamic" APIs that provide position
and size. We will also maintain backwards compatibility with the current
"immediate mode" layout scheme, but this behavior will be marked
as DEPRECATED. Moving forward, we will drop support for this method in favor of the
the startLayout
/ finishLayout
, and updateLayout
semantics.
Definitions
- 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.
Layout parameters
This is the list of Layout Parameters used by "Composite", "Horizontal", and "Vertical" layouts. Layout Parameters are discussed in more detail in the Layout specification, but these all correspond to existing layout settings.
- width : The width of the view
- left : Pins left side of the view to this x position in the parent, measured from the parent's left bound.
- right : Pins right side of the view to this x position in the parent, measured from the parent's right bound.
- height : The height of the view
- top : Pins top side of the view to this y position in the parent, measured from the parent's top bound.
- bottom : Pins bottom side of the view to this y position in the parent, measured from the parent's bottom bound.
- center : (x, y) : Pins the view's center to the specified point:
- center.x : The x-coordinate, measured from the parent's left bound
- center.y : The y-coordinate, measured from the parent's top bound
- layout : The layout type to use. The default value is for the "Composite"
layout. Other valid values are "vertical" and "horizontal".
This property is intended to be replaced by the new UI Layout API Spec
- 'vertical' : Pinning happens vertically relative to other views inside the parent, using remaining space
- 'horizontal' : Pinning happens horizontally relative to other views inside the parent, using remaining space
- zIndex : The stack order of the view inside its parent. Higher values are rendered towards the top.
When a child view's boundary exceeds that of the parent view,
it should be clipped to the size of the parent view.
Available units
A unit is a unit of measurement on the device screen or, for some types, a measurement relative to information from the parent view.
- Absolute measurements
- px : pixels
- mm : millimeters
- cm : centimeters
- in : inches
- dp/dip : Density-independent pixels
- Android : px = dip * (screen density) / 160
- iOS : px = dip * (screen density) / 163 (effectively; 1dip=1px on standard, 1dip=2px on retina)
- Mobile Web: px = dip * (screen density) / 96 (effectively; 1dip=1px 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.
- % : Percentage of the size of the parent.
tiapp.xml properties
We allow users to specify a default unit type to interpret untyped values. By default, this value is a special unit type only available to this property, 'system' (see Definitions, above).
- ti.ui.defaultunit : String, the default unit to interpret values without
a unit as.
- Generated in default tiapp.xml template (users can explicitly see default value)
- Valid values: px, mm, cm, in, dp, dip, system
As of SDK 8.0.0, dp
starts supported for default unit on Windows. Since Windows default unit
has been px
in previous versions this means this may cause breaking change. You might
want to update your tiapp.xml
to use px
for default unit explicitly like <property name="ti.ui.defaultunit" type="string">px</property>
to keep consistency between previous SDK versions.
Precedence of layoutParams
Certain parameters influence the calculation of others when they are unset (see UNDEFINED behavior ). The purpose of establishing an order of precedence is only to determine when certain settings override others when there is an obvious conflict, or determine which properties are used for computing implicit values.
In order of precedence, from 'evaluated first' to 'evaluated last':
- width : overrides implicit width calculations
- left : overrides horizontal positioning determined by center.x, right
- center.x : overrides horizontal positioning determined by other pins
- right
- height : overrides implicit height calculations
- top : overrides vertical positioning determined by other pins
- center.y : overrides vertical positioning determined by other pins
- bottom
When a conflict occurs between the different layout params, the order of precedence will determine which params will be ignored. If a view has a width of 200, a left value of 100, and a right value of 10, then the view would be 200 wide, and 100 from the left of its parent view. The right value is ignored since it conflicts the width and left values, and has lower precedence.
Auto size behaviors
"auto" (sometimes refered to as "psychic") is a measurement specification for width/height which "sizes the view appropriately given the type of view and its contents." This is a vague descriptor, and has been broken up into two general specified behaviors: SIZE and FILL.
"auto" will be deprecated in 2.0.0, to be replaced with Ti.UI.SIZE and Ti.UI.FILL constants which represent explicit "auto" behavior. As a result, this section codifies existing "auto" behavior into these two subtypes, and declares which views use which type when their width or height is set to "auto" under the present system.
ScrollView content size
In the case of ScrollView, contentWidth and contentHeight may also be set to "auto", 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.
SIZE behavior
This behavior represents constraining a view size to fit its contents.
- height only : Constrained by view's width, or if width is incalcuable (<2 horizontal pinning properties) and unset, then constrained by parent's width.
- width only : Constrained by view's height, or if height is incalculable (<2 vertical pinning properties) and unset, then constrained by parent's height.
- height + width : width constrained by parent width, height constraint by content height, i.e. grows to fill width first, and then sizes height to display content.
ScrollView SIZE
Scrollview contentWidth
or contentHeight
can be set to Ti.UI.SIZE
. This value behaves as SIZE
is described above, where the scrollview itself first sizes to contents,
and then if those contents extend beyond the bounds of the scrollview,
the content view sizes to fit the contents appropriately.
FILL behavior
This behavior represents growing a view size to fill its parent.
- height only : Fills all available vertical space inside the parent
- width only : Fills all available horizontal space inside the parent
- height + width : Fills all available space inside the parent\
- NOTE: The fill behavior does not take any other views into account except for its parent. i.e. If the parent view has 2 children, the first with a static width/height and the second with fill behavior for both, the second view will still fill its parent.
- If the parent does not have a height/width constraint (i.e. The parent has size behavior for width/height while the child has fill behavior), then the view will recursively go through the parents to find a width/height constraint and fill to that constraint.
ScrollView FILL
Scrollview contentWidth
or contentHeight
can be set to Ti.UI.FILL
. Regardless of contents, this behaves as described above, meaning that
the content view bounds fill the scrollview. This has the side-effect that
the scrollview does not scroll, so using this value is considered undesirable.
View auto size classification
Views are logically grouped into either SIZE or FILL for their auto behavior. Windows FILL the screen by default.
- SIZE Views
- Button
- Label
- ImageView
- ProgressBar
- Switch
- TextArea
- TextField
- Picker
- SearchBar
- height only, FILL width
- ButtonBar
- TableViewSection
- FILL Views
- View
- TabGroup
- VideoView
- Toolbar
- width only, SIZE height
- TableView
- TableViewRow
- width only, SIZE height
- WebView
- ScrollView
- ScrollableView
- Slider
- width only, SIZE height
UNDEFINED behavior
If layout parameter values are undefined, then this means that they need to be computed based on existing values if possible, and if not, then have some sensible default.
- width : implicit based on horizontal pins (left, center.x, right)
- If two (or more) horizontal pins are available, computed from these values
- Otherwise, follows view's "auto" behavior
- On "auto" deprecation, follows SIZE behavior
- If width is dependent on the parent (e.g. percentage), and the parent is set to SIZE, width is undefined
- left : implicit based on other horizontal pins (center.x, right)
- If no pinning properties are set : The view is centered horizontally in it's parent
- Otherwise, no left side pinning
- center.x : No center pinning (horizontal)
- right : No right side pinning
- height : implicit based on vertical pins (top, center.y, bottom)
- If two (or more) vertical pins are available, computed from these values
- Otherwise, follows view's "auto" behavior
- On "auto" deprecation, follows SIZE behavior
- If height is dependent on the parent (e.g. percentage), and the parent is set to SIZE, height is undefined
- top : implicit based on other vertical pins (center.y, bottom)
- If no pinning properties are set : The view is centered vertically in it's parent
- Otherwise, no top side pinning
- center.y : No center pinning (vertical)
- bottom : No bottom side pinning
- zIndex: implementation treats as 0, but will still be undefined in the View's layout params. Views are stacked in order of being added to the parent based on their index.
UNDEFINED and scrollview contents
For clarity, scrollview contentWidth
and contentHeight
behave as if they were set to "auto" when undefined. This is
consistent with the behavior described above.
Code Examples
Each of these examples contrast the way something is done today ("old") with the way it will be done with the new dynamic size / rect properties, and batch updating semantics ("new").
// [old] changes the top and left of the view directly, re-layout twice view.top = 50; view.left = 50; // [new] change top and left, but only request a single layout view.startLayout(); view.top = 50; view.left = 50; view.finishLayout(); // [new] equivalent to above, but using batch update for convenience view.updateLayout({ top: 50, left: 50 }); |
view.width = 100; // [old] get the views's native width, but user supplied width is unavailable // view.width may not necessarily be 100 after being laid out Ti.API.debug( "view width = " + view.width); // [new] get the view's native width, and the user supplied width // view.rect, view.size, are the new dynamic/native APIs // view.X will always have the user-supplied layout params Ti.API.debug( "button width = " + view.rect.width + ", my width = " + view.width); |
Proposed API
Dimension (duck type)
- Properties
- x : Number
- y : Number
- width : Number
- height : Number
Ti.UI.View
- Properties
- size : Dimension, read-only, returns the bounds of the view in system units. x and y properties are always 0
- rect : Dimension, read-only, returns the frame of the view (position relative to parent bounds) in system units.
- left : String or Number, the left bound of the view specified by the user
- right : String or Number, the right bound of the view specified by the user
- top : String or Number, the top bound of the view specified by the user
- bottom : String or Number, the bottom bound of the view specified by the user
- center : Object, the center point of the view specified by the user
- x : String or Number, the x coordinate of the center point
- y : String or Number, the y coordinate of the center point
- width : String or Number, the width of the view specified by the user
- height : String or Number, the height of the view specified by the user
- Functions
- startLayout() : void, starts a batch-update of the View's layout params
- finishLayout() : void, finishes a batch-update of the View's layout params, and schedules a layout pass of the view tree
- updateLayout(Object params) : void, performs a batch-update of all supplied layout params, and schedules a layout pass after they have been updated
Ti.UI
- Constants
- Ti.UI.SIZE : Sets a view's height/width to be that of "auto" SIZE
- Ti.UI.FILL : Sets a view's height/width to be that of "auto" FILL
- Ti.UI.UNIT_PX : px unit
- Ti.UI.UNIT_MM : mm unit
- Ti.UI.UNIT_CM : cm unit
- Ti.UI.UNIT_IN : in unit
- Ti.UI.UNIT_DIP : dip unit
- Functions
- convertUnits(String convertFromValue, String convertToUnits) : Number,
the conversion of one unit type to another using the metrics of the main
Display
- convertFromValue : A measurement and optional unit to convert from, i.e. 160, "120dip"
- convertToUnits : The unit to convert to from one of the UNIT_* constants above
- NOTE we will need to accomodate for multiple displays in future revisions of this API
- NOTE because parent/self information is required for converting % values, there is no unit constant for '%' or conversions allowed to/from this value. If a percent value is passed in, this method returns 0.
- convertUnits(String convertFromValue, String convertToUnits) : Number,
the conversion of one unit type to another using the metrics of the main
Display
Events
- 'postlayout' : This event will be fired after a layout pass has occurred.
5 コメント
Kevin Whinnery
Why would layout properties become read-only in 1.8? I am assuming there will be a dynamic means of setting all of these properties at runtime. Will that be restricted to the "setXXX" format? If so, is there a technical reason for this to be the case?
Marshall Culpepper
Only the native system values would be read only. Any value on "layoutParams" would be both read and write.
Kevin Whinnery
I figured I was confused on this point, thanks.
Arthur Evans
I think
Size
would be clearer thanDimension
for the duck type, since it actually has two dimensions (width and height).Arthur Evans
BTW, will we still have the current issue where the app has no way of knowing when the size and position of a control have been determined? As described in:
https://jira.appcelerator.org/browse/TIMOB-7266
I.e., if finishLayout and updateLayout are asynchronous, we should fire an event when the controls' size and position is available. Current workaround is to guess how long it's going to take and set a timeout, which seems icky.