Menu

Spark Scroller

SourceForge Editorial Staff

Spark Scroller - Functional and Design Specification


Summary and Background


The skinnable Scroller component displays a pair of scrollbars and a viewport. A viewport is a UIComponent that implements IViewport, like Group or DataGroup.

The scrollbars control the viewport's vertical and horizontalScrollPosition and they reflect the viewport's actual size and content size. Scrollbars are displayed according to the Scroller's vertical and horizontalScrollPolicy. By default the policy is "auto", which means that scrollbars are displayed when the viewport's content size is larger than its actual size.

This feature adds classes spark.components.Scroller and spark.skins.spark.ScrollerSkin, and the ScrollBar viewport property.

Detailed Description


In Halo, scrollbar support was intrinsic to all container classes. The 14 properties devoted to scrolling, along with the related internal state and logic, added significantly to the size and complexity of Halo containers. Most of the containers in typical applications don't require scrollbars, so the burden was largely unnecessary. For Gumbo we've factored the support for displaying a viewport and scrollbars into a separate skinnable component called Scroller. Scroller's skin provides scrollbars and manages layout according to the vertical,horizontalScrollPolicy Scroller properties. These properties have the same meaning as in Halo (and see the API Description section below).

The connections between the Scroller's viewport and its scrollbars are created by setting the scrollbar's viewport property. The VScrollBar and HScrollBar classes bind to the viewport's scroll position and actual and content sizes. They also use the viewport's vertical,horizotnalScrollPositionDelta methods to compute the offsets for page and step scrolling.

Usage: Scroller Basics, Adding a Group to a Scroller

Here's a typical Scroller example. We've defined a 300×200 (width x height) Group that contains a rectangle graphic element that's 500×400. We've set clipAndEnableScrolling=true so that the Group clips its contents, however there's no way to interactively scroll:

<s:Group width="300" height="200" clipAndEnableScrolling="true">
    <s:Rect x="0" y="0" width="500" height="400">
        <s:fill>
            <mx:LinearGradient rotation="45">
                <mx:entries>
                    <mx:GradientEntry color="red" />
                    <mx:GradientEntry color="yellow" />
                </mx:entries>
            </mx:LinearGradient>
        </s:fill>
        <s:stroke>
            <mx:SolidColorStroke color="black"/>
        </s:stroke>                
    </s:Rect>
</s:Group>

To make the Group scrollable we put it inside a Scroller. We've also made two other significant changes:

  • The Group's width and height properties were moved to the Scroller because we want the Scroller to occupy the same space the Group did, relative to its parent. Note also: it's rarely useful to specify the size of a Scroller's viewport, since the Scroller's layout adjusts the size of the viewport based on which scrollbars need to be visible.

  • The Group's clipAndEnabledScrolling=true attribute setting was removed. The Scroller automatically sets this property on its viewport, so there's no need to do so explicitly.

    <s:scroller>
    <s:group>
    <s:rect x="0" y="0" width="500" height="400">
    ...
    </s:rect>
    </s:group>
    </s:scroller>

[[ align mode=u'center' ]]

[[ /align ]]

Scroller Layout, Well Behaved Viewports

The ScrollerLayout class makes scrollPolicy=auto scrollbars visible if the viewport's content size is larger than its actual size. For example if the viewport's contentWidth is larger than the width parameter passed to updateDisplayList(), and verticalScrollPolicy=auto, then the vertical scrollbar will be shown. To show a scrollbar the ScrollerLayout reduces the size of the viewport to provide enough room for the scrollbar's preferred size.

ScrollerLayout makes some assumptions about the way the viewport will be respond to being resized to make room for the scrollbars. A viewport that's sufficiently perverse about violating these assumptions can cause ScrollerLayout to loop.

  • If the viewport's width is reduced, then its contentHeight should grow or stay the same.

  • If the viewport's height is reduced, then its contentWidth should grow or stay the same.

  • If both the viewport's width and height are reduced, then either the contentWidth or the contentHeight should grow or stay the same.

Scroller Skin, minViewportInset

Scrollers depend on a private custom layout called ScrollerLayout to arrange their viewport and scrollbars. The relationship between ScrollerLayout and its elements is complicated and (currently) it is not particularly configurable. Currently only the scrollbar skin parts can be replaced.

The minViewportInset Scroller property exists to enable adding a Scroller border that overlaps the right and bottom edges of the vertical and horizontal scrollbar respectively. If specifies the minimum space between the viewport and the edges of the Scroller. This minimum (typically a small value) is usually exceeded by the scrollbars, when they're visible, and is always added on the top and left where scrollbars are not shown. If neither of the scroll bars is visible, then the viewport is inset by minViewportInset on all four sides.

