Jetpack compose offers a lot of ready-to-use material design components to speed up and simplify UI development. However, available composables cover the basic usage, these do not perfectly reflect the components that can be seen in the material guidelines. This article will explain how to customize the Slider component from Compose API to mimic the Weather Card Example from Material Guidelines.
In order to get as much out of this article as possible, some basic knowledge of Jetpack Compose is required. If you have not been familiar with Jetpack Compose yet or your knowledge about it is small, I recommend this set of courses, called Compose pathway.
Disclaimer, The Jetpack Compose library at the time of writing this article(June 2021) is in the beta stage. API should not change, but some small changes in functionality may occur. Keep that in mind.
Compose the Card
I will not go into details of how to position all the elements in the card, but the below graph shows the position of
Composable components names.
If you would like to check the exact implementation, here is the GIT repo with a fully working example. The final result, without a slider:
Composable form of slider looks a bit different from what you have seen at material.io.
As you can see, this slider has dots at each discrete step, instead of lines and at every second step, there is a label. Let‘s take a look at the slider implementation
What can we customize here? Value range, size via modifier, colors, number of steps. That is pretty good, but there are no parameters to customize labels or ticks(graphic equivalent are points for each step). We need to build our own
Composable component that will change the shape of the tick and add labels.
If you check how the circle ticks are being created for Slider, you will find this code (Slider -> SliderImpl -> Track -> Canvas):
As we can see, those points are drawn on canvas, lets use the same approach for vertical ticks. First, a quick reminder of how to draw on the android canvas. Values on the x-axis (horizontal) increase in the right direction and values on the y-axis (vertical) increase from top to bottom of the view.
The above illustration shows how these ticks should be drawn. These will have the constant distance and start/end padding, marked in blue. You may ask why we are not starting from (0,0). Starting from point 0 on the X axis, we won’t be able to correctly draw the thickness of the first line, because the coordinate describing the lines refers to its center, not the beginning, so starting to draw at point (0,0) we would draw a half-line.
Now, we can connect this function with the slider. For this I will use the box component with the option of aligning the components to center.
Hide the original points(ticks)
The vertical lines looks good. These are positioned in right place, center vertically, with good thickness. Now, we need to get rid of original slider points. In
@Composable Slider function, there is parameter named colors of type SliderColors. To override this, we need to create
@Composable function that extends SliderColors.
Using Color.Transparent as a tickColors, the points will disappear. The result with changed colors of thumb and track looks as shown below:
The last missing item in our card design are labels. We could use the Row with Text functions, but manage all the spacers or padding feel very hacky to me, so I choose to continue work with a Canvas. However, there is a small issue with a
@Composable Canvas. It do not provide any function for drawing text. There is a feature request for that functionality on google issue tracker. I encourage you to star it and follow -> Issue tracker.
For now, we can use
drawText function from android.graphics.Canvas, which can be easily accessed from our
As you can see on the above snippet, the text is drawn for every second tick and it is positioned at the bottom of the canvas.
Composing all of this together results in this view:
React on slider change
The UI part is done, but it is not over yet. We need to update the base of the measurement on the slider position.
To make things a bit more readable, let’s divide our card into two separate
@Composable functions. First, called
MeasurementView that will represent all of the components, without the slider and it will receive the parameter of
WeatherItem class that holds all the results for one hour.
The second function will be a
ForecastSlider that will take as parameters: list of dates, value(position of thumb), and caller function: onValueChange.
Let’s go over step by step what functions were used to connect the two views:
mySelectionis a mutable field that uses rememberSaveable, so the
@Composablewill remember about its state even on configuration changes
itemis an immutable field that also uses remember, but in lambda, there is another function called, derivedStateOf. This means, that whenever one of the parameters (mySelection, list) will change, the item will be recalculated. More about it here.
The final result
Thanks for reading this post. The full code can be found HERE.
Thanks to Damian Petla for the review and tips.
Happy coding everyone 💻 👋