Closed12
JetLagged コードリーディング
- Custom layout のサンプルコード
- 睡眠のグラフが Custom layout と Canvas(Modifier#drawWithCache) で実装されている
- グラフの実装は
TimeGraph.kt
にある
レイアウトに関係する 3つの Composable を Layout の contents に渡している
Layout(
contents = listOf(hoursHeader, dayLabels, bars),
modifier = modifier.padding(bottom = 32.dp)
) {
ヘッダー部分と dayLabel 部分は普通に measure して placeable に変換する
val hoursHeaderPlaceable = hoursHeaderMeasurables.first().measure(constraints)
val dayLabelPlaceables = dayLabelMeasurables.map { measurable ->
val placeable = measurable.measure(constraints)
placeable
}
- ParantDataModifier を使って、
duration: Int
とoffset: 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: LocalDateTime
とend: LocalDateTime
とhours: 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
)
)
}
-
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
}
- 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
}
}
ParentDataModifier
- やんざむさんのブログ
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にクローズされました