2021年5月14日金曜日

inline class および value class で kotlinx.serialization (JSON) が動く組み合わせ

inline class および value class で kotlinx.serialization (JSON) が動く組み合わせを調べてみた


inline class のときのコード @Serializable inline class ItemId(val value: String) @Serializable data class Item(val id: ItemId, val name: String) fun main() { val item = Item(ItemId("1"), "Android") val json = Json.encodeToString(item) println(json) println(Json.decodeFromString<Item>(json)) } value class のときのコード @Serializable @JvmInline value class ItemId(val value: String) @Serializable data class Item(val id: ItemId, val name: String) fun main() { val item = Item(ItemId("1"), "Android") val json = Json.encodeToString(item) println(json) println(Json.decodeFromString<Item>(json)) }

Kotlin: 14.32, kotlinx.serialization: 1.1.0 + inline class

ビルドエラーになる

e: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during file facade code generation

Kotlin: 1.4.32, kotlinx.serialization: 1.2.1 + inline class

ビルドエラーになる

e: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during file facade code generation

Kotlin: 1.4.32, kotlinx.serialization: 1.1.0 + value class

@JvmInline が無いのでビルドエラーになる

Kotlin: 1.4.32, kotlinx.serialization: 1.2.1 + value class

@JvmInline が無いのでビルドエラーになる

Kotlin: 1.5.0, kotlinx.serialization: 1.1.0 + inline class

動く

Kotlin: 1.5.0, kotlinx.serialization: 1.2.1 + inline class

動く

Kotlin: 1.5.0, kotlinx.serialization: 1.1.0 + value class

動く

Kotlin: 1.5.0, kotlinx.serialization: 1.2.1 + inline class

動く


Kotlin を 1.5.0 にすれば kotlinx.serialization を 1.2 にしなくても inline class と value class 両方で動いた。


2021年5月9日日曜日

Jetpack Compose : Canvas Composable を使う

View の onDraw() で描画して Custom View を作る、というのを Compose でやりたいときは Canvas Composable を使います。

Canvas に渡す onDraw lamnda は Receiver が DrawScope になっています。
DrawScope からは描画エリアの大きさとして size: Size が取れます。

また、DrawScope は Density を継承しているので、Dp.toPx() とかも呼び出せます。 @Composable fun CircleProgress( progress: Int, modifier: Modifier, colorProgress: Color = MaterialTheme.colors.primary, colorBackground: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) .compositeOver(MaterialTheme.colors.surface), strokeWidth: Dp = 8.dp, ) { Canvas(modifier = modifier) { val stroke = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Round) val diameter = min(size.width, size.height) - stroke.width val topLeft = Offset((size.width - diameter) / 2, (size.height - diameter) / 2) val circleSize = Size(diameter, diameter) drawArc( color = colorBackground, startAngle = -90f, sweepAngle = 360f, useCenter = false, style = stroke, topLeft = topLeft, size = circleSize, ) drawArc( color = colorProgress, startAngle = -90f, sweepAngle = 360f / 100 * progress, useCenter = false, style = stroke, topLeft = topLeft, size = circleSize, ) } } @Preview @Composable fun CircleProgressPreview() { var progress by remember { mutableStateOf(0) } val animateProgress by animateIntAsState(targetValue = progress, animationSpec = tween()) Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { Button(onClick = { progress = Random.nextInt(0, 101) }) { Text("Change Progress") } Spacer(modifier = Modifier.height(16.dp)) CircleProgress( progress = animateProgress, modifier = Modifier.size(120.dp) ) } } animate**AsState などでアニメーションも簡単にできます。



Jetpack Compose で Material Design の ToggleButton を作ってみた

Material Design の ToogleButton (https://material.io/components/buttons#toggle-button) を Jetpack Compose で作ってみた。

https://github.com/yanzm/ComposeToggleButton

こんな感じのやつ。


選択の処理は Modifier.toggleable() を使えば OK。 @Composable fun IconToggleButton( imageVector: ImageVector, contentDescription: String?, checked: Boolean, onCheckedChange: (Boolean) -> Unit, enabled: Boolean = true ) { CompositionLocalProvider( LocalContentColor provides contentColor(enabled = enabled, checked = checked), ) { Box( contentAlignment = Alignment.Center, modifier = Modifier .toggleable( value = checked, onValueChange = onCheckedChange, role = Role.RadioButton, ) .size(48.dp) ) { Icon( imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(24.dp) ) } } }

枠線などの描画は Modifier.drawWithContent() でやったが、これが面倒だった〜(特にRTL対応)。 private fun Modifier.drawToggleButtonFrame( ... ): Modifier = this.drawWithContent { ... // draw checked border drawPath( path = ..., color = checkedBorderColor, style = Stroke(strokeWidth), ) drawContent() }