In this post I will show you how to create a custom Android number picker in Compose.
Android Number Picker
Process
The foundation for this custom element is the LazyColumn. The LazyColumn has enough of the built in attributes that we can use to create this with a few changes. Such as the scroll state which is held in a variable named lazyListState.
Note
This Android number picker composable is not just for numbers. This can be used for any input type you can feed into it. At time of creation, I needed it to show numbers.
Code
Below is the sample code. You may need to change or update it based on your project use case / needs.
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SampleNumberPickerTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center
) {
val lazyListState = rememberLazyListState(initialFirstVisibleItemIndex = 0)
var values = remember {
(0..20).map { it.toString() }
}
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
NumberPicker(
modifier = Modifier,
list = values,
fontSize = 32.sp,
state = lazyListState,
flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState)
)
}
Row() {
Text(text = "${values[lazyListState.firstVisibleItemIndex+1]}")
}
}
}
}
}
}
}
@Composable
fun NumberPicker(
modifier: Modifier = Modifier,
list: List<String>,
fontSize: TextUnit,
state: LazyListState,
flingBehavior: FlingBehavior
) {
Box(
modifier = modifier
)
{
// add 1 for size and offset
val shownCount = 3 + 1
val height = with(LocalDensity.current) {
fontSize.toDp()
}
LazyColumn(
state = state,
flingBehavior = flingBehavior,
modifier = Modifier
.padding(5.dp)
.height(height * shownCount)
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
.drawWithContent {
drawContent()
drawRect(
brush = Brush.verticalGradient(
0f to Color.Transparent,
0.5f to Color.Black,
1f to Color.Transparent
),
blendMode = BlendMode.DstIn
)
}) {
items(list.size) { index ->
//if first item then add end empty slot in the beginning so the first item can be scrolled to center of screen
if(index == 0){
Text(
modifier = Modifier.padding(5.dp),
text = " ", fontSize = fontSize
)
} else {
Text(
modifier = Modifier.padding(5.dp),
text = "${list[index]}", fontSize = fontSize
)
}
//if end of list then create a empty slot so the last item can scroll to center of screen
if(index == list.size-1){
Text(
modifier = Modifier.padding(5.dp),
text = " ", fontSize = fontSize
)
}
}
}
}
}Problem I Encountered While Creating
I initially creating this from a Canvas instead of LazyColumn. The last custom composable elements I have created used a Canvas so that was my starting point. The issue was the scrolling and stopping at a position smoothly.
Fix
This is why I ended up with using the LazyColumn. It was faster to just use the built in FlingBehavior for the scrolling to stop then to attempt to create one.

