You.i Engine
List View

Detailed Description

List views provide a means to display dynamic and scrollable content.

CYIListView enables browsing through dynamic content via focus or pointer navigation. The content is usually, though not necessarily, specified in a CYIAbstractDataModel. CYIListView enables streaming of its contents with out-of-the-box View Recyclers or manually through the Adapters. 'Streaming' refers to the tracking of items within the list's visible region. For more on streaming, see the CYIStreamer documentation.

For browsing through static content, or content that is not numerous enough to warrant lifecyle management, CYIScrollingView may be used instead. Many of the features documented here are applicable to CYIScrollingView, these will be highlighted.

For presentation of static content that does not need to scroll, consider CYISceneView with an optional CYILayout.

Contents:

Components

This diagram demonstrates the role of each of: a CYIListView, a CYINodeAdapter implementation, and an optional IYIViewRecycler in producing content for the list to display.

ListView.png

List view

CYIListView is a scrolling view which displays dynamic content. This content is produced by an adapter as needed based on the user's scroll position within the list. A Layout must be used with CYIListView due to the dynamically-generated nature of its content. If no layout is specified a CYIColumnLayout will automatically be assigned.

Adapters

The content of a CYIListView is abstracted as a set of 0-based indices. Adapters are responsible for producing views based on these indices. These indices are consecutive numbers from 0 to one fewer than the number of items in the list, as defined by the adapter in CYINodeAdapter::GetItemsCount. It is up to the application developer to match these indices to their data model representation. For applications making use of CYIAbstractDataModel, this might be as simple as using the item index as a row index in their data model from within the adapter. See Integration with Data Model for an example of this.

You.i Engine provides a set of adapter interfaces but implementations must be provided by application developers, either through concrete implementations of an adapter interface or through the use of delegate adapters.

CYINodeAdapter is the most generalized adapter interface and provides the base for the adapter class hierarchy. CYIViewAdapter provides a simpler interface specialized for the construction of views from templates.

Each adapter has a delegated implementation. These concrete implementations allow the configuration of a delegate to which the adapter's responsibilities will be delegated. This allows the extension of an existing component, such as a CYIAbstractScreenViewController subclass, with adapter responsibilities.

View recyclers

View recyclers are used by CYIViewAdapter. When CYIListView indicates that a view at an index has streamed out, CYIViewAdapter uses its view recycler to determine what to do with the view. By default a CYICreateDeleteViewRecycler is used, which simply deletes the view. When a view at an index has streamed in, the view recycler will be used to produce the view for that index. The CYICreateDeleteViewRecycler will allocate a new view instance for this purpose. CYIPooledViewRecycler makes use of a dynamic view pool, creating instances as needed but re-using instances where possible.

Basic List

CYIListView may be found in the You.i Engine After Effects Plug-in Component Library under "List."

ListViewAE.png

height=540px

The list component contains placeholder items so that it can be previewed. The properties of these items can be used in construction of views within this list. See Components for more information on the adapter and how it populates the list with views. The "placeholder" property is a general purpose property and may be used outside of CYIListView as well. The properties panel may be used to configure an item as a placeholder:

ListViewContentsPlaceholder.png

Content within a CYIListView which is not marked as placeholder content will appear inside the list at run-time, as if it were inside a CYIScrollingView. This content does not stream in and out as it moves through the list, but it will participate in the list's layout and will scroll along with the dynamic content of the list.

An adapter must be used to populate this list at run-time. Two examples making use of CYIViewAdapter and CYIDelegatingViewAdapter appear below. See Components for more information on the adapter and its relationship with CYIListView.

Using a View Adapter (CYIViewAdapter)

The following code constructs a set of 5 views using the same template for each view. In practice, it may be desirable to have a different template for each index. To accomplish this SingleTemplateViewAdapter::GetViewTemplate would be modified to return a different std::shared_ptr<CYIAssetViewTemplate> for each index.

