Skip to main content

Using Layouts

A Layout keeps track of a View and its children and automatically arranges the children as sizes change. This happens (by default) whenever View's size changes, or one of its children has its bounds change. The View class also protects its layout property by default so it can encapsulate special handling if needed, but sub-classes are free to expose it.

This examples shows the use of

HorizontalFlowLayout, which wraps a View's children from left to right within its bounds.

import io.nacular.doodle.core.container import io.nacular.doodle.layout.HorizontalFlowLayout fun horizontalLayout() { //sampleStart val container = container {} container.layout = HorizontalFlowLayout() // Container exposes its layout //sampleEnd }
caution

Changes to a View's transform will not trigger layout.

Custom Layouts

Doodle comes with several useful layouts, including one based on constraints. But you can easily create custom Layouts by implementing the Layout interface or using the simpleLayout utility function.

import io.nacular.doodle.core.Layout import io.nacular.doodle.core.Layout.Companion.simpleLayout import io.nacular.doodle.core.PositionableContainer import io.nacular.doodle.geometry.Rectangle fun example() { //sampleStart class CustomLayout: Layout { override fun layout(container: PositionableContainer) { container.children.filter { it.visible }.forEach { child -> child.bounds = Rectangle(/*...*/) } } } // DSL for basic layout simpleLayout { container -> container.children.filter { it.visible }.forEach { child -> child.bounds = Rectangle(/*...*/) } } //sampleEnd }
tip

Layout works with PositionableContainer instead of View directly because the latter does not expose its children by design.

When layout triggers

Layouts are generally triggered whenever their container's size changes or a child of their container has a bounds change. But there are cases when this default behavior does not work as well. A good example is a Layout that uses a child's idealSize in positioning. Such a Layout won't be invoked when a child's idealSize changes, and will be out of date in some cases. The following demo shows this.

info

Moving the slider changes the ideal width of the blue boxes. But the container isn't updated because the Layout used does not indicate (via requiresLayout) it needs an updated when a View's SizePreferences change.

You can see that it is out of date by resizing the container after moving the slider.

This is why Doodle offers a Layout the chance to customize when they are invoked. In fact, Layouts are asked whether they want to respond to several potential triggers. These include size changes in the container, bounds and SizePreferences changes for children. The latter happens whenever minimumSize or idealSize are updated for a child. This way, a Layout can fine tune what triggers it.

The following shows how updating the Layout so it replies to requiresLayout for this scenario fixes the issue.

info

Notice that this Layout will actually ignore changes to the container's size! Layouts are free to do that if the container's size is irrelevant to the positioning of its children. This is very unlikely, but there might be cases where one dimension of size, maybe width or height is irrelevant. In which case the Layout can ignore updates if only that component changes.