Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のSliderについて、構成や使用方法などをまとめます。
※環境:Android Studio Hedgehog | 2023.1.1 Patch 2
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.material3:material3 1.1.1
目次
UIの概要
Sliderはつまみ(Thumb)をドラッグして、バー(Track)上を左右に移動させることで、値の入力を行うUIです。
@Preview_mdpi
@Composable
fun SliderBasic() {
Row(
modifier = Modifier.size(320.dp, 120.dp).padding(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
var _value by remember { mutableFloatStateOf(0.5f) }
Text(text = "Value [ %5.2f ]".format(_value))
Slider(
value = _value,
onValueChange = { _value = it }
)
}
}
関数の引数
Sliderの状態(装飾、演出など)は、引数により指定できます。
@Composable
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
/*@IntRange(from = 0)*/
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) { ... }
@Composable
@ExperimentalMaterial3Api
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
thumb: @Composable (SliderPositions) -> Unit = { ... },
track: @Composable (SliderPositions) -> Unit = { ... },
/*@IntRange(from = 0)*/
steps: Int = 0,
) { ... }
| 引数 | 概要 | |
|---|---|---|
| value | Float | 現在の値 |
| onValueChange | (Float) -> Unit | 値の変更時に実行されるラムダ式 (引数で値の取得が可能) |
| modifier | Modifier | UI全般のCompose修飾子 |
| enable | Boolean | UI有効フラグ(true:有効 / false:無効) |
| valueRange | ClosedFloatingPointRange | 値の範囲(最小...最大) 例:-0.5...0.5 |
| steps | Int | ノッチ(目盛り)数(0の時はノッチ無し) 例:steps←5:min-1-2-3-4-5-max |
| onValueChangeFinished | (( ) -> Unit)? | ドラッグ⇒リリース時に実行されるラムダ式 |
| colors | SliderColors | スライダーの配色 ・つまみ(thumbColor) ・アクティブのバー(activeTrackColor) ・非アクティブのバー(inactiveTrackColor) |
| interactionSource | MutableInteractionSource | ユーザが行ったUI操作の観測と報告 |
| thumb | @Composable (SliderPositions) -> Unit | スライダーのつまみ (Composable関数のコマンドブロック) |
| track | @Composable (SliderPositions) -> Unit | スライダーのバー (Composable関数のコマンドブロック) |
関数の構成
Sliderは2つのBoxを重ね合わせた構成になっています。
一つのBoxはスライダーのバー(SliderDefaults.Track)を格納し、もう一つはスライダーのつまみ(SliderDefaults.Thumb)を格納します。
これらが、Layout(Composable関数)により配置されています。