class SingleTemplateViewAdapter : public CYIViewAdapter
{
public:
SingleTemplateViewAdapter()
: m_pTemplate(CYIViewTemplate::GetViewTemplate("ListItemTemplate"))
, m_imageURL(CYIString("http://youiimageexample.com/image.jpg"))
{
}
virtual size_t GetItemsCount() const override
{
return 5;
}
virtual std::shared_ptr<CYIAssetViewTemplate> GetViewTemplate(size_t index) const override
{
YI_UNUSED(index);
return m_pTemplate;
}
protected:
virtual void PopulateView(size_t index, CYISceneView *pView) override
{
pView->GetNode<CYITextSceneNode>("TextLabel")->SetText(CYIString::FromValue(index));
pView->GetNode<CYIImageView>("PosterImage")->SetImage(m_imageURL);
}
std::shared_ptr<CYIAssetViewTemplate> m_pTemplate;
CYIUrl m_imageURL;
};
void ConfigureListWithViewAdapter()
{
CYIListView *pListView = pMainView->GetNode<CYIListView>("ListView");
pListView->SetAdapter(std::make_unique<SingleTemplateViewAdapter>());
}

Using a Delegating View Adapter (CYIDelegatingViewAdapter)

The following code also constructs a set of 5 views using the same template for each view. However, this makes use of an external class which implements CYIDelegatingViewAdapter::Delegate in order to move the adapter's responsibility to an existing class, which may have additional responsibilities.

class SingleTemplateAdapterDelegate : public CYIDelegatingViewAdapter::Delegate
{
public:
SingleTemplateAdapterDelegate()
: m_pTemplate(CYIViewTemplate::GetViewTemplate("ListItemTemplate"))
, m_imageURL(CYIString("http://youiimageexample.com/image.jpg"))
{
}
virtual ~SingleTemplateAdapterDelegate() = default;
virtual size_t GetItemsCountForAdapter() const override
{
return 5;
}
virtual std::shared_ptr<CYIAssetViewTemplate> GetViewTemplateForAdapter(size_t index) const override
{
YI_UNUSED(index);
return m_pTemplate;
}
protected:
virtual void PopulateViewForAdapter(size_t index, CYISceneView *pView) override
{
pView->GetNode<CYITextSceneNode>("TextLabel")->SetText(CYIString::FromValue(index));
pView->GetNode<CYIImageView>("PosterImage")->SetImage(m_imageURL);
}
std::shared_ptr<CYIAssetViewTemplate> m_pTemplate;
CYIUrl m_imageURL;
};
void ConfigureListWithDelegatingViewAdapter()
{
SingleTemplateAdapterDelegate *pDelegate = GetAdapterDelegate();
CYIListView *pListView = pMainView->GetNode<CYIListView>("ListView");
pListView->SetAdapter(std::make_unique<CYIDelegatingViewAdapter>(pDelegate));
}
Note
If the contents of the list are not specified from a view template, or are not CYISceneView types, then CYINodeAdapter may be used to produce CYISceneNode contents.

View Recyclers

As a user navigates through a CYIListView new views need to be presented to the user and old views are no longer needed. For complex, rich, animated views with many elements this construction and deletion might be taxing on the system to the point where it becomes noticeable. For this reason CYIViewAdapter and CYIDelegatingViewAdapter can make use of an optional IYIViewRecycler implementation which can be used to re-use views as they become available.

There are two IYIViewRecycler types available: the default and trivial CYICreateDeleteViewRecycler which offers no benefit over manual deletion and creation of views, and the CYIPooledViewRecycler which will use a view pool to re-use views which are based on the same view template. If using CYIPooledViewRecycler it is the responsibility of the adapter implementation to ensure that views are returned to the pool in a defaulted state. For custom views this can be accomplished by implementing CYISceneView::Reset to clear all state from the view, and in the general case can be accomplished by overriding CYIViewAdapter::ReleaseNode and clearing all state from the view prior to calling the base class implementation.

Integration with Data Model

CYIAbstractDataModel provides a generic means to represent arbitrary data, organized as nested tables. Refer to the Data Model documentation for detailed explanation of its usage. The following sample makes use of a data model representation of a list, seen below:

