Closed12

JetLagged コードリーディング

maxfie1dmaxfie1d
  • Custom layout のサンプルコード
  • 睡眠のグラフが Custom layout と Canvas(Modifier#drawWithCache) で実装されている

maxfie1dmaxfie1d
  • グラフの実装は TimeGraph.kt にある
maxfie1dmaxfie1d

レイアウトに関係する 3つの Composable を Layout の contents に渡している

    Layout(
        contents = listOf(hoursHeader, dayLabels, bars),
        modifier = modifier.padding(bottom = 32.dp)
    ) {
maxfie1dmaxfie1d

ヘッダー部分と dayLabel 部分は普通に measure して placeable に変換する

        val hoursHeaderPlaceable = hoursHeaderMeasurables.first().measure(constraints)

        val dayLabelPlaceables = dayLabelMeasurables.map { measurable ->
            val placeable = measurable.measure(constraints)
            placeable
        }
maxfie1dmaxfie1d
  • ParantDataModifier を使って、 duration: Intoffset: Int を渡している
    • duration: bar の長さが何時間分かの割合 (0.0~1.0)
    • offset: bar の先端が何時間分ずれているかの割合 (0.0~1.0)
class TimeGraphParentData(
    val duration: Float,
    val offset: Float,
) : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?) = this@TimeGraphParentData
}
  • start: LocalDateTimeend: LocalDateTimehours: List<Int> から TimeGraphParentData を作る
fun Modifier.timeGraphBar(
        start: LocalDateTime,
        end: LocalDateTime,
        hours: List<Int>,
    ): Modifier {
        val earliestTime = LocalTime.of(hours.first(), 0)
        val durationInHours = ChronoUnit.MINUTES.between(start, end) / 60f
        val durationFromEarliestToStartInHours =
            ChronoUnit.MINUTES.between(earliestTime, start.toLocalTime()) / 60f
        // we add extra half of an hour as hour label text is visually centered in its slot
        val offsetInHours = durationFromEarliestToStartInHours + 0.5f
        return then(
            TimeGraphParentData(
                duration = durationInHours / hours.size,
                offset = offsetInHours / hours.size
            )
        )
    }
maxfie1dmaxfie1d
  • measurable.parentDataで TimeGraphParentData を取得する
  • TimeGraphParentData を使って 幅を計算し、計測する
  • totalHeight に bar の高さを加算していく
        val barPlaceables = barMeasureables.map { measurable ->
            val barParentData = measurable.parentData as TimeGraphParentData
            val barWidth = (barParentData.duration * hoursHeaderPlaceable.width).roundToInt()

            val barPlaceable = measurable.measure(
                constraints.copy(
                    minWidth = barWidth,
                    maxWidth = barWidth
                )
            )
            totalHeight += barPlaceable.height
            barPlaceable
        }
maxfie1dmaxfie1d
  • hoursHeaderPlaceable を dayLabel の右端かつ上端に配置する
  • bar と dayLabel は1対1対応なので、セットで配置していく
layout(totalWidth, totalHeight) {
            val xPosition = dayLabelPlaceables.first().width
            var yPosition = hoursHeaderPlaceable.height

            hoursHeaderPlaceable.place(xPosition, 0)

            barPlaceables.forEachIndexed { index, barPlaceable ->
                val barParentData = barPlaceable.parentData as TimeGraphParentData
                val barOffset = (barParentData.offset * hoursHeaderPlaceable.width).roundToInt()

                barPlaceable.place(xPosition + barOffset, yPosition)
                // the label depend on the size of the bar content - so should use the same y
                val dayLabelPlaceable = dayLabelPlaceables[index]
                dayLabelPlaceable.place(x = 0, y = yPosition)

                yPosition += barPlaceable.height
            }
        }
maxfie1dmaxfie1d

ParentDataModifier

maxfie1dmaxfie1d

ParentDataModifier は、親の Layout にデータを提供するための Modifier です。
ParentDataModifier の Density.modifyParentData() で返したデータが IntrinsicMeasurable.parentData に格納されます。

  • ParentDataModifier では Any? 型のデータを渡せる
interface ParentDataModifier : Modifier.Element {
    /**
     * Provides a parentData, given the [parentData] already provided through the modifier's chain.
     */
    fun Density.modifyParentData(parentData: Any?): Any?
}
このスクラップは2023/04/10にクローズされました