Custom UI Components using Jetpack Compose Canvas based on NeoPoP Design — Part 1 (Radio Button)
--
An exploration into Jetpack compose animation and canvas to create custom UI components
What we would be creating
The UI components created will be based on NeoPop UI from Cred. It will be used as a reference and not everything would be exactly the same.
I would be writing separate articles for each component.
This is the first one for Radio Button.
Project Creation
Let’s start by creating a new Android project using the Empty Compose Activity
template.
Dependencies
Added the dependencies used here as some are alpha/beta and they might be changed in the future.
Now that project is set up, let's start with the Radio Button.
Expectation
- A default state UI:
2. A selected state UI:
3. UI to be created using Jetpack compose canvas
4. Animations when switching states
5. Cancellable Animations
As we would be animating between the selection and default states, I am creating a single composable with parameters to define the state at any given point.
NeoPopRadioButtonView
- innerRadius: The radius of the white inner circle
- outerWidth: Width of the black circle
At any given point in time innerRadius + outerWidth
will be equal to the radius of the radio button. Hence size = (innerRadius + outerWidth) * 2
.
Things to notice
toPx()
extension function fromDp
can be used forDp to Px
conversion.- The order of drawing inside the Canvas is important. Thought in this case, both the circles are not overlapping, if there are overlapping drawings on canvas the latest drawing will be drawn on top of the previous one.
- When drawing a circle with stroke style, the radius should be the center as the stroke would be on both sides of the circle equally.
Now that we have completed the UI using Canvas, we can proceed to use them along with Animations.
NeoPopRadioButton
The code seems like a lot 😅, but most of it is only for making smoother animation.
The borderWidthValue
and selectionWidthValue
is written like this as a safety measure in case the passed borderWidth
or selectionWidth
is too large.
The division by 10
& 2.dp
are random values.
.value
is used to convertDp
toFloat
.- The code inside
LaunchedEffect
is triggered every time the radio button is clicked. - Simultaneous animations: Both the outer circle and inner should animate in parallel, so we have to use separate
launch{}
to start them at the same time.
Note
For cancellable animation, we have to use the coroutine scope of the LaunchedEffect
. If you use coroutine from rememberCoroutineScope()
, it gets canceled only when the composable is disposed.
Usage
You can add more customization as required.
Thanks for reading.
Please comment with your valuable feedback.
It would help me to improvise.
If you like this article and you would be interested to know about the other UI components given at the starting please 👏👏👏 .