Horizontal Scrolling and Measurement
This page covers the older Java codegen-based Sections API. If you're using the Kotlin Lazy Collection API, refer to the layout docs for Lazy Collection.
Vertical scrolling lists usually fill the width of the screen so it's easy to measure them with a fixed size. Working with horizontal scrolling lists (h-scroll) is not always so trivial because different height settings have different performance implications.
To see how to create a h-scroll, using a RecyclerCollectionComponent
, refer to the 'Horizontal Lists' section of the Adding and Adapting the RecyclerCollection to your App page.
The code samples contained within this page are extracted from the h-scroll height codelab.
Setting the height of a horizontal RecyclerCollectionComponentβ
You can set the height of a horizontally scrolling RecyclerCollectionComponent
with the following methods:
- Fixed height method - a fixed height is set on the H-Scroll component. This is the most performant method and is recommended where possible.
- Unknown height method - height is not known when the Component is created so let the h-scroll set its height to the height of the first item.
- Dynamic height method - lets the h-scroll dynamically change its height to fit the tallest item. This is the least performant method.
Each of these methods is detailed in the following sub-sections.
Fixed height methodβ
Using this method, a fixed height is set on the h-Scroll Component. The client knows the height of the h-scroll when it creates it, but it cannot be changed once the h-scroll gets measured.
Children of this h-scroll are measured with, at most, the height of the h-scroll. Taller children will be clipped, and smaller children will be positioned at the start of the h-scroll.
In Litho, this is the most performant (efficient) way to set the height of a h-scroll. It's recommended to use this option whenever possible.
To use this preferred method, set the height through the height
prop on your RecyclerCollectionComponent
:
@LayoutSpec
object FixedHeightHscrollComponentSpec {
@OnCreateLayout
fun onCreateLayout(c: ComponentContext, @Prop colors: List<Int>): Component {
return RecyclerCollectionComponent.create(c)
.recyclerConfiguration(ListRecyclerConfiguration.create().orientation(OrientationHelper.HORIZONTAL).build())
.section(DataDiffSection.create<Int>(SectionContext(c))
.data(colors)
.renderEventHandler(FixedHeightHscrollComponent.onRender(c))
.build())
.heightDip(150f)
.build()
}
@OnEvent(RenderEvent::class)
fun onRender(c: ComponentContext, @FromEvent model: Int): RenderInfo {
return ComponentRenderInfo.create()
.component(SolidColor.create(c).color(model).heightDip(100f).widthDip(100f))
.build()
}
}
The code produces the structure shown in the following image.
Notice the grey background is the actual bounds of the h-scroll, and the children have smaller heights.
Unknown height methodβ
Using this method, the height is unknown when the Component is created. The height is determined by measuring the first child of the h-scroll and setting that as the height of the h-scroll.
This measurement occurs only when the h-scroll is first measured, after which the height cannot be changed. All other children heights will be measured with, at most, the height of the h-scroll and position at the start of the h-scroll.
To enable this, instead of passing a height
prop on the RecyclerCollectionComponent
, specify it through the canMeasureRecycler
prop it should measure itself:
@LayoutSpec
object MeasureFirstItemForHeightHscrollComponentSpec {
@OnCreateLayout
fun onCreateLayout(c: ComponentContext, @Prop colors: List<Int>): Component {
return RecyclerCollectionComponent.create(c)
.recyclerConfiguration(ListRecyclerConfiguration.create().orientation(OrientationHelper.HORIZONTAL).build())
.section(DataDiffSection.create<Int>(SectionContext(c))
.data(colors)
.renderEventHandler(MeasureFirstItemForHeightHscrollComponent.onRender(c))
.build())
.canMeasureRecycler(true)
.build()
}
@OnEvent(RenderEvent::class)
fun onRender(c: ComponentContext, @FromEvent model: Int, @FromEvent index: Int): RenderInfo {
if (index == 0) {
return ComponentRenderInfo.create()
.component(SolidColor.create(c).color(model).heightDip(100f).widthDip(100f))
.build()
}
if (index == 1) {
return ComponentRenderInfo.create()
component(SolidColor.create(c).color(model).heightDip(200f).widthDip(100f))
.build()
}
return ComponentRenderInfo.create()
.component(SolidColor.create(c).color(model).heightDip(50f).widthDip(100f))
.build()
}
}
The code produces the structure shown in the following image.
In this case, the first child has a height of 100dip; the second child has a height of 200dip, but it's cropped to fit the size of the h-scroll as determined by the first child. Once measured like this, the height cannot be changed. The grey background represents the bounds of the h-scroll.
If you don't set a non-zero height on the RecyclerCollectionComponent
and canMeasureRecycler
is not enabled, your RecyclerCollectionComponent will end up with a height of zero.
Dynamic height methodβ
This is the least performant (inefficient) method. Enabling this option should be done only if absolutely needed and should especially be avoided for lists with infinite scrolling.
Using this method, the h-scroll dynamically changes its height to fit the tallest item.
H-Scrolls can be configured to support items of different height or remeasuring of the height if the height of the children could change after the initial measurement.
In this case:
- The initial height of the h-scroll is determined by the height of the tallest child.
- If a child wants to expand to become taller than the current height of the h-scroll, the h-scroll will be remeasured with the new height of the child. Other items will not be remeasured.
- If the child with the biggest height collapses, then the h-scroll will again determine what its height should be by remeasuring all the items.
Measuring all the children to determine the tallest comes with a high-performance cost, especially for infinite loading h-scrolls when the height needs to be remeasured every time new items are inserted.
If you must use this method, you can pass your own RecyclerConfiguration to the RecyclerCollectionComponent
and enable hasDynamicItemHeight
on the RecyclerBinderConfigurationer that is used to create the RecyclerConfiguration
, as shown in the following code:
RecyclerCollectionComponent.create(c)
.recyclerConfiguration(ListRecyclerConfiguration.create()
.recyclerBinderConfiguration(RecyclerBinderConfiguration.create().hasDynamicItemHeight(true).build())
.orientation(OrientationHelper.HORIZONTAL).build())
.section(DataDiffSection.create<Int>(SectionContext(c))
.data(colors)
.renderEventHandler(DynamicHeightHscrollComponent.onRender(c))
.build())
.canMeasureRecycler(true)
.build()
In the short video below, you can see that the h-scroll will remeasure to always adjust height to accommodate the tallest item, but it won't collapse to fit a smaller maximum height.