Skip to main content

ScrollView

ScrollView

ScrollView is a focus-aware container for one page-like view tree that is larger than its viewport. It renders real child views, then translates an internal scrollable group as focus changes or as web mouse/touch input scrolls the surface.

Use ScrollView for long detail screens, settings pages, and forms that span several viewport heights. Use Repeater for a short, bounded run of similar controls. For data-driven or frequently changing lists, rails, grids, EPGs, and season or episode lists, use CollectionView because it virtualizes and pools rows and cells.

Keep scrolling ownership at screen level

Do not embed ScrollView, ScrollContainer, or Repeater inside a CollectionView cell, custom cell, or supplementary/header view. Express that content as CollectionView rows and cells instead.

Example

ScrollerRig

Source: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/controls/ScrollerRig.ts
@view('ScrollerRig')
export class ScrollerRigView extends BaseExampleScreenView<ScrollerRigState> {

  protected override getViews(): ViewStruct<ViewState>[] {
    const isMobile = this.isMobileRigLayout();
    const contentWidth = isMobile ? this.getRigContentWidth() : 1920;
    const heroHeight = isMobile ? 220 : 500;
    const tabWidth = isMobile ? contentWidth : 1820;
    const sideSpacer = isMobile ? 20 : 200;
    const cellSize = isMobile ? Math.min(110, Math.round((contentWidth - sideSpacer - 40) / 3)) : 400;
    const sectionSpacer = isMobile ? 40 : 200;

    return [
      ScrollView([
        VGroup([
          Rectangle({ id: 'hero', color: '#00ff00', width: contentWidth, height: heroHeight }),
          Spacer({ height: isMobile ? 40 : 100 }),
          Rectangle({ id: 'tab', color: '#00fff0', width: tabWidth, height: 100 }),
          HGroup([
            Button({ text: 'Episodes' }),
            Button({ text: 'Extras' }),
            Button({ text: 'Details' }),
          ])
            .isInitialFocus()
            .id('tabBar'),
          Spacer({ height: sectionSpacer }),
          HGroup([
            Rectangle({ id: 'episodesList', color: '#ff0000', width: cellSize, height: cellSize, canReceiveFocus: true }),
            Spacer({ width: sideSpacer }),
            VGroup([
              HGroup([
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
              ]).id('collectionViewRow1'),
              HGroup([
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
              ]).id('collectionViewRow2'),
              HGroup([
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
                Rectangle({ color: '#ff00ff', width: cellSize, height: cellSize, canReceiveFocus: true }),
                Spacer({ width: 20 }),
              ]).id('collectionViewRow3')
            ]).itemSpacing(20)
              // .focusMap({
              //   left: 'episodesList'
              // })
            .id('collectionView')
          ])
        ])
      ])
        .viewportWidth(contentWidth)
        .translation([this.getRigLeftOffset(0), 0])
        .verticalAlignment(ScrollVerticalAlignment.Top)
        .horizontalAlignment(ScrollHorizontalAlignment.None)
      // .onFocusedChildChange((event: FocusChildChangeEvent) => {
      //   this.focusId = event.childId;
      //   console.info('SCROLLABLE CHILD FOCUS CHANGE focus id');
      // })
    ];
  }

}
Keep One Item Focused

Set .canReceiveFocus(true) on scrollable children and .isInitialFocus(true) on the first item you want focused. Auto-scroll follows the focused child.

Common API

  • viewportWidth and viewportHeight: explicit visible area. If omitted, ScrollView falls back to its own size, a clipping parent, calculated size, or the default design surface.
  • autoScroll: when true, focused child changes move the scroll position.
  • verticalAlignment: Top, Center, Bottom, Floating, None, Predefined, CustomPosition, or CustomCallback.
  • horizontalAlignment: Left, Center, Right, Floating, None, Predefined, CustomPosition, or CustomCallback.
  • customPositionX and customPositionY: offsets used by custom-position alignment.
  • customVerticalPositionCallback and customHorizontalPositionCallback: calculate positions from the focus-change event.
  • scrollToChild: state field that animates a specific child into view.
  • animationDuration: duration for focus-driven and programmatic scroll animations.

Programmatic Scrolling

const scrollView = this.getSubView<ScrollViewView>('settingsScroll');

scrollView?.scrollTo([0, -480], 250);
scrollView?.animateToChild(targetChild, 250);
scrollView?.reset();

scrollTo(position, duration) expects a scroll translation in design coordinates. animateToChild(child, duration) computes the translation needed to bring that child into view.

Mouse and Touch

On web, ScrollView exposes an external scroll API used by input adapters:

  • Mouse wheel calls into a scroll controller and updates the scroll offset.
  • Touch drag starts a drag session and applies momentum after release.
  • Scroll extents can be hard-stopped or overscrolled by the controller.
  • Browser coordinates are normalized into Hosanna design coordinates before hit testing.

Application code usually does not call setExternalScrollOffset; it should configure the ScrollView's viewport and focus behavior, then let adapters drive pointer scrolling.

When CollectionView is Better

CollectionView is not limited to catalog screens. It can also model detail screens and form-like flows when sections are data-driven or can grow. Its fragment-backed cells make presentation easier to reuse and update independently than bespoke nested scroll trees.

API Reference

ScrollView API
Talk to us