Here's an example from the List skin that uses minViewportInset=1 to expose a one pixel border defined with a Rect object. This skin assumes that the scrollbars will also have a one pixel border which will line up with the one drawn by the Rect.

<s:Rect left="0" right="0" top="0" bottom="0" id="border">
    <s:stroke>
    <s:SolidColorStroke id="borderStroke" weight="1"/>
    </s:stroke>
</s:Rect>

<s:Scroller left="0" top="0" right="0" bottom="0" id="scroller" minViewportInset="1" focusEnabled="false">
    <s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
    <s:layout>
        <s:VerticalLayout gap="0" horizontalAlign="contentJustify" requestedMinRowCount="5" />
    </s:layout>
    </s:DataGroup>
</s:Scroller>

Scroller Layout, measuredSizeIncludesScrollBars

As noted earlier, the ScrollerLayout class makes scrollPolicy=auto scrollbars visible if the viewport's content size is larger than its actual size.

A Scroller's measured size is the ideal amount of space for the viewport and scrollbars. The measured size is essentially a request that's balanced against the parent's layout constraints and the available space. The space allocation that's granted is passed to the layout's updateDisplayList() method.

By default a Scroller's measured size includes viewport's preferred size and the space required by the visible scrollbars. If the Scroller's measured width or height is unconstrained, then the addition of a scrollbar causes its measured size to grow. Sometimes applications do not want the addition or removal of a scrollbar to causes the viewport's content to reflow, or the layout the Scroller is embedded in to be disturbed. Setting measuredSizeIncludesScrollBars=false forces ScrollerLayout to base the measured size of the Scroller excuslively on the viewport.

Components like TextArea, which "reflow" their contents to fit the available width or height may use this property to stabilize their measured size. By default a TextArea's is defined by its widthInChars and heightInChars properties and in many applications it's preferable for the measured size to remain constant, event when scroll bars are displayed by the TextArea skin's Scroller.

In components where the content does not reflow, like a typical List's items, the default behavior is preferable because it makes it less likely that the component's content will not obscured by a scroll bar.

Mouse Wheel and Keyboard Event Handling

The Scroller component handles mouse wheel events a few keyboard events: the arrow keys, page up, page down, home, and end. Mouse wheel events scroll the viewport, by changing its scroll position by event.delta. Mouse wheel events always cause a vertical scroll, unless only a horizontal scrollbar is visible. The scroll position delta is computed by calling viewport.getVerticalScrollPositionDelta() or getHorizontalScrollPositionDelta(). Keyboard events are similar.

If the Scroller handles a mouse wheel or keyboard event then the event is "cancelled" with event.preventDefault(), so that other bubble phase event handlers don't redundantly handle it.

HScrollBar and VScrollBar add listeners to the viewport that handle the same mouse and keyboard events in the same way, so usually the Scroller's event handlers receive events that have already been cancelled. The Scroller's event handlers usually only come into play when the the scroll policies are "off".

Scrollers in Spark Component Skins

Halo top level containers like Panel and Application included support for scrolling, the corresponding Spark containers do not. One can make the contents of a Spark container scrollable by adding a Scroller, as in the examples from the previous section. One can make the contents of a skinnable Spark container, like Panel, scrollable by wrapping the contentGroup with a Scroller.

The current Panel skin defines its contentGroup like this:

<s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0"/>

To add a Scroller to the skin, we move the size properties to the new Scroller, like this:

<s:Scroller width="100%" height="100%" minWidth="0" minHeight="0">
    <s:Group id="contentGroup"/>

Note also: The parent of the Scroller in this case is a Group with a VerticalLayout. That means that the width/height constraints are not specified relative to the Panel skin itself, but to the vertical group (which does not support BasicLayout constraints left,right,top,bottom).

API Description


Class spark.components.Scroller


The Scroller component displays a single scrollable component, called a viewport, and a horizontal and vertical scrollbars. The viewport must implement the IViewport interface. The scrollbars control the viewport's horizontalScrollPosition and verticalScrollPosition properties. Scrollbars make it possible to view the area defined by the viewport's contentWidth and contentHeight properties.

The scrollbars are displayed according to the vertical and horizontal scrollbar policy, which can be auto, on, or off. The auto policy means that the scrollbar will be visible and included in the layout when the viewport's content is larger than the viewport itself.

/**
 *  Indicates under what conditions the horizontal scroll bar is displayed.
 *  
 *  ScrollPolicy.ON ("on") - the scroll bar is always displayed.
 * 
 *  ScrollPolicy.AUTO ("auto") - the scroll bar is displayed when the 
 *      viewport's contentWidth is larger than its width.
 * 
 *  ScrollPolicy.OFF ("off") - the scroll bar is never displayed. 
 *      The viewport can still be scrolled programmatically, by setting its 
 *      horizontalScrollPosition property.
 *  
 *  The scroll policy affects the measured size of the Scroller component.
 *  
 *  The default value is ScrollPolicy.AUTO.
 */