@Composable
fun Slider( ... ) {
require(steps >= 0) { "steps should be >= 0" }
SliderImpl(
... ,
thumb = {
SliderDefaults.Thumb(
interactionSource = interactionSource,
colors = colors,
enabled = enabled
)
},
track = { sliderPositions ->
SliderDefaults.Track(
colors = colors,
enabled = enabled,
sliderPositions = sliderPositions
)
}
)
}
@Composable
private fun SliderImpl(
...
thumb: @Composable (SliderPositions) -> Unit,
track: @Composable (SliderPositions) -> Unit
) {
...
Layout(
{
Box(modifier = Modifier.layoutId(SliderComponents.THUMB)) { thumb(sliderPositions) }
Box(modifier = Modifier.layoutId(SliderComponents.TRACK)) { track(sliderPositions) }
},
...
) { measurables, constraints ->
...
layout(
sliderWidth,
sliderHeight
) {
trackPlaceable.placeRelative( // 先に来るものが下、Track:バー
trackOffsetX,
trackOffsetY
)
thumbPlaceable.placeRelative( // 後に来るものが上、Thumb:つまみ
thumbOffsetX,
thumbOffsetY
)
}
}
}
ノッチ(目盛り)の表示
引数:stepsでノッチ(目盛り)を表示することが出来ます。
ノッチを表示することで、つまみの位置はノッチの位置に限定されます。つまり、ノッチ間の値は入力できません。
「steps = 4」とした場合、ノッチは4つです。その結果、レンジ(引数:valueRangeで指定、最小と最大の幅)は5等分されることになります。
@Preview_mdpi
@Composable
fun StepsSample() {
Row(
modifier = Modifier
.size(320.dp, 120.dp)
.padding(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
var _value by remember { mutableFloatStateOf(0.4f) }
Text(text = "Value [ %5.2f ]".format(_value))
Slider(
value = _value,
onValueChange = { _value = it },
steps = 4
)
}
}

ちなみに、デフォルトは「steps = 0」です。ノッチ無しを意味します。
配色の変更
引数:colorsで配色を変更できます。
Sliderはマテリアルデザインに準拠しています。ですので、デフォルト(SliderDefaults.colors()の出力)はテーマで指定されたマテリアルデザインの配色になります。
@Preview_mdpi
@Composable
fun ColorsSample() {
Row(
modifier = Modifier
.size(320.dp, 120.dp)
.padding(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
var _value by remember { mutableFloatStateOf(0.4f) }
Text(text = "Value [ %2.1f ]".format(_value))
Slider(
value = _value,
onValueChange = { _value = it },
steps = 4,
colors = SliderDefaults.colors(
thumbColor = Color(0xffff0000), // つまみの色
activeTrackColor = Color(0xffffb0b0), // バーの色
activeTickColor = Color(0xff000000), // ノッチの色
inactiveTrackColor = Color(0xffe0e0e0),
inactiveTickColor = Color(0xff808080)
)
)
}
}

つまみとバーの変更(タイプ2を使用)
Slider(タイプ2)は引数:thumbでつまみを変更できます。また、引数:trackでバーを変更できます。
引数:thumbとtrackの値はComposable関数のコマンドブロック(スロット)です。この中で、つまみとバーのUIコンポーネントを記述します。
タイプ2は「アノテーション:@ExperimentalMaterial3Api」付きです。
@ExperimentalMaterial3Apiは「実験的なAPI」を意味します。
ですので、今後のリリースで変更や削除の可能性があります。その点を理解した上で使用する必要があります。
使用に際して、上記の点についての許諾(アノテーション:@OptIn)が求められます。
Thumb(つまみ)の作成
Thumb(つまみ)のUIコンポーネント(Composable関数)を作成します。
※コマンドブロックの引数:sliderPositionsは未使用
@Composable
fun HeartThumb(
sliderPositions: SliderPositions, // 未使用
modifier: Modifier = Modifier,
colors: HeartSliderColors = HeartSliderColors.create(),
enabled: Boolean = true,
thumbSize: DpSize = DpSize(24.dp, 24.dp)
) {
val _color = if(enabled) colors.thumbColor else colors.disableThumbColor
Image( // ハートアイコン:baseline_favorite_black_24
painter = painterResource(R.drawable.baseline_favorite_black_24),
contentDescription = null,
modifier = modifier.size(thumbSize),
colorFilter = ColorFilter.tint(_color, BlendMode.SrcIn)
)
}
Track(バー)の作成
Track(バー)のUIコンポーネント(Composable関数)を作成します。
コマンドブロックの引数:sliderPositionsはつまみの位置情報を持っています。これを使って、バーの配色(Active/Inactive)を変えることができます。ただし、レンジ(最小と最大の幅)を1.0fに正規化した時の係数(0.5の時、レンジの中間)なので、注意してください。
@Composable
fun HeartTrack(
sliderPositions: SliderPositions,
modifier: Modifier = Modifier,
colors: HeartSliderColors = HeartSliderColors.create(),
enabled: Boolean = true,
) {
val inactiveTrackColor = colors.trackColor(enabled, active = false)
val activeTrackColor = colors.trackColor(enabled, active = true)
val inactiveTickColor = colors.tickColor(enabled, active = false)
val activeTickColor = colors.tickColor(enabled, active = true)
Canvas(
modifier
.fillMaxWidth()
.height(10.dp)
// .graphicsLayer(alpha = 0.99f)
.graphicsLayer(
compositingStrategy = CompositingStrategy.Offscreen
)
) {
val _width = size.width
val _height = size.height
// スライドバー(Inactive)
val _inactivePath = Path().apply {
moveTo(0.0f, _height)
lineTo(_width, 0.0f)
lineTo(_width, _height)
lineTo(0.0f, _height)
}
drawPath(path = _inactivePath, color = inactiveTrackColor.value)
// スライドバー(Active)
val _activePosition = _width * sliderPositions.activeRange.endInclusive
val _activeSize = Size(_activePosition, _height)
drawRect(color = activeTrackColor.value, size = _activeSize, blendMode = BlendMode.SrcIn)
// 目盛り
sliderPositions.tickFractions.groupBy {
it > sliderPositions.activeRange.endInclusive
}.forEach { (key, list) ->
list.forEach {
val _color = if(key) inactiveTickColor.value
else activeTickColor.value
drawLine(
color = _color,
start = Offset(_width * it, 0.0f),
end = Offset(_width * it, _height),
blendMode = BlendMode.SrcIn
)
}
}
}
}
ThumbとTrackの指定
引数:thumbとtrackへ作成したUIコンポーネントを指定します。
@OptIn(ExperimentalMaterial3Api::class)
@Preview_mdpi
@Composable
fun ThumbTrackSample() {
Row(
modifier = Modifier
.size(320.dp, 120.dp)
.padding(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
var _value by remember { mutableFloatStateOf(0.5f) }
Text(text = "Value [ %5.2f ]".format(_value))
Slider( // タイプ2
value = _value,
onValueChange = { _value = it },
steps = 5,
thumb = { sliderPositions ->
HeartThumb(sliderPositions = sliderPositions) },
track = { sliderPositions ->
HeartTrack(sliderPositions = sliderPositions) }
}
)
}
}
![]()
派生タイプ:RangeSlider
RangeSliderは2つのつまみ(StartThumb,EndThumb)をドラッグして、バー(Track)上を左右に移動させることで、値の範囲の入力を行うUIです。
引数はSliderとほぼ同じです。
ただし、引数:valueがClosedFloatingPointRange型なっているので、範囲演算子「..」を用いた範囲値を入力します。
fun RangeSlider(
value: ClosedFloatingPointRange<Float>,
onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
/*@IntRange(from = 0)*/
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors()
) { ... }
以下は、サンプルです。
@Preview_mdpi
@Composable
fun RangeSliderBasic() {
Column(modifier = Modifier.size(320.dp, 120.dp).padding(20.dp)) {
var _range by remember { mutableStateOf(0.2f..0.8f) }
val _start = _range.start
val _end = _range.endInclusive
Text(text = "Range [ %5.2f~%5.2f ]".format(_start, _end))
RangeSlider(
value = _range,
onValueChange = { range -> _range = range }
)
}
}
レンジの開始点と終了点が同じになる時、つまみが重なってしまいます。重なったつまみのドラッグは、対象を間違えることがあります。操作性を損なう結果になるので注意してください。
関連記事:
