A viewport displays the subset of its content that falls within a clipping rectangle whose width/height match the viewport's. By resetting the clipping rectangle's origin, one can survey all of the viewport's content, even though the content's width and height are much larger than the viewport's. This is called "scrolling".
This feature adds viewport functionality to the Group and DataGroup classes. It also extends the layout classes to enable specifying viewport geometry.
The Flash Player supports scrolling with the Rectangle valued DisplayObject scrollRect property. The DisplayObject's display list is clipped to the scrollRect and displayed at the DisplayObject's origin. Using scrollRect, one can realize the usual scrolling idiom by making the scrollRect's width and height the same as the DisplayObject's, and by binding the scrollRect's x,y coordinates to the values of a horizontal and vertical scrollbar. The scrollbars' maximum values must be configured to match the bounds of the Display Object's children, and the scrollbars' thumb sizes should reflect the relative size of the Display Object and the size of the bounds of its children.
To allow a Group or a DataGroup to function as a viewport, we've added a pair of properties to GroupBase (their common superclass) that define the overall width and height of the viewport's content, contentWidth and contentHeight. We've also added a pair of properties that define the origin of the viewport's clipping rectangle, verticalScrollPosition and horizontalScrollPosition, and a boolean clipContent property that enables scrolling. If clipContent is true, then the Group's scrollRect will be set.
Here's an example of the viewport properties:
<Group clipContent="true" width="142" height="75"
horizontalScrollPosition="46" verticalScrollPosition="100">
<Image source="curly.jpg" width="600" height="376">
</Group>
Scrolling is enabled when the viewport's content width or height is larger than its width or height and clipContent is true. Group and DataGroup will not include support for automatically displaying scrollbars when scrolling is enabled (as Halo did). A separate skinnable component called Scroller will provide the conventional arrangement of scrollbars and a viewport.
The "Group as Viewport" feature corresponds to a set of changes applied to GroupBase, LayoutBase, and the LayoutBase subclasses BasicLayout, VerticalLayout, and HorizontalLayout. The viewport API is defined by a new interface called IViewport. Explicit support for viewports has been added to the ScrollBar classes.
GroupBase gets five new properties, all of which are defined by the new IViewport inteface:
contentWidth:Number
contentHeight:Number
verticalScrollPosition:Number = 0
horizontalScrollPosition:Number = 0
clipContent:Boolean = false
All of the new properties are bindable except the clipContent flag. All but the first two are delegated to the group's layout. LayoutBase provides the implementation of the verticalScrollPosition, horizontalScrollPosition, and clipContent properties.
The layout classes have been updated to initialize their (GroupBase) layoutTarget's contentWidth,Height at updateDisplayList() time. Additional properties have been added to VerticalLayout and HorizontalLayout to enable one to specify the relative size of the viewport in terms of the number of visible rows or columns. The names and semantics of the new layout properties are largely compatible with Halo:
VerticalLayout
gap:Number = 6
rowCount:int = -1
variableRowHeight:Boolean = true
rowHeight:Number
HorizontalLayout
gap:Number = 6
columnCount:int = -1
variableColumnWidth:Boolean = true
columnWidth:Number
The viewport feature does not change the default behavior of the Gumbo Group or the layout classes. In other words, tests and applications written before the feature was introduced should continue to work, unchanged.
The definition and implementation of the Group layout property have been revised to allow one to specify layout object values. Previously, one could only specify the layout property's class:
<Group layout="flex.layout.VerticalLayout">
your column of items here ...
</Group>
The type of the Group layout property was Class and the MXML compiler handled this special case by mapping the property value string to Class object. The actual ILayout instance was private to Group and was initialized by calling the layout class constructor with no arguments.
This approach complicates configuring layout classes, since one would have to create subclasses whose null-constructors configured the layout appropriately. To remedy this, the type of the Group layout property has been changed to LayoutBase and layouts must be specified with a subelement. Like this:
<Group>
<layout>
<VerticalLayout gap="4" rowCount="7" rowHeight="20" />
</layout>
your column of items here ...
</Group>
To simplify creating rows and columns of items, and to provide something analagous to the Halo HBox and VBox classes, wrapper classes called HGroup and VGroup have been defined. These two classes are simply Groups with a predefined layout and layout geometry properties that delegate to the predefined layout. This enables one to write:
<VGroup gap="4" rowCount="7" rowHeight="20">
your column of items here ...
</VGroup>
The viewport API. Implemented by GroupBase and TextView, supported by the Scroller component.
public interface IViewport extends IEventDispatcher
{
function get width():Number;
function get height():Number;
/**
* The positive extent of the content, relative to the 0,0
* origin, along the X axis.
*
* The value of this property is defined relative to the container's
* coordinate system.
*
* Implementations of this property must be Bindable and
* they must generate events of type "propertyChange".
*/
function get contentWidth():Number;
/**
* The positive extent of the content, relative to the 0,0
* origin, along the Y axis.
*
* The value of this property is defined relative to the container's
* coordinate system.
*
* Implementations of this property must be Bindable and
* they must generate events of type "propertyChange".
*/
function get contentHeight():Number;
/**
* The X coordinate of the origin of the region the target is
* scrolled to.
*
* If clipContent is true, setting this property typically causes
* scrollRect to be set to:
*
* new Rectangle(horizontalScrollPosition, verticalScrollPosition, width, height)
*
* Implementations of this property must be Bindable and
* they must generate events of type "propertyChange".
*
* @default 0
*/
function get horizontalScrollPosition():Number;
function set horizontalScrollPosition(value:Number):void;
/**
* The Y coordinate of the origin of the region this Group is
* scrolled to.
*
* If clipContent is true, setting this property typically causes
* scrollRect to be set to:
*
* new Rectangle(horizontalScrollPosition, verticalScrollPosition, width, height)
*
* Implementations of this property must be Bindable and
* they must generate events of type "propertyChange".
*
* @default 0
*/
function get verticalScrollPosition():Number;
function set verticalScrollPosition(value:Number):void;
/**
* Returns the amount one would have to add to the viewport's current
* horizontalScrollPosition to scroll by the requested "scrolling" unit.
*
* The value of unit must be one of the following spark.core.ScrollUnit
* constants: LEFT, RIGHT, PAGE_LEFT, PAGE_RIGHT, HOME, END.
*
* To scroll by a single column use LEFT or RIGHT and to scroll to the
* first or last column, use HOME or END.
*/
function getHorizontalScrollPositionDelta(unit:ScrollUnit):Number
/**
* Returns the amount one would have to add to the viewport's current
* verticalScrollPosition to scroll by the requested "scrolling" unit.
*
* The value of unit must be one of the following spark.core.ScrollUnit
* constants: UP, DOWN, PAGE_UP, PAGE_DOWN, HOME, END.
*
* To scroll by a single row use UP or DOWN and to scroll to the
* first or last row, use HOME or END.
*/
function getVerticalScrollPositionDelta(unit:ScrollUnit):Number
/**
* If true then clip the viewport's contents by setting its scrollRect
* to a rectangle with origin at horizontalScrollPosition,
* verticalScrollPosition and width and height equal to the
* viewport's width and height.
*
* If false, the scrollRect is set to null.
*
* @default false
*/
function get clipContent():Boolean;
function set clipContent(value:Boolean):void;
}
Enumerated type for the IViewport getVerticalScrollPositionDelta() and getHorizontalScrollPositionDelta() methods. All of these constants have the same values as their flash.ui.Keyboard counterparts, except PAGE_LEFT and PAGE_RIGHT, for which no keyboard key equivalents exist.
public final class ScrollUnit
{
/**
* Scroll to the origin of the document, typically by setting
* the viewport's vertical,horizontalScrollPosition to 0.
*/
public static const HOME:ScrollUnit = new ScrollUnit(Keyboard.HOME, "home");
/**
* Scroll to the end of the document, typically by setting
* the viewport's vertical,horizontalScrollPosition to
* the content size less the actual size.
*/
public static const END:ScrollUnit = new ScrollUnit(Keyboard.END, "end");
/**
* Scroll one line or "step" upwards, typically by making the
* line that's just above (or overlaps) the top of the viewport
* visible.
*/
public static const UP:ScrollUnit = new ScrollUnit(Keyboard.UP, "up");
/**
* Scroll one line or "step" downwards, typically by making the
* line that's just below (or overlaps) the bottom of the viewport
* visible.
*/
public static const DOWN:ScrollUnit = new ScrollUnit(Keyboard.DOWN, "down");
/**
* Scroll one line or "step" to the left, typically by making the
* column that's just to the left (or overlaps) the left edge
* of the viewport visible.
*/
public static const LEFT:ScrollUnit = new ScrollUnit(Keyboard.LEFT, "left");
/**
* Scroll one line or "step" to the right, typically by making the
* line that's just to the right (or overlaps) the right edge
* of the viewport visible.
*/
public static const RIGHT:ScrollUnit = new ScrollUnit(Keyboard.RIGHT, "right");
/**
* Scroll one page upwards, typically by top justifying
* the line that's just below (or overlaps) the bottom of
* the viewport.
*/
public static const PAGE_UP:ScrollUnit = new ScrollUnit(Keyboard.PAGE_UP, "pageUp");
/**
* Scroll one page downwards, typically by bottom justifying
* the line that's just above (or overlaps) the top of
* the viewport.
*/
public static const PAGE_DOWN:ScrollUnit = new ScrollUnit(Keyboard.PAGE_DOWN, "pageDown");
/**
* Scroll one page to the left, typically by right justifying
* the line that's just to the left of (or overlaps)
* the left edge of the viewport.
*
* The value of this constant, 0x2397, is the same as the Unicode
* "previous page" character.
*/
public static const PAGE_LEFT:ScrollUnit = new ScrollUnit(0x2397, "pageLeft");
/**
* Scroll one page to the right, typically by left justifying
* the line that's just to the right of (or overlaps)
* the right edge of the viewport.
*
* The value of this constant, 0x2398, is the same as the Unicode
* "next page" character.
*/
public static const PAGE_RIGHT:ScrollUnit = new ScrollUnit(0x2398, "pageRight");
public function get value():uint // returns the first ctor argument
public function toString():String // returns the second ctor argument
}
The base class for layouts and the type of the GroupBase layout property. The methods and properties that just implement the IViewport interface are not shown here.
public class LayoutBase extends OnDemandEventDispatcher
{
/**
* The GroupBase whose layout we're responsible for.
* The target is responsible for delegating the updateDisplayList()
* and measure() methods to its layout.
*
* @default null
*/
public function get target():GroupBase
public function set target(value:GroupBase):void
/**
* Called when the verticalScrollPosition or horizontalScrollPosition
* properties change. Resets the target's scrollRect property by calling
* updateScrollRect(). Subclasses can override this method to compute
* other values that are based on the current scrollPosition or scrollRect.
*/
protected function scrollPositionChanged():void
/**
* If clipContent is true, sets the origin of the scrollRect to
* verticalScrollPosition,horizontalScrollPosition and its width
* width,height to w,h (the target's unscaled width,height).
* If clipContent is false, sets the scrollRect to null.
*
* @param w The target's unscaled width.
* @param h The target's unscaled height.
*/
public function updateScrollRect(w:Number, h:Number):void
/**
* ... see UIComponent
*/
public function measure():void
public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
}
This section just lists the methods and properties added to the VerticalLayout class for the sake of the viewport feature.
public class VerticalLayout extends LayoutBase
{
/**
* Vertical space between rows.
*
* @default 6
*/
public function get gap():int
public function set gap(value:int):void
/**
* Returns the current number of visible rows.
*
* @default -1
*/
public function get rowCount():int
/**
* Sets the rowCount property and dispatches a PropertyChangeEvent.
*
* This method is intended to be used by subclass updateDisplayList()
* methods to sync the rowCount property with the actual number
* of visible rows.
*
* @param value The number of visible rows.
*/
protected function setRowCount(value:int):void
/**
* Specifies the number of items to display. If requestedRowCount
* is -1, then all of the items are displayed.
*
* This property implies the layout's measuredHeight.
*
* If the height of the target has been explicitly set,
* then this property has no effect.
*
* @default -1
*/
public function get requestedRowCount():int
public function set requestedRowCount(value:int):void
/**
* Specifies the height of the rows if variableRowHeight is false.
*
* @default NaN
*/
public function get rowHeight():Number
public function set rowHeight(value:Number):void
/**
* If false, i.e. "fixed row height" is specified, the height of
* each item is set to the value of rowHeight.
* If the rowHeight property wasn't explicitly set,
* then it's initialized with the measuredHeight of
* the first item.
*
* The items' includeInLayout, measuredHeight, minHeight,
* and percentHeight properties are ignored when
* variableRowHeights is false.
*
* @default true
*/
public function get variableRowHeight():Boolean
public function set variableRowHeight(value:Boolean):void
/**
* The index of the first row that's part of the layout and within
* the layout target's scrollRect, or -1 if nothing has been displayed yet.
*
* Note that the row may only be partially in view.
*/
public function get firstIndexInView():int
/**
* The index of the last row that's part of the layout and within
* the layout target's scrollRect, or -1 if nothing has been displayed yet.
*
* Note that the row may only be partially in view.
*/
public function get lastIndexInView():int
/**
* Sets the firstIndexInView and lastIndexInView
* properties and dispatches a "indexInViewChanged"
* event.
*
* This method is intended to be used by subclasses that
* override updateDisplayList() to sync the first and
* last indexInView properties with the current display.
*/
protected function setIndexInView(firstIndex:int, lastIndex:int):void
}
This section just lists the methods and properties added to the HorizontalLayout class for the sake of the viewport feature.
public class HorizontalLayout extends LayoutBase
{
/**
* Horizontal space between columns.
*
* @default 6
*/
public function get gap():int
public function set gap(value:int):void
/**
* Returns the current number of visible items.
*
* @default -1
*/
public function get columnCount():int
/**
* Sets the columnCount property and dispatches a PropertyChangeEvent.
*
* This method is intended to be used by subclass updateDisplayList()
* methods to sync the columnCount property with the actual number
* of visible columns.
*
* @param value The number of visible columns.
*/
protected function setColumnCount(value:int):void
/**
* Specifies the number of items to display.
*
* If requestedColumnCount is -1, then all of them items are displayed.
*
* This property implies the layout's measuredWidth.
*
* If the width of the target has been explicitly set,
* then this property has no effect.
*
* @default -1
*/
public function get requestedColumnCount():int
public function set requestedColumnCount(value:int):void
/**
* The column width requested by explicitly setting columnWidth.
*/
protected var explicitColumnWidth:Number;
/**
* Specifies the width of the columns if variableColumnWidth is false.
*
* @default NaN
*/
public function get columnWidth():Number
public function set columnWidth(value:Number):void
/**
* If false, i.e. "fixed column width" is specified, the width of
* each item is set to the value of columnWidth.
*
* If the columnWidth property wasn't explicitly set,
* then it's initialized with the measuredWidth of
* the first item.
*
* The items' includeInLayout,
* measuredWidth, minWidth,
* and percentWidth properties are ignored when
* variableColumnWidth is false.
*
* @default true
*/
public function get variableColumnWidth():Boolean
public function set variableColumnWidth(value:Boolean):void
/**
* The index of the first column that's part of the layout and within
* the layout target's scrollRect, or -1 if nothing has been displayed yet.
*
* Note that the column may only be partially in view.
*/
public function get firstIndexInView():int
/**
* The index of the last column that's part of the layout and within
* the layout target's scrollRect, or -1 if nothing has been displayed yet.
*
* Note that the column may only be partially in view.
*/
public function get lastIndexInView():int
/**
* Sets the firstIndexInView and lastIndexInView
* properties and dispatches a "indexInViewChanged"
* event.
*
* This method is intended to be used by subclasses that
* override updateDisplayList() to sync the first and
* last indexInView properties with the current display.
*
* @param firstIndex The new value for firstIndexInView.
* @param lastIndex The new value for lastIndexInView.
*/
protected function setIndexInView(firstIndex:int, lastIndex:int):void
}
A Group with a VerticalLayout. All of the VerticalLayout properties
exposed by this class (and listed below) are simply delegated to the
layout property. The layout property should not be set or configured
directly.
public class VGroup extends Group
{
public function get gap():int
public function set gap(value:int):void
public function get rowCount():int
public function get requestedRowCount():int
public function set requestedRowCount(value:int):void
public function get rowHeight():Number
public function set rowHeight(value:Number):void
public function get variableRowHeight():Boolean
public function set variableRowHeight(value:Boolean):void
public function get firstIndexInView():int
public function get lastIndexInView():int
}
A Group with a HorizontalLayout.
All of the HorizontalLayout properties exposed by this class are simply
delegated to the layout property.
The layout property should not be set or configured directly.
public class HGroup extends Group
{
public function set gap(value:int):void
public function get columnCount():int
public function get requestedColumnCount():int
public function set requestedColumnCount(value:int):void
public function get columnWidth():Number
public function set columnWidth(value:Number):void
public function get variableColumnWidth():Boolean
public function set variableColumnWidth(value:Boolean):void
public function get firstIndexInView():int
public function get lastIndexInView():int
}
When fixed row heights (variableRowHeight = false) are requested for a VerticalLayout, or fixed column widths for a HorizontalLayout, it's usually preferable not to specify the fixed size. It's more reliable to allow the fixed size to be computed, to accomodate differences in fonts and
rendering on different platforms and potentially for different configuations of the application. The existing API supports this by using the size of the first item, if fixed size items are requested and an explicit size isn't provided. A more flexible approach would be to allow the developer to specify a "typical" item instead. Such an item might not appear in the layout at all, it would be designed to consume as much space as typical items require.
Methods that compute the scroll "delta" to expose a particular item index, or to top/bottom/center justify it would be useful. Scrolling applications often want to bring a particular item into view - if it isn't already - and doing so now requires significant work.
Something comparable to the Halo TileLayout needs to be provided.
A viewport's "step" and page size can be direction and content dependent. For example, the step size for a VerticalLayout is as big as the row just above the top of the scrollRect for a step up, and as big as the row just below the bottom of the scrollRect for a step down. Obviously, if the viewport contains variable height rows, the step size depends on the step direction and the the size of the row that comes next in that direction. The current ScrollPositionDelta methods also take partial visibility into account, which means that even with fixed sze items, the step size varies depending on the partial/total visibility of the next item. There's a significant mismatch between ScrollBar's simple stepSize property and a viewport's support for stepping up/down or left/right. ScrollBar's pageSize has similar problems, plus it's used to define the viewport's size for the sake of the thumb geometry. More about this here http://bugs.adobe.com/jira/browse/SDK-17288. ScrollBars and their myriad superclasses will need refinement.
Nothing applicable yet.
This feature has been implemented.
No.
None.
This feature is part of the basic Gumbo infrastructure. Many other features and components depend on it.
This feature is not intended to be compatible with existing applications or components.
None.
ScrollRect based scrolling performance is remarkably good.
Recently we've learned that even seemingly inconsequential operations on a DisplayObject's scrollRect, like getting its value or setting it to null, cause the FlashPlayer to allocate large data structures. The viewport API has been revised to ensure that by default, scrolling isn't enabled, i.e. the scrollRect is not used. To ensure that the scrollRect property isn't needlessly queried or set to null will require additional implementation work.
Efficient support for large layouts will require "virtualization".
There aren't any globalization issues.
No issues at this time.
See the B features section.
Yes
Wiki: Flex 4
Wiki: Spark DataGroup
Wiki: Spark Group
Wiki: Spark Scroller