<a href="Style%28name%3D%26quot%3BhorizontalScrollPolicy%26quot%3B%2C%20type%3D%26quot%3BString%26quot%3B%2C%20inherit%3D%26quot%3Bno%26quot%3B%2C%20enumeration%3D%26quot%3Boff%2Con%2Cauto%26quot%3B%29">Style(name="horizontalScrollPolicy", type="String", inherit="no", enumeration="off,on,auto")</a>

/**
 *  Indicates under what conditions the vertical scroll bar is displayed.
 *  
 *  ScrollPolicy.ON ("on") - the scroll bar is always displayed.
 *  
 *  ScrollPolicy.AUTO ("auto") - the scroll bar is displayed when the 
 *      viewport's contentHeight is larger than its height.
 *  
 *  ScrollPolicy.OFF ("off") - the scroll bar is never displayed. 
 *      The viewport can still be scrolled programmatically, by setting its
 *      verticalScrollPosition property.
 *  
 *  The scroll policy affects the measured size of the Scroller component.
 */
<a href="Style%28name%3D%26quot%3BverticalScrollPolicy%26quot%3B%2C%20type%3D%26quot%3BString%26quot%3B%2C%20inherit%3D%26quot%3Bno%26quot%3B%2C%20enumeration%3D%26quot%3Boff%2Con%2Cauto%26quot%3B%29">Style(name="verticalScrollPolicy", type="String", inherit="no", enumeration="off,on,auto")</a>

/**
 *  The Scroller component displays a single scrollable component, called
 *  a viewport, and horizontal and vertical scroll bars. The viewport must
 *  implement the IViewport interface. Its skin must be a derivative of
 *  the Group class.  The Spark Group, DataGroup, and RichEditableText
 *  components implement the IViewport interface and can be used as the
 *  children of the Scroller control, as the following example shows:
 *  
 *    <s:Scroller width="100" height="100">
 *         <s:Group> 
 *            <mx:Image width="300" height="400" 
 *                 source="@Embed(source='assets/logo.jpg')"/> 
 *         </s:Group>        
 *    </s:Scroller>
 *  
 *  The size of the Image control is set larger than that of its parent
 *  Group container. By default, the child extends past the boundaries of
 *  the parent container. Rather than allow the child to extend past the
 *  boundaries of the parent container, the Scroller specifies to clip the
 *  child to the boundaries and display scroll bars.
 *  
 *  Not all Spark containers implement the IViewPort interface. Therefore,
 *  those containers, such as the Border and SkinnableContainer
 *  containers, cannot be used as the direct child of the Scroller
 *  component. However, all Spark containers can have a Scroller component
 *  as a child component. For example, to use scroll bars on a child of
 *  the Spark Border container, wrap the child in a Scroller component.
 *  
 *  To make the entire Border container scrollable, wrap it in a Group
 *  container. Then, make the Group container the child of the Scroller
 *  component, For skinnable Spark containers that do not implement the
 *  IViewport interface, you can also create a custom skin for the
 *  container that includes the Scroller component.
 *  
 *  The IViewport interface defines a viewport for the components that
 *  implement it. A viewport is a rectangular subset of the area of a
 *  container that you want to display, rather than displaying the entire
 *  container. The scroll bars control the viewport's
 *  horizontalScrollPosition and verticalScrollPosition properties. scroll
 *  bars make it possible to view the area defined by the viewport's
 *  contentWidth and contentHeight properties.
 *  
 *  You can combine scroll bars with explicit settings for the container's
 *  viewport. The viewport settings determine the initial position of the
 *  viewport, and then you can use the scroll bars to move it, as the
 *  following example shows:
 *  
 *    <s:Scroller width="100" height="100">
 *        <s:Group
 *            horizontalScrollPosition="50" verticalScrollPosition="50"> 
 *            <mx:Image width="300" height="400" 
 *                source="@Embed(source='assets/logo.jpg')"/> 
 *        </s:Group>                 
 *    </s:Scroller>
 *  
 *  The scroll bars are displayed according to the vertical and horizontal
 *  scroll bar policy, which can be auto, on, or off. The auto policy
 *  means that the scroll bar will be visible and included in the layout
 *  when the viewport's content is larger than the viewport itself.
 *  
 *  The Scroller skin layout cannot be changed. It is unconditionally set
 *  to a private layout implementation that handles the scroll
 *  policies. Scroller skins can only provide replacement scroll bars. To
 *  gain more control over the layout of a viewport and its scroll bars,
 *  instead of using Scroller, just add them to a Group and use the scroll
 *  bar viewport property to link them together.
 */
