Measuring
Primitive should provide an implementation of LayoutBehavior interface to define how it should measure itself, based on the provided SizeConstraints.
The layout()
function returns a PrimitiveLayoutResult
object that contains the width and height of the content, and optionally any layout data.
There are a few of built-in LayoutBehavior
implementations provided for most common use cases:
- ExactSizeConstraintsLayoutBehavior - width and height are set based on the provided SizeConstraints.
- FillLayoutBehavior - width and height are set based on the SizeConstraints if the constraints are bounded, otherwise uses default values which can be provided via constructor.
- FixedSizeLayoutBehavior - width and height are set to values provided via constructor.
- AspectRatioLayoutBehavior - width and height are set according to an aspect ratio and SizeConstraints. It will respect the intrinsic size of the component being measured.
- EqualDimensionsLayoutBehavior - width and height are set respecting SizeConstraints and trying to keep width and height equal. This will only not guarantee equal width and height if the constraints don't allow for it.
There are utility methods defined as extension functions on the Size
object that can be used to compute appropriate Size
. It's also possible to access androidContext
in the layout()
method via LayoutScope.
layout()
method can be called on any thread, and has the following characteristics:
- it must not cause any side-effects
- it is guaranteed to be called at least once, and can be called multiple times
Measuring in practiceβ
In principle, there are two different ways that measuring can be implemented:
Mathematical calculations using SizeConstraints
β
SizeConstraints
is an object that provides the minimum and maximum width and height available for a Component. SizeConstraints
are provided by parent Component to a child component. A child Component should define its size within those constraints.
In practice, the SizeConstraints
object is passed as a parameter of the layout()
method and can be used to carry out mathematical calculations and compute the final measurements based on minWidth
, maxWidth
, minHeight
, and maxHeight
values, as shown in the following example code:
override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
return PrimitiveLayoutResult(
size =
if (!sizeConstraints.hasBoundedWidth && !sizeConstraints.hasBoundedHeight) {
Size(defaultSize, defaultSize)
} else {
Size.withEqualDimensions(sizeConstraints)
})
}
The actual computation logic that uses SizeConstraints
values is in Size.withEqualDimensions
helper function.
Creating a View, measuring it, and reading the measured sizesβ
Alternatively, a View can be created that represents the mount content then call the View.measure()
method on it directly. After View.measure()
completes, the measured width and height can be retrieved by calling View.getMeasuredWidth()
and View.getMeasuredHeight()
:
override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
// The height should be the measured height of EditText with relevant params
val editTextForMeasure: EditText =
AppCompatEditText(androidContext).apply {
setHint(hintText)
setText(initialText)
background =
getBackgroundOrDefault(
androidContext,
if (inputBackground === ColorDrawable(Color.TRANSPARENT)) background
else inputBackground)
}
editTextForMeasure.measure(sizeConstraints.toWidthSpec(), sizeConstraints.toHeightSpec())
val measuredWidth = max(sizeConstraints.minWidth, editTextForMeasure.measuredWidth)
val measuredHeight = max(sizeConstraints.minHeight, editTextForMeasure.measuredHeight)
// For width we always take all available space, or collapse to 0 if unspecified.
val width =
if (!sizeConstraints.hasBoundedWidth) 0 else min(sizeConstraints.maxWidth, measuredWidth)
return PrimitiveLayoutResult(width, measuredHeight)
}
When the width and height can be determined using mathematical calculations, it is preferred over creating a View because the calculations are much more lightweight.