📚

Create a Custom Arrangement with Compose

2023/09/13に公開

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()
    }
}
株式会社THIRD エンジニアブログ

Discussion