In this post I will show you how to create a custom number picker in Android Studio using Jetpack Compose.
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.
I use lazyListState as the state to hold the value returned from scrolling.
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 elements I have needed custom elements for, I used a Canvas so that was my starting point. The issue was the scrolling and stopping at a position.
Fix
This is why I ended up with using the LazyColumn. It was faster to just use the built-in FlingBehavior for the scrolling stop then to attempt to create one.
Note
This is not just for numbers. This can be used for any type you can feed into it. At time of creation, I needed it to show numbers.
Leave a Reply