ListDataModel.png

height=312px

The data model is contained in an adapter which uses it to populate the CYIListView. This example differs from the Using a View Adapter (CYIViewAdapter) example in that the data model contains a list of CYIUrl instances with which to populate the list.

class SingleTemplateViewAdapterWithDataModel : public CYIViewAdapter
{
public:
SingleTemplateViewAdapterWithDataModel(const CYIAbstractDataModel &model)
: m_pTemplate(CYIViewTemplate::GetViewTemplate("ListItemTemplate"))
, m_model(model)
{
}
virtual size_t GetItemsCount() const override
{
return 5;
}
virtual std::shared_ptr<CYIAssetViewTemplate> GetViewTemplate(size_t index) const override
{
YI_UNUSED(index);
return m_pTemplate;
}
protected:
virtual void PopulateView(size_t index, CYISceneView *pView) override
{
CYIAny imageUrl = m_model.GetItemData(m_model.GetIndex(index, 0));
pView->GetNode<CYITextSceneNode>("TextLabel")->SetText(CYIString::FromValue(index));
pView->GetNode<CYIImageView>("PosterImage")->SetImage(imageUrl.Get<CYIUrl>());
}
std::shared_ptr<CYIAssetViewTemplate> m_pTemplate;
const CYIAbstractDataModel &m_model;
};
void ConfigureListWithViewAdapterUsingDataModel()
{
const CYIAbstractDataModel &postersModel = GetPosterListDataModel();
CYIListView *pListView = pMainView->GetNode<CYIListView>("ListView");
pListView->SetAdapter(std::make_unique<SingleTemplateViewAdapterWithDataModel>(postersModel));
}

List with 'Move' timeline

Note
This feature is inherited from CYIScrollingView

In order to increase the visual appeal of a CYIListView it is possible to make use of a 'move' timeline for the items within the list. This is a special timeline which is not driven by time changes but is instead driven by the position of the item in view.

For one dimensional lists a timeline named 'Move' is adequate, for two dimensional lists a different timeline may be specified for each of the horizontal and vertical directions. These directional move timelines are named 'MoveHorizontal' and 'MoveVertical' respectively. For two dimensional lists the 'Move' timeline may still be specified and will use the distance from the top-left of the list as its position within the timeline.

More information on the 'move' timeline feature can be found in CYIScrollingView documentation, under 'Move Timelines.'

As an example, adding an opacity 'MoveHorizontal' timeline to the component library list like so:

ListMoveAE.png

Produces this output when previewed:

ListMove.gif

List with 'Cascade' timeline

Note
This feature is inherited from CYIScrollingView

'In' and 'Out' animations are used when switching between collections of views such as in the case of screen transitions. CYIListView allows its contents to be animated in a 'cascading' fashion, meaning that items within the list begin to animate with staggered offsets, allowing for rich and interesting animations to be created. This is accomplished by the inclusion of 'CascadeIn' and 'CascadeOut' timelines within the list items. Note that an 'In' or 'Out' marker must be present inside the CYIListView and in all views between the CYIListView and the screen root in order for the 'In' and 'Out' timeline group generation to find the cascade timelines automatically.

More information on the 'cascade' timeline feature can be found in CYIScrollingView documentation, under 'Cascade Timelines.'

As an example, adding a 'CascadeIn' timeline to the component library list contents:

ListCascadeAE.png

Produces this output when previewed:

ListCascade.gif

Navigating a List

Note
This feature is inherited from CYIScrollingView

Just like CYIScrollingView the list can be thought of as a scrolling window to view content. On platforms with touch and cursor input users can scroll throught the list's content by pressing on the content of the list and dragging in the opposite direction of a scroll.

TouchNavigationInList.gif

On platforms which use focus to navigate, the list scrolls whenever a new item is focused until the item is completely visible or, if the item is larger than the list, until its top or left edge is aligned with the top or left edge of the list.

FocusNavigationInList.gif

