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 } ) } }
レンジの開始点と終了点が同じになる時、つまみが重なってしまいます。重なったつまみのドラッグは、対象を間違えることがあります。操作性を損なう結果になるので注意してください。
関連記事: