📚
Create a Custom Arrangement with Compose
object Arrangementの中身(例)
interface Vertical {
/**
* Spacing that should be added between any two adjacent layout children.
*/
val spacing get() = 0.dp
/**
* Vertically places the layout children.
*
* @param totalSize Available space that can be occupied by the children, in pixels.
* @param sizes An array of sizes of all children, in pixels.
* @param outPositions An array of the size of [sizes] that returns the calculated
* positions relative to the top, in pixels.
*/
fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
)
}
totalSizeのなかに、outPositionsの要素の順番にsizesのサイズを使って詰め込んでいけば良さそう。
カスタム定義を考える前に、プリセットの実装がどうなっているのか見てみる。
Top
val Top = object : Vertical {
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
) = placeLeftOrTop(sizes, outPositions, reverseInput = false)
override fun toString() = "Arrangement#Top"
}
internal fun placeLeftOrTop(size: IntArray, outPosition: IntArray, reverseInput: Boolean) {
var current = 0
size.forEachIndexed(reverseInput) { index, it ->
outPosition[index] = current
current += it
}
}
上から順番(outPosition[index])に、sizeのサイズを割り当てている。
SpaceEvenly
val SpaceEvenly = object : HorizontalOrVertical {
override val spacing = 0.dp
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
layoutDirection: LayoutDirection,
outPositions: IntArray
) = if (layoutDirection == LayoutDirection.Ltr) {
placeSpaceEvenly(totalSize, sizes, outPositions, reverseInput = false)
} else {
placeSpaceEvenly(totalSize, sizes, outPositions, reverseInput = true)
}
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
) = placeSpaceEvenly(totalSize, sizes, outPositions, reverseInput = false)
override fun toString() = "Arrangement#SpaceEvenly"
}
internal fun placeSpaceEvenly(
totalSize: Int,
size: IntArray,
outPosition: IntArray,
reverseInput: Boolean
) {
val consumedSize = size.fold(0) { a, b -> a + b }
val gapSize = (totalSize - consumedSize).toFloat() / (size.size + 1)
var current = gapSize
size.forEachIndexed(reverseInput) { index, it ->
outPosition[index] = current.roundToInt()
current += it.toFloat() + gapSize
}
}
consumedSize:各要素が消費するサイズの合計
gapSize:要素間の間隔
SpaceEvenlyは両端を含めた各要素間の間隔が等しくなるように配置するので、size.size + 1になっている。あとは、間隔と要素自身を足したサイズをoutPosition[index]割り当てている。
これらを踏まえて、N個のitemがあるとき、N-1個まではArrangement.Topと同様の配置をして、最初からN-1個のセットと最後の1つはArrangement.SpaceBetweenと同じ配置をするArrangementを作ってみる。
object TopAndSpaceBetweenArrangement : Arrangement.Vertical {
override fun Density.arrange(totalSize: Int, sizes: IntArray, outPositions: IntArray) {
if (sizes.isEmpty()) return
var currentY = 0
// 最後のアイテムを除くアイテムをArrangement.Topと同じように配置
for (i in 0 until sizes.size - 1) {
outPositions[i] = currentY
currentY += sizes[i]
}
val consumedSize = sizes.fold(0) { a, b -> a + b }
// 間隔は1つだけ
val gapSize = (totalSize - consumedSize).toFloat()
// 最後のアイテムをArrangement.BetWeenと同じように配置
outPositions[sizes.size - 1] = (currentY + gapSize).roundToInt()
}
}
Discussion