Navigation with magnets

Note
This feature is inherited from CYIScrollingView

Magnets can be added to the list so that items snap to a position as the view is scrolled. A list scrolled left will have content entering on the left side. If the view is configured with horizontal edge magnets the list will move to align the left magnet and the closest left edge of a magnetic child. Similarly when content enters from the right, top, or bottom while scrolling, the view will align the right, up, and down magnets to the right, top and bottom edge of the closest magnetic child.

TouchNavigationInListMagnetsEdges.gif

When the list has a horizontal center magnet the view will snap the center of each item to the magnet's position. The same is true for a single vertical magnet.

TouchNavigationInListMagnetsCenter.gif

When the list has a horizontal begin magnet the view will snap the left of each item to the magnet's position. For vertical magnets the view will snap the top of each item to the magnet's position.

TouchNavigationInListMagnetsBegin.gif

When the list has a horizontal end magnet the view will snap the right of each item to the magnet's position. For vertical magnets the view will snap the bottom of each item to the magnet's position.

TouchNavigationInListMagnetsEnd.gif

See the CYIScrollingView documentation under 'Magnets' for more details.

Carousel list

Note
This feature is inherited from CYIScrollingView

CYIListView can be used to present infinitely scrollable content by enabling the 'carousel' mode in either the horizontal or vertical directions:

ListCarouselAE.png
ListCarouselNormal.gif

Carousel causes the content of the list to wrap around so that a full scroll through the list contents returns the user to the point at which they started, rather than at the end of the content. If the list content does not fill the CYIListView but the view is set to 'carousel always' then the list will be allowed to scroll through the negative space between the existing content and its next iteration, as demonstrated here (background added for clarity):

ListCarouselNegativeSpace.gif

There are limitations to the supported use cases of carousel mode when working with 3D content, refer to the CYIScrollingView documentation, under 'Limitations in 3D' for more details.

The out-of-the-box 'Cascade' timeline behaviour is supported with carousel mode but customizing the cascade animation in code by overriding CYIScrollingView::GetTotalCascadeDelayFor in a CYIListView subclass is not supported.

Due to the fact that a single large item may be present multiple times within the visible region, 'Move' timelines are played based on whichever manifestation of the view is closest to the center of the view. For more details refer to the CYIScrollingView documentation under 'Move Timelines.'

Layouts

Note
This section applies to CYIScrollingView and CYISceneView

Unlike with CYISceneView or CYIScrollingView, a CYILayout must be used when using CYIListView. Typically, a CYIRowLayout should be used for horizontal lists and a CYIColumnLayout should be used for vertical lists. The component library list is an example of a horizontal list laid out using a CYIRowLayout. The Basic 2D List example below demonstrates the use of a CYIGridLayout to build a two dimensional list. Unlike CYIScrollingView, CYIListView will automatically apply a CYIColumnLayout to itself if no layout is specified.

It should be noted that though it is possible to construct custom layouts by subclassing CYILayout, the 2D panning behaviour of the CYIListView will not change. So long as the layout produces an arrangement of items that spills out beyond the confines of the view and scrolling is not explicitly disabled on the CYIListView, the user will be able to pan (or focus navigate) through the content as if it were on a sliding plane.

Normally when a view is re-sized inside of a layout more of its content is revealed but the content does not re-size unless configured to do so. For a one dimensional list it is often desirable for the list to scale its contents direction orthogonal to its scrolling direction while revealing more content when growing into its scrolling direction, as demonstrated here:

ListGrow.gif

The example above was accomplished by placing the list into a container with a CYIScalingLayout applied with scaling mode "fit." The scaling container was configured to fill its parent in both axes, and the list was configured to fill the scaling container on the horizontal axis. As a result, as the scaling container was resized the list was scaled vertically to "fit" the container and resized horizontally to reveal more content in the direction of scroll. More information on Layout can be found at the linked page.

Margins, Padding, and Spacing

Note
This section applies to CYIScrollingView and CYISceneView
margins.png

