🎉
Jetpack Composeで自動スクロールするテキストの作成
Layout()
を使って文字列のサイズに合わせてアニメーションさせる
@Composable
fun AutoScrollText(
modifier: Modifier = Modifier,
text: String,
style: TextStyle = LocalTextStyle.current,
) {
val transition = rememberInfiniteTransition(
label = "AutoScrollTextRepeater"
)
val textWidth = remember { mutableIntStateOf(0) }
val layoutWidth = remember { mutableIntStateOf(0) }
val transitionWidth = textWidth.intValue
val animateX: Int by transition.animateValue(
initialValue = layoutWidth.intValue,
targetValue = -transitionWidth,
typeConverter = Int.VectorConverter,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = transitionWidth * 15,
delayMillis = 300,
easing = LinearEasing
),
)
)
Layout(
modifier = modifier,
content = {
Text(
text = text,
style = style,
maxLines = 1,
)
},
measurePolicy = { measurable, constraints ->
val textMeasurable = measurable.first()
layoutWidth.intValue = constraints.maxWidth
val placeable = textMeasurable.measure(
// layoutのサイズに関わらず全てを表示する
constraints.copy(maxWidth = Int.MAX_VALUE)
)
val fixedWidth = placeable.width
textWidth.intValue = fixedWidth
layout(layoutWidth.intValue, placeable.height) {
placeable.place(animateX, 0)
}
}
)
}
@Preview
@Composable
private fun AutoScrollTextPreview0() {
Column(
modifier = Modifier
.width(100.dp)
) {
AutoScrollText(
text = "abcdefghijklmnopqrstuvwxyz",
)
}
}
バリエーションを作ってみる
sealed class AutoScrollType(open val delayMillis: Long) {
// 指定秒数かけてscrollする
data class Duration(val millis: Long, override val delayMillis: Long = 0) : AutoScrollType(delayMillis)
// 文字列の長さに関わらず一定の速度で移動する
data class PerCharacterLength(val millis: Long, override val delayMillis: Long = 0) : AutoScrollType(delayMillis)
}
@Composable
fun AutoScrollText(
modifier: Modifier = Modifier,
text: String,
style: TextStyle = LocalTextStyle.current,
autoScrollType: AutoScrollType = AutoScrollType.Duration(5000),
startVisualize: Boolean = true,
) {
val textWidth = remember { mutableIntStateOf(0) }
val layoutWidth = remember { mutableIntStateOf(0) }
val transitionWidth =
if (startVisualize) textWidth.intValue - layoutWidth.intValue
else textWidth.intValue
val animatedOffsetX = remember { Animatable(0f) }
LaunchedEffect(transitionWidth) {
while (
// layoutサイズに収まっていれば何もしない
transitionWidth > 0
) {
animatedOffsetX.snapTo(if (startVisualize) 0f else layoutWidth.intValue.toFloat())
delay(autoScrollType.delayMillis)
animatedOffsetX.animateTo(
targetValue = -transitionWidth.toFloat(),
animationSpec = tween(
durationMillis = when (autoScrollType) {
is AutoScrollType.Duration -> autoScrollType.millis
is AutoScrollType.PerCharacterLength -> transitionWidth * autoScrollType.millis
}.toInt(),
easing = LinearEasing
)
)
delay(autoScrollType.delayMillis)
}
}
Layout(
modifier = modifier,
content = {
Text(
text = text,
style = style,
maxLines = 1
)
},
measurePolicy = { measurable, constraints ->
val textMeasurable = measurable.first()
layoutWidth.intValue = constraints.maxWidth
val placeable = textMeasurable.measure(
// layoutのサイズに関わらず全てを表示する
constraints.copy(maxWidth = Int.MAX_VALUE)
)
// textのサイズを取得する
textWidth.intValue = placeable.width
layout(layoutWidth.intValue, placeable.height) {
placeable.place(animatedOffsetX.value.toInt(), 0)
}
}
)
}
@Preview
@Composable
private fun AutoScrollTextPreview() {
Column(
modifier = Modifier
.width(100.dp)
) {
AutoScrollText(
text = "abcdefghijklmnopqrstuvwxyz",
autoScrollType = AutoScrollType.Duration(3000, 500),
startVisualize = true
)
AutoScrollText(
text = "abcdefghijklmnopqrstuvwxyz",
autoScrollType = AutoScrollType.Duration(3000),
startVisualize = false
)
AutoScrollText(
text = "abcdefghijklmnopqrstuvwxyz",
autoScrollType = AutoScrollType.PerCharacterLength(20),
startVisualize = true
)
AutoScrollText(
text = "abcdefghijklmnopqrstuvwxyz",
autoScrollType = AutoScrollType.PerCharacterLength(10),
startVisualize = false
)
}
}
Discussion