Scroller/List Item Snapping - Functional and Design Specification
Glossary
Content - In a scrollable component (i.e. List), the content is the full extent of the area being scrolled, of which only a subset may be visible at any one moment.
Viewport - The "window" through which a subset of the content in a List or other scrollable component is visible.
Velocity - A measurement of the rate and direction of movement. In our case, we are typically interested in the velocity of the user's finger during a touch gesture, and this is usually expressed in pixels-per-millisecond along both the X and Y axes.
Drag - A touch gesture in which the user touches scrollable content and moves their finger such that the content moves along with it.
Throw - A drag gesture in which the user lifts the finger once it has reached a certain velocity, and the scrollable content underneath continues to move for some time/distance
Summary and Background
List Item Snapping is a new set of touch-interaction behaviors for Spark List and other scrollable components. It provides an alternative to pixel-based scrolling, in which user scroll gestures can end on any arbitrary pixel location. With List Item Snapping, scrolls always end at well-defined locations: either at multiples of the viewport size or relative to the location of individual items in the component's layout.
The SpinnerList component will use List Item Snapping to ensure that all scrolls end with an item located exactly at the center of the component.
Usage Scenarios
Parker is building a Flex application to display cocktail recipes on a mobile phone. She wants the user to be able to scroll between recipes, but wants one recipe at a time to fill the screen, and wants it to be easy for the user to center it there. To do this, she uses a Spark List with a horizontal layout. She creates an item renderer that sizes each item as large as the List component, and enables paging and item snapping in order to obtain the desired user-interaction behavior.
Harper is creating a Flex application which displays a Facebook user's news feed in a scrollable vertical list. She wants to ensure that the topmost item is always fully visible. To do so, she creates a Spark List component and enables item snapping to achieve the desired effect.
Detailed Description
There are two distinct and yet related behaviors under the "List Item Snapping" feature umbrella:
Paging. The scrollable content is logically divided into distinct "pages" and user interactions can cause the content to scroll a maximum of one page at a time in either direction. Throws or drags of more than one page at a time are not allowed. The size of a page is primarily determined by the dimension of the viewport along the axis of scrolling (i.e. the viewport width for horizontal scrolling, or the viewport height for vertical scrolling). If a user gesture is not of a sufficient distance or velocity to scroll to the adjacent page, the current page "snaps" (with an animation) back into position. A good example of this sort of behavior can be found in the iPhone Weather application.
Item Snapping. This causes the scrollable content to always be aligned in the viewport according to the position of individual layout items it contains. All touch interactions result in this alignment requirement being met - even if an animation needs to be added to the end of the interaction to "snap" things into place. This behavior will be used by SpinnerList, which requires that a layout item is positioned exactly in the vertical center of the viewport once any touch interaction has concluded. To avoid confusion, the rest of this spec uses the term "item snapping" only to refer to this one specific behavior - not to the development feature as a whole. Also note that PARB has chosen the name "scroll snapping" for what most of this spec refers to as "item snapping". This is reflected in the API description below.
These two behaviors can be enabled in any combination, depending on the type of scrolling desired.
| Paging? | Snapping? | Resulting behavior |
|---|---|---|
| N | N | Today's normal pixel-based scrolling. |
| N | Y | Long throws end at a correct item-aligned position. Drags snap back to alignment if necessary |
| Y | N | Throws and drags scroll one page at a time. Page size is determined solely by viewport scroll-axis dimension. Pages snap back into place if gesture does not switch pages. |
| Y | Y | Throws and drags scroll one page at a time. Page size is determine primarily by viewport dimension, but item nearest to the new page boundary is snapped to. Pages snap back into place if gesture does not switch pages. |
When item snapping is enabled, the developer must select one of the following types of item alignment:
- Leading Edge. For horizontal scrolling, the left edge of an item aligns with the left edge of the viewport. For vertical scrolling, the top edges align.
- Center. The midpoint of an item (either horizontally or vertically, depending on the direction of scrolling) aligns with the midpoint of the viewport.
- Trailing Edge. For horizontal scrolling, the right edge of an item aligns with the right edge of the viewport. For vertical scrolling, the bottom edges align.
An important point to note is that item snapping involves the alignment of the specified edges of both the viewport and an item visible within.
Paging and item snapping are exposed through public properties of the List class, which are proxied to the same set of properties on the Scroller class. This allows the behaviors to be configured for List components declared in MXML files, and also allows other scrollable components not derived from List to make use of them.
These new behaviors are only available in touch interaction mode. The UIComponent's interactionMode style must be set to "touch".
Interaction Specifics
Paging: When paging is enabled (and whether or not snapping is enabled), here are the possible touch interactions and their effects:
- Dragging. If the current page is dragged such that less than 50% of it remains visible, releasing the finger (i.e. with little to no velocity) will cause the adjacent partly-exposed page to animate into position in the viewport. If the drag is less than 50% of the current page, the current page animates back into position in the viewport when the finger is released.
- Throwing. If the current page is thrown at a velocity that exceeds a certain threshold (to be determined), the adjacent page will animate into position in the viewport. Throws that do not exceed the velocity threshhold will have their velocity decay to zero and change direction - bringing the current page back into position. If a throw causes a page switch, and the velocity is high enough, the new page may scroll a bit past its final position and "bounce" back into place.
Item Snapping: When item snapping (but not paging) is enabled, here are the possible touch interactions and their effects:
- Dragging. When the user lifts the finger, the item which has its leading edge, center, or trailing edge nearest the leading edge, center, or trailing edge of the viewport is animated into alignment.
- Throwing. The distance of the throw is calculated according to its initial velocity and the Scroller's natural deceleration rate. Then the item closest to alignment at the end of the throw is identified, and the deceleration rate is adjusted so the throw ends at the correct aligned position. In the case of virtual layout, the item chosen for alignment may just be an estimate based on "typical item" measurement. If the estimate is incorrect, the throw animation will be adjusted near its completion to satisfy the alignment requirements.
Paging and Item Snapping combined
Enabling both paging and item snapping can be useful in cases where items are not exactly the size of the viewport. In this mode, switching to an adjacent page scrolls the content nearly the size of the viewport, but the distance is adjusted to ensure that the content ends up correctly aligned. This mode can support the sort of "hinting" seen on Windows mobile phones: a small portion of an adjacent item is visible, and paging it into view causes it to be fully visible. Paging by viewport size alone would mean that the partially visible item would still only be partially visible in the adjacent page.
Having both paging and item snapping enabled is probably the recommended mode for most paging scenarios. In the most straightforward case, wherein each item is the same size as the viewport, this mode allows the paging to skip over any padding between items without it taking up space on each page.
Status and Scroll Position
The developer should use the existing TOUCH_INTERACTION_START and TOUCH_INTERACTION_END events to determine whether a page switch might have occurred. The existing verticalScrollPosition and horizontalScrollPosition properties can be checked at the end of a touch gesture to determine what part of the content is currently visible.
The developer can also use the viewport scroll position properties to cause a specific item and/or page to be snapped into place. If item snapping is off but paging is on, the scroll position can only be set to a multiple of the viewport size. If item snapping is on, only scroll positions that conform to the type of snapping in effect are allowed. Attempting to set a scroll position value that does not conform to the current snapping and/or paging modes will result in the nearest conforming value being set instead.
Supported Layouts
Initially, the only layouts supported for these features are HorizontalLayout, VerticalLayout, VerticalSpinnerLayout, and TileLayout.
API Description
package spark.components
{
public class List extends ListBase implements IFocusManagerComponent
{
...
/**
* Whether page scrolling is currently enabled for this List
*
* @default false
*/
public function get pageScrollingEnabled():Boolean;
public function set pageScrollingEnabled(value:Boolean):void;
/**
* The scroll snapping mode currently in effect for this List
*
* <p>Changing this property to anything other than "none" may
* result in an immediate change in scroll position to ensure
* an element is correctly "snapped" into position. This change
* in scroll position is not animated</p>
*
* @see spark.components.ScrollSnappingMode
*
* @default "none"
*/
public function get scrollSnappingMode():String;
public function set scrollSnappingMode(value:String):void;
...
}
}
package spark.components
{
public class Scroller extends SkinnableComponent
implements IFocusManagerComponent, IVisualElementContainer, ITouchScrollClient
{
...
/**
* Whether page scrolling is currently enabled for this Scroller
*
* @default false
*/
public function get pageScrollingEnabled():Boolean;
public function set pageScrollingEnabled(value:Boolean):void;
/**
* The scroll snapping mode currently in effect for this Scroller
*
* <p>Changing this property to anything other than "none" may
* result in an immediate change in scroll position to ensure
* an element is correctly "snapped" into position. This change
* in scroll position is not animated</p>
*
* @see spark.components.ScrollSnappingMode
*
* @default "none"
*/
public function get scrollSnappingMode():String;
public function set scrollSnappingMode(value:String):void;
...
}
}
package spark.components
{
/**
* Values for the <code>scrollSnappingMode</code> property of the
* List and Scroller classes
*
*/
public final class ScrollSnappingMode
{
/**
* Scroll snapping is off
*
*/
public static const NONE:String = "none";
/**
* Elements are snapped to the left (horizontal) or top (vertical)
* edge of the viewport.
*
*/
public static const LEADING_EDGE:String = "leadingEdge";
/**
* Elements are snapped to the center of the viewport.
*
*/
public static const CENTER:String = "center";
/**
* Elements are snapped to the right (horizontal) or bottom (vertical)
* edge of the viewport.
*
*/
public static const TRAILING_EDGE:String = "trailingEdge";
}
}
B Features
- Page indicator/control. iOS has a separate dedicated page control that is visible as a series of dots (one for each page) typically located below paging content. Developing something like that is beyond the scope of this feature for now. We'll continue to rely on scroll bars to give visual feedback to the user.
- Diagonal scrolling. For now, this feature only supports content that scrolls along a single axis.
- Declarative use for any other component beside List. For now, only List will expose the relevant properties and proxy them to Scroller.
- List with no Scroller. Scroller is an optional skin part of the List component, but this feature will only work when there is a Scroller. Setting the properties will have no effect if there is no Scroller in the skin attached to the List.
- Supporting additional layouts.
- Give the developer some control over the minimum velocity threshold needed for "throwing" to an adjacent page.
- Other scroll snapping modes. For instance, one which aligns to either the lead or trailing edge depending on the direction of the scroll/throw.
Examples and Usage
Declaring a simple horizontally-paging List in MXML.
<s:List id="pageList" pageScrollingEnabled="true" scrollSnappingMode="center" itemRenderer="renderers.MyPageRenderer" dataProvider="{createPages()}" verticalScrollPolicy="off" horizontalScrollPolicy="on"> <s:layout> <s:HorizontalLayout/> </s:layout> </s:List>
A basic vertical list in which the top item is always fully visible.
<s:List id="snapList" scrollSnappingMode="leadingEdge" itemRenderer="renderers.MyItemRenderer" dataProvider="{createItems()}" verticalScrollPolicy="on" horizontalScrollPolicy="off"> <s:layout> <s:VerticalLayout/> </s:layout> </s:List>
Additional Implementation Details
Enter implementation/design details for the feature here. This section may be updated after the spec signs off.
Compiler Work
Will this feature require any compiler changes? If so, describe here, if not already included in the Detailed Description.
- any new compiler switches (flex-config.xml configuration)
- proposed workflow changes
- changes to the Compiler API - example, a logger or localization manager now required
Cross-Form-Factor Considerations
If this feature primarily targets a particular form factor such as phone, tablet, or desktop, then what is the impact on the other form factors? Will work be needed/done (e.g., additional skins or functionality) to support other form factors? Will the feature be disallowed altogether on some form factor (and if so, how)? Is support on another form factor covered by a different spec (if so, provide link)?
All form factors (whether supported or gracefully unsupported by the feature) should be addressed in some way (with all work reflected in the task list), so there are no loose ends.
Cross-Platform Considerations
Does this feature apply differently to (or require special work for) Windows versus Mac platforms, iOS versus Android versus [...], or require different testing depending on platform?
Backwards Compatibility
Syntax changes
List any syntax changes (MXML tags, attributes, AS syntax changes) that may affect backward compatibility with existing applications.
Does any part of this feature support the -compatibility-version compiler argument?
Behavior
List any changes to behaviors of existing MXML tags, attributes, etc.
Warnings/Deprecation
Clearly identify anything in existing features that will become deprecated. Identify warnings that will be issued (if any) as a result. Identify recommended documentation changes that should accompany.
Accessibility
Describe any accessibility considerations.
Performance
Describe any performance considerations or requirements.
Globalization
All components that deal with text will need to be globalization-aware. This means that text in any supported language can be used with the component.
The component must use the Unicode character set (mostly comes for free from Flash Player) and must interoperate with IME's (Input Method Editors) for foreign languages (also mostly supported via Flash Player).
Globalization also includes issues with date/time formatting and currency formatting for specific locales.
European (French, German, etc.) and Asian (Japanese, Korean, Chinese) languages are of particular concern.
Components should look and behave correctly when mirrored by setting layoutDirection to rtl.
Note any specific globalization issues or challenges here.
Localization
List the RTE message strings that will require localization. (They must all come from .properties files.)
List all UI text that will require localization, such as the month and day names in a DateChooser. (All such text must all come from .properties files.)
List all UI images, skins, sounds, etc. that will require localization.
Discuss any locale-specific formatting requirements for numbers, dates, currency, etc.
Discuss any locale-specific sorting requirements.
Issues and Recommendations
Enumerate open issues needing resolution, along with recommended solutions, if any.