CYIListView makes full use of CYILayout and its properties related to margins, padding, and spacing. See Layout for details on how to configure the layout of the CYIListView to achieve any desired item placement. The only special consideration for CYIListView is the inability to set per-item layout properties such as margins on the dynamic contents of the list. In order to configure these per-item properties the adapter implementation must override CYINodeAdapter::PopulateLayoutConfig and provide the correct configuration for the item's index.

Basic 2D List

Note
This section applies to CYIScrollingView

CYIListView is innately two dimensional. Since CYIListView may be used with any CYILayout, applying a two-dimensional layout to the list is enough to produce a 2D list. Note that CYIListView supports two dimensional panning, but nothing else. Applying a custom layout to the list does not change the interaction behaviour, for instance applying a radial layout to the list will not enable rotation of the contents through user interaction.

Applying the following layout configuration to the component library list:

ListGridLayoutConfiguration.png

Then increasing its view size in the vertical direction to reveal more content, followed by enabling bi-directional scrolling on it via the properties panel, yields this result:

ListGridLayout.gif

Expandable List Items

Note
This section applies to CYIScrollingView

CYIListView is entirely compatible with the Layout system. Resizing list items does not require any configuration or consideration beyond the normal layout considerations. This example changes the scale of a list item causing the list's CYIRowLayout to reposition the other elements.

ListExpandingItem.gif

As with any other state changes, since the content of CYIListView is dynamically populated via an adapter, the size changes to items must be accounted for in the adapter as the item is released and reacquisitioned during list scrolling. That is to say, if an item at an index doubles in size and is scrolled out of view, then the adapter must produce it at its doubled size the next time it enters view in order to maintain a continuity of experience.

List Backgrounds

Note
This section applies to CYIScrollingView

Using the layout system's background property, a background may be set on the CYIListView. This background will encapsulate the entirety of the content and will scroll with the list, but will not appear in the overflow region of the list. To remedy this, a negative margin may be used that is equal in magnitude to the overpull amount. Alternatively the background may be pulled out of the list and applied to a container containing the list, this will be demonstrated later in this section.

ListBackgroundInternal.gif

The background will be stretched to cover the entirety of the content by default. If the content is smaller than the view size, the background will fill the view size. If the background is an image, instead of a solid color as in the example above, the properties panel may be used control the scaling properties of the image, i.e. to tile it instead.

ImageTileProperties.png

If the background should not scroll with the list, should not encapsulate the entire content of the list, or should be within the overpull region of the list, then the background should be applied to a container containing the list instead of applied to the list directly. In this example the list is within a container that has a CYIStackLayout and is set to fit its content, and the background is applied to the outer container.

ListBackgroundExternal.gif

Lists of Lists

Constructing a list of lists requires the use of an adapter for the outer list, and adapters for each of the inner lists. The outer list may make use of a CYIViewAdapter with a view template constaining a CYIListView in order to construct its inner lists. Due to the increased complexity in using lists within lists, it is recommended that CYIScrollingView be used for the outer scrolling container in the case of a static number of sub-lists, or sub-lists that do not need to benefit from CYIListView's streaming of its content. The following example demonstrates a list with a CYIColumnLayout applied, and horizontal scrolling disabled, that contains horizontal sub-lists. Disabling horizontal scrolling on the outer vertical list is not strictly necessary but does prevent the outer list from consuming horizontal scroll actions under any circumstances.

ListOfLists.gif

Migrating to the new CYIListView

CYIListView replaces the legacy CYIListView. Prior to You.i Engine 4.4.0. Applications that used the legacy list view must update their After Effects component to use 'List' rather than 'Deprecated List', and ensure the comment on the list composition is 'CYIListView' rather than 'yi::deprecated::CYIListView'. This section describes the noteworthy changes between the two list implementations.

List Root

