visible true

技術的なメモを書く

Jetpack Compose (0.1.0-dev04) でSeekBarをスクラッチする

Jetpack ComposeにはSeekBarがないので、必要な場合は今の所自分で作ることになります。で作りました。0.1.0-dev04 での実装なので将来そのままでは動かなくなると思うのでご注意ください。

f:id:sys1yagi:20200209145316p:plain
Preview

使う

実際の動作はこんな感じになります

streamable.com

実装

Draggableを使って実装します。横棒とか丸は頑張って描画してます。 Draggableは値の範囲がfixedなので、横幅が動的(いわゆるmatch_parent)の場合利用が難しいです。そのためDraw関数とparentSizeを使って、widthをstateに持つみたいなことをやってます。

@Composable
private fun paint(): Paint {
    return Paint().apply {
        color = MaterialTheme.colors().primary
        isAntiAlias = true
    }
}

@Composable
fun SeekBar(
    @FloatRange(from = 0.0, to = 1.0) progress: Float,
    fixedWidth: Dp? = null,
    onChangeProgress: (Float) -> Unit
) {
    val squareSize = 32.dp
    val barHeight = 8.dp
    val fixedWidthPx = withDensity(ambientDensity()) { fixedWidth?.toPx()?.value }
    val (width, setWidth) = state {
        fixedWidthPx ?: 0f
    }
    if (width == 0f) {
        Container(
            modifier = LayoutWidth.Fill
        ) {
            Draw { _, parentSize ->
                val newWidth = parentSize.width.value
                if (newWidth != width) {
                    setWidth(newWidth)
                }
            }
        }
    } else {
        val squareSizePx = withDensity(ambientDensity()) { squareSize.toPx().value }

        val max = width - squareSizePx
        val min = 0.dp
        val (minPx, maxPx) = withDensity(ambientDensity()) {
            min.toPx().value to max
        }
        val position = animatedDragValue(maxPx * progress, minPx, maxPx)
        val paint = paint()

        Draggable(
            dragDirection = DragDirection.Horizontal,
            dragValue = position,
            onDragValueChangeRequested = {
                position.animatedFloat.snapTo(it)
                onChangeProgress(position.value / max)
            }
        ) {
            Container(
                modifier = fixedWidth?.let { LayoutWidth(it) } ?: LayoutWidth.Fill,
                alignment = Alignment.CenterLeft,
                height = squareSize
            ) {
                Stack {
                    Padding(
                        top = squareSize / 2 - barHeight / 2,
                        left = squareSize / 2,
                        right = squareSize / 2
                    ) {
                        ColoredRect(
                            Color.LightGray,
                            height = barHeight
                        )
                    }
                    Draw { canvas, _ ->
                        canvas.drawCircle(
                            Offset(position.value + squareSizePx / 2, squareSizePx / 2),
                            squareSizePx / 2,
                            paint
                        )
                    }
                }
            }
        }
    }
}

おわりに

ProgressBarなども横幅fixedなんでスクラッチしたり、わりとスクラッチが必要だけど、結構カスタムなコンポーネント作るのそんなに難しくないので、UIライブラリがどんどん出てくるかもなと思ったりします。β、RCが待ち遠しいですね。