Skip to content

Commit a96014d

Browse files
committed
feat(Slider): add basic SliderLayout to position slider elements
1 parent ec76916 commit a96014d

File tree

7 files changed

+208
-34
lines changed

7 files changed

+208
-34
lines changed

demo/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ kotlin {
4343
}
4444

4545
dependencies {
46-
46+
implementation(project(":slider"))
4747
implementation(libs.androidx.core.ktx)
4848
implementation(libs.androidx.lifecycle.runtime.ktx)
4949
implementation(libs.androidx.activity.compose)

demo/src/main/java/io/monstarlab/mosaic/MainActivity.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import androidx.activity.enableEdgeToEdge
77
import androidx.compose.foundation.layout.fillMaxSize
88
import androidx.compose.foundation.layout.padding
99
import androidx.compose.material3.Scaffold
10+
import androidx.compose.material3.Slider
1011
import androidx.compose.material3.Text
1112
import androidx.compose.runtime.Composable
1213
import androidx.compose.ui.Modifier
1314
import androidx.compose.ui.tooling.preview.Preview
15+
import io.monstarlab.mosaic.features.SliderDemo
1416
import io.monstarlab.mosaic.ui.theme.MosaicTheme
1517

1618
class MainActivity : ComponentActivity() {
@@ -19,12 +21,7 @@ class MainActivity : ComponentActivity() {
1921
enableEdgeToEdge()
2022
setContent {
2123
MosaicTheme {
22-
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
23-
Greeting(
24-
name = "Android",
25-
modifier = Modifier.padding(innerPadding),
26-
)
27-
}
24+
SliderDemo()
2825
}
2926
}
3027
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.monstarlab.mosaic.features
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.foundation.shape.RoundedCornerShape
6+
import androidx.compose.material3.Scaffold
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.draw.clip
10+
import androidx.compose.ui.graphics.Color
11+
import androidx.compose.ui.tooling.preview.Preview
12+
import androidx.compose.ui.unit.dp
13+
import io.monstarlab.mosaic.slider.Slider
14+
import io.monstarlab.mosaic.slider.SliderColors
15+
16+
@Composable
17+
fun SliderDemo() = Scaffold(modifier = Modifier) {
18+
Column(modifier = Modifier.padding(it).padding(16.dp)) {
19+
Slider(
20+
value = 0.5f,
21+
onValueChange = {},
22+
colors = SliderColors(Color.Red),
23+
modifier = Modifier.clip(RoundedCornerShape(2.dp))
24+
)
25+
}
26+
}
27+
28+
@Preview
29+
@Composable
30+
private fun PreviewSliderDemo() {
31+
SliderDemo()
32+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.monstarlab.mosaic.slider
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.BoxScope
7+
import androidx.compose.foundation.layout.BoxWithConstraints
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.size
12+
import androidx.compose.foundation.shape.CircleShape
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.graphics.Color
17+
import androidx.compose.ui.layout.Layout
18+
import androidx.compose.ui.tooling.preview.Preview
19+
20+
@Composable
21+
public fun Slider(
22+
value: Float,
23+
onValueChange: (Float) -> Unit,
24+
colors: SliderColors,
25+
modifier: Modifier = Modifier,
26+
thumb: @Composable () -> Unit = { DefaultSliderThumb(colors = colors) }
27+
) {
28+
29+
SliderLayout(
30+
progress = value,
31+
thumb = thumb,
32+
track = {
33+
SliderTrack(
34+
modifier = modifier,
35+
progress = value,
36+
colors = colors,
37+
)
38+
}
39+
)
40+
}
41+
42+
@Composable
43+
internal fun DefaultSliderThumb(colors: SliderColors) {
44+
Box(
45+
modifier = Modifier
46+
.size(SliderDefaults.ThumbSize)
47+
.background(
48+
color = colors.active,
49+
shape = CircleShape
50+
)
51+
)
52+
}
53+
54+
@Preview
55+
@Composable
56+
private fun PreviewSlider() {
57+
Slider(
58+
value = 0.5f,
59+
onValueChange = {},
60+
colors = SliderColors(Color.Yellow)
61+
)
62+
}

slider/src/main/java/io/monstarlab/mosaic/slider/SliderDefaults.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import androidx.compose.ui.unit.dp
44

55
internal object SliderDefaults {
66
val TrackHeight = 4.dp
7+
val ThumbSize = 16.dp
78
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.monstarlab.mosaic.slider
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.foundation.layout.height
7+
import androidx.compose.foundation.layout.size
8+
import androidx.compose.foundation.shape.CircleShape
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.graphics.Color
12+
import androidx.compose.ui.layout.Layout
13+
import androidx.compose.ui.layout.layoutId
14+
import androidx.compose.ui.tooling.preview.Preview
15+
import androidx.compose.ui.unit.dp
16+
import kotlin.math.max
17+
import kotlin.math.roundToInt
18+
19+
@Composable
20+
internal fun SliderLayout(
21+
progress: Float,
22+
track: @Composable () -> Unit,
23+
thumb: @Composable () -> Unit
24+
) {
25+
Layout(
26+
content = {
27+
Box(modifier = Modifier.layoutId(SliderLayoutElements.Track)) {
28+
track()
29+
}
30+
Box(modifier = Modifier.layoutId(SliderLayoutElements.Thumb)) {
31+
thumb()
32+
}
33+
}) { mesuarables, constraints ->
34+
35+
val trackPlaceable = mesuarables
36+
.first { it.layoutId == SliderLayoutElements.Track }
37+
.measure(constraints)
38+
39+
val thumbPlaceable = mesuarables
40+
.first { it.layoutId == SliderLayoutElements.Thumb }
41+
.measure(constraints)
42+
43+
val sliderHeight = max(trackPlaceable.height, thumbPlaceable.height)
44+
val sliderWidth = trackPlaceable.width
45+
layout(sliderWidth, sliderHeight) {
46+
val trackY = sliderHeight / 2 - trackPlaceable.height / 2
47+
val thumbY = sliderHeight / 2 - thumbPlaceable.height / 2
48+
val thumbX = (sliderWidth * progress).roundToInt() - thumbPlaceable.width / 2
49+
trackPlaceable.place(0, trackY)
50+
thumbPlaceable.place(thumbX, thumbY)
51+
}
52+
}
53+
}
54+
55+
56+
internal enum class SliderLayoutElements {
57+
Track, Thumb
58+
}
59+
60+
61+
@Preview
62+
@Composable
63+
private fun PreviewSliderLayout() {
64+
SliderLayout(
65+
progress = 0.5f,
66+
track = {
67+
Box(
68+
modifier = Modifier
69+
.background(Color.Yellow)
70+
.fillMaxWidth()
71+
.height(20.dp)
72+
)
73+
},
74+
thumb = {
75+
Box(modifier = Modifier
76+
.background(Color.Blue, shape = CircleShape)
77+
.size(50.dp))
78+
})
79+
}
Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.monstarlab.mosaic.slider
22

33
import androidx.compose.foundation.Canvas
4+
import androidx.compose.foundation.layout.Box
45
import androidx.compose.foundation.layout.fillMaxWidth
5-
import androidx.compose.foundation.layout.height
6+
import androidx.compose.foundation.layout.heightIn
67
import androidx.compose.runtime.Composable
78
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.draw.drawBehind
810
import androidx.compose.ui.geometry.Offset
911
import androidx.compose.ui.geometry.Size
1012
import androidx.compose.ui.graphics.Color
@@ -19,34 +21,35 @@ internal fun SliderTrack(
1921
) {
2022

2123
check(progress in 0f..1f) { "Invalid progress value should be between 0 and 1" }
22-
Canvas(
24+
Box(
2325
modifier = modifier
2426
.fillMaxWidth()
25-
.height(SliderDefaults.TrackHeight)
26-
) {
27-
val activeRectWidth = size.width * progress
28-
drawRect(
29-
color = colors.active,
30-
topLeft = Offset.Zero,
31-
size = Size(activeRectWidth, size.height)
32-
)
27+
.heightIn(min = SliderDefaults.TrackHeight)
28+
.drawBehind {
29+
val activeRectWidth = size.width * progress
30+
drawRect(
31+
color = colors.active,
32+
topLeft = Offset.Zero,
33+
size = Size(activeRectWidth, size.height)
34+
)
3335

34-
drawRect(
35-
color = colors.inactive,
36-
topLeft = Offset(activeRectWidth, 0f),
37-
size = Size(size.width - activeRectWidth, size.height)
38-
)
36+
drawRect(
37+
color = colors.inactive,
38+
topLeft = Offset(activeRectWidth, 0f),
39+
size = Size(size.width - activeRectWidth, size.height)
40+
)
3941

40-
if (!disabledRange.isEmpty()) {
41-
val disabledStart = size.width * disabledRange.start
42-
val disabledEnd = size.width * disabledRange.endInclusive
43-
drawRect(
44-
color = colors.disabled,
45-
topLeft = Offset(size.width * disabledRange.start, 0f),
46-
size = Size(disabledEnd - disabledStart, size.height)
47-
)
48-
}
49-
}
42+
if (!disabledRange.isEmpty()) {
43+
val disabledStart = size.width * disabledRange.start
44+
val disabledEnd = size.width * disabledRange.endInclusive
45+
drawRect(
46+
color = colors.disabled,
47+
topLeft = Offset(size.width * disabledRange.start, 0f),
48+
size = Size(disabledEnd - disabledStart, size.height)
49+
)
50+
}
51+
}
52+
)
5053
}
5154

5255

@@ -55,7 +58,7 @@ internal fun SliderTrack(
5558
private fun PreviewSliderTrack() {
5659
SliderTrack(
5760
progress = 0.5f,
58-
colors = SliderColors(Color.Red),
59-
disabledRange = 0.8f..1f
61+
colors = SliderColors(Color.Yellow),
62+
disabledRange = 0.8f..1f,
6063
)
6164
}

0 commit comments

Comments
 (0)