The legacy CYIListView made use of a 'ListRoot' node, specified in After Effects as a 'Null Object.' This is no longer necessary. Anything placed within the new CYIListView that contains the 'placeholder' property will not be exported by the You.i Engine After Effects Plug-in. Those items which do not have the property will exist within the list but will not benefit from its streaming. When exporting the AE project, you may receive a warning urging you to remove this unnecessary 'ListRoot' component. Likewise, when loading a You.i Engine application using AE project files that have not removed the 'ListRoot' you will receive an error requesting you to make this change.

Streaming, Visible, and Loading Range

The legacy CYIListView used three ranges to manage the streaming of its content: a visible range outside of which items were hidden by default, a streaming range outside of which views could be deleted, and a loading range which, upon entry, could be used to initiate asynchronous requests for content. The new CYIListView contains signals corresponding to the visible in/out and stream in/out hooks of the legacy CYIListItem, but these are non-configurable ranges. There is no loading range.

Paging

The legacy CYIListView provided 'paging' functionality through its SetPageSize, ShowPage, and other functions. This 'paging' feature allowed for the specification of resting points through the content that the list would snap to, and for navigation one page at a time through swipe gestures. There is currently no substitute for this functionality in the new CYIListView.

Move and Cascade timelines

The legacy CYIListView allowed specification of 'move' and 'cascade' timelines on its contents through precise and elaborate configuration of its contents in After Effects. These timelines are now contained within the list items rather than within the list as outlined in the List with 'Move' timeline and List with 'Cascade' timeline portions of this document.

Margins and Padding

Padding between items in the legacy CYIListView could be accomplished by changing the reported the legacy CYIListItem size, padding the view size in After Effects, or any number of other means. As outlined in the Margins and Padding this is now entirely done through CYILayout.

List Items

The legacy CYIListItem served as the data model of the legacy CYIListView, containing information about a single element inside the list. The adapter now fills this role for the set of all items in the list.

The class used for items in a list is determined by CYIViewAdapter::GetViewClass. By default, CYISceneNode is used. The CYISceneNode class may be extended as necessary to add more specific functionality, but the default should handle most use cases.

Per-item configurations may still be achieved through the use of a CYIViewAdapter by referencing the item index and setting properties directly using CYIViewAdapter::ConfigureProperties.

To respond to changes to the set of items being managed by the adapter such as items changing index, items being removed, or items being added, refer to their respective signals CYINodeAdapter::ItemMoved, CYINodeAdapter::ItemRemovedAtIndex, and CYINodeAdapter::ItemAddedAtIndex, all inherited by CYIListView.

Carousel Behaviour

The legacy CYIListView supported carousel mode in its items, but required a minimum of three items, one of which had to be entirely outside of the viewable region. Its behaviour was roughly analogous to CYIListView with carousel mode 'CYIScrollingView::CarouselRule::IfOverflowing.' These restrictions no longer apply as outlined in the Carousel list documentation.

Image Download List Item

The legacy CYIImageDownloadListItem provided a convenience when constructing lists of remotely downloaded images. The same functionality is now achievable through the use of CYIImageView::SetImage(const CYIUrl &).

Responsive Layout Anchors

The legacy CYIListView worked with specific responsive layout anchors to control its scaling behaviour. As outlined in the Layouts the same functionality is provided through CYILayout.

Classes

class  CYIDelegatingNodeAdapter
 An CYINodeAdapter implementation that delegates its responsibilities to a CYIDelegatingNodeAdapter::Delegate implementation. More...
 
class  CYIDelegatingViewAdapter
 A CYIViewAdapter implementation that delegates its responsibilities to a CYIDelegatingViewAdapter::Delegate implementation. More...
 
class  CYINodeAdapter
 Provides an interface for supplying and reclaiming nodes to and from CYIStreamer. More...
 
class  CYIStreamer
 A class that implements a scene node streamer. More...
 
class  CYIViewAdapter
 A simplified CYINodeAdapter interface for automatically building views using provided view templates. More...
 
class  CYICreateDeleteViewRecycler
 
class  CYIListView
 A class that provides streaming functionality in a scrolling view. More...
 
class  CYIPooledViewRecycler
 
class  IYIViewRecycler