
This is a custom switch button made with Android Jetpack Compose. It uses the Canvas to draw the thumb and track. The main problem with the default switch is that you cannot change the size, padding, animation duration, etc… So, I designed it using the Canvas API.
The Jetpack Compose custom switch takes multiple parameters. They are the Canvas scale factor, width and height of the switch, checked and unchecked track colors, thumb color, and the gap between thumb and track. You can adjust them according to your requirements.
To animate the thumb, I’m using the animateFloatAsState() method. It moves the thumb from left to right when it is ON and right to left when it is OFF. You can adjust the animation delay. drawRoundRect() is used to draw the track and drawCircle() is used to draw the thumb.
Modifier.pointerInput() is used to detect the tap gestures on the Canvas. Inside this block, the switch state will be toggled. There is also a text below the Canvas that shows the ON and OFF values.
Related:
Final output:
If the video isn’t working, watch it on the YouTube.
Helpful links to understand the code:
- Jetpack Compose drawRoundRect
- Exploring Canvas with Examples
- animateFloatAsState
- 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.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: http://semicolonspace.com/semicolonspace-license/
For more designs with source code,
visit: http://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
) {
Switch1()
}
}
}
}
}
}
@Composable
fun Switch1(
scale: Float = 2f,
width: Dp = 36.dp,
height: Dp = 20.dp,
checkedTrackColor: Color = Color(0xFF35898F),
uncheckedTrackColor: Color = Color(0xFFe0e0e0),
thumbColor: Color = Color.White,
gapBetweenThumbAndTrackEdge: Dp = 4.dp
) {
val switchON = remember {
mutableStateOf(true)
}
val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge
// To move the 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())
)
// Thumb
drawCircle(
color = thumbColor,
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")
}