public class Scroller extends SkinnableComponent 
       implements IFocusManagerComponent, IVisualElementContainer
{

    <a href="SkinPart">SkinPart</a> public var horizontalScrollBar:HScrollBar;
    <a href="SkinPart">SkinPart</a> public var verticalScrollBar:VScrollBar;

    /**
     *  The viewport component to be scrolled.
     * 
     *  The viewport is added to the Scroller component's skin, 
     *  which lays out both the viewport and scroll bars.
     * 
     *  When the <code>viewport</code> property is set, the viewport's 
     *  <code>clipAndEnableScrolling</code> property is 
     *  set to true to enable scrolling.
     * 
     *  The Scroller does not support rotating the viewport directly.  The viewport's
     *  contents can be transformed arbitrarily, but the viewport itself cannot.
     * 
     *  @default null
     */
    <a href="Bindable%28event%3D%26quot%3BviewportChanged%26quot%3B%29">Bindable(event="viewportChanged")</a>
    public function get viewport():IViewport
    public function set viewport(value:IViewport):void

    /**
     *  The minimum space between the viewport and the edges of the Scroller.  
     * 
     *  If neither of the scroll bars is visible, then the viewport is inset by 
     *  <code>minViewportInset</code> on all four sides.
     * 
     *  If a scroll bar is visible then the viewport is inset by <code>minViewportInset</code>
     *  or by the scroll bar's size, whichever is larger.
     * 
     *  ScrollBars are laid out flush with the edges of the Scroller.   
     * 
     *  @default 0 
     */
    public function get minViewportInset():Number
    public function set minViewportInset(value:Number):void

    /**
     *  If <code>true</code>, the Scroller's measured size includes the space required for
     *  the visible scroll bars, otherwise the Scroller's measured size depends
     *  only on its viewport.
     * 
     *  <p>Components like TextArea, which "reflow" their contents to fit the
     *  available width or height may use this property to stabilize their
     *  measured size.  By default a TextArea's is defined by its <code>widthInChars</code>
     *  and <code>heightInChars</code> properties and in many applications it's preferable
     *  for the measured size to remain constant, event when scroll bars are displayed
     *  by the TextArea skin's Scroller.</p>
     * 
     *  <p>In components where the content does not reflow, like a typical List's
     *  items, the default behavior is preferable because it makes it less
     *  likely that the component's content will not obscured by a scroll bar.</p>
     * 
     *  @default true
     */
    public function get measuredSizeIncludesScrollBars():Boolean
    public function set measuredSizeIncludesScrollBars(value:Boolean):void
}

spark.components.ScrollBar viewport Property


One property and five methods were added to the ScrollBar to support linking a scrollbar and a viewport. Most of the linking "work" is done by the vertical and horizontal ScrollBar subclasses VScrollBar and HScrollBar.

    /**
     *  The viewport controlled by this scrollbar.  If a viewport is
     *  specified, then changes to its actual size, content size, and
     *  scroll position, cause the corresponding ScrollBar methods to run:
     * 
     *  - viewportResizeHandler()
     *  - contentWidthChangeHandler()
     *  - contentHeightChangeHandler()
     *  - viewportVerticalScrollPositionChangeHandler()
     *  - viewportHorizontalScrollPositionChangeHandler()
     * 
     *  The VScrollBar and HScrollBar classes override these methods to 
     *  keep their pageSize, maximum, and value properties in sync with the
     *  viewport.   Similarly, they override their page and step methods to
     *  use the viewport's scrollPositionDelta methods to compute page and
     *  and step offsets.
     *    
     *  @default null
     */
    public function get viewport():IViewport
    public function set viewport(value:IViewport):void

   /**
    *  Called when the viewport's width or height changes, does nothing by default.
    */
    protected function viewportResizeHandler(event:ResizeEvent):void

   /**
    *  Called when the viewport's contentWidth changes, does nothing by default.
    */
    protected function viewportContentWidthChangeHandler(event:PropertyChangeEvent):void

    /**
     *  Called when the viewport's contentHeight changes, does nothing by default.
     */
    protected function viewportContentHeightChangeHandler(event:PropertyChangeEvent):void

    /**
     *  Called when the viewport's horizontalScrollPosition changes, does nothing by default.
     */
    protected function viewportHorizontalScrollPositionChangeHandler(event:PropertyChangeEvent):void

    /**
     *  Called when the viewport's verticalScrollPosition changes, does nothing by default. 
     */
    protected function viewportVerticalScrollPositionChangeHandler(event:PropertyChangeEvent):void

B Features


Content size changes should be reported with a ResizeEvent, for consistency with changes to a component's acutal size.


Related

Wiki: Flex 4
Wiki: Spark DataGroup
Wiki: Spark Group
Wiki: Spark Viewport

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.