
This is a Jetpack Compose custom switch. It is made with Canvas API to draw the track and thumb. It doesn’t contain any text inside the switch. You can adjust its size and color. I have added onClick event on the canvas. The state will be toggled between ON and OFF.
The default Jetpack Compose Switch API doesn’t offer many customizations. You can’t change the size of the thumb and track, adjust the thumb’s animation duration, and the gap between them. So, I made this custom switch using the Canvas API. The composable accepts the parameters like width, height, stroke width, checkedColor, unCheckedColor, and the gap between the thumb and the track. You can adjust them according to your requirements.
I have set animation on the track. Whenever the state of the switch is changed, the track will be moved with a smooth animation. Thanks to animateFloatAsState() API. You can adjust the animation duration and other specifications according to your requirements.
Coming to design, the track’s background color is the same as the layout color. I have added border color to it by using the drawRoundRect() function. The thumb is in a circle shape. Its color is the same as the track’s border color. It is made with drawCircle() function. It displays the switch state using the Text() composable.
Jetpack Compose Canvas API has pointerInput modifier. We can detect tap gestures using it. When the user taps on the Canvas (switch actually), the state of the switch will be toggled. When the switch is ON, the checkedColors will be applied and when the switch is OFF, unCheckedColors will be applied. The thumb’s position along the x-axis is changed with a smooth animation.
Android Jetpack Compose is not supported in the older versions of the Android Studio IDE. You need to download the latest version to use the code in your projects.
Related:
Final output:
If the video isn’t working, watch it on the YouTube.
Helpful links to understand the code:
- Jetpack Compose Canvas
- animateFloatAsState with Examples
- drawCircle() with Examples
- Android Material Switch
Here are the Gradle files used in the project.
Here is the Source Code:
MainActivity.kt:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/*
You can use the following code for commercial purposes with some restrictions.
Read the full license here: https://semicolonspace.com/semicolonspace-license/
For more designs with source code,
visit: https://semicolonspace.com/jetpack-compose-samples/
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Switch2()
}
}
}
}
}
}
@Composable
fun Switch2(
scale: Float = 2f,
width: Dp = 36.dp,
height: Dp = 20.dp,
strokeWidth: Dp = 2.dp,
checkedTrackColor: Color = Color(0xFF35898F),
uncheckedTrackColor: Color = Color(0xFFe0e0e0),
gapBetweenThumbAndTrackEdge: Dp = 4.dp
) {
val switchON = remember {
mutableStateOf(true) // Initially the switch is ON
}
val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge
// To move thumb, we need to calculate the position (along x axis)
val animatePosition = animateFloatAsState(
targetValue = if (switchON.value)
with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() }
else
with(LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() }
)
Canvas(
modifier = Modifier
.size(width = width, height = height)
.scale(scale = scale)
.pointerInput(Unit) {
detectTapGestures(
onTap = {
// This is called when the user taps on the canvas
switchON.value = !switchON.value
}
)
}
) {
// Track
drawRoundRect(
color = if (switchON.value) checkedTrackColor else uncheckedTrackColor,
cornerRadius = CornerRadius(x = 10.dp.toPx(), y = 10.dp.toPx()),
style = Stroke(width = strokeWidth.toPx())
)
// Thumb
drawCircle(
color = if (switchON.value) checkedTrackColor else uncheckedTrackColor,
radius = thumbRadius.toPx(),
center = Offset(
x = animatePosition.value,
y = size.height / 2
)
)
}
Spacer(modifier = Modifier.height(18.dp))
Text(text = if (switchON.value) "ON" else "OFF")
}
Thank you for nice explanation.
Look at the helpful links.