Create a Custom Android Number Picker in Compose
Create a custom scrollable number picker in Android Compose using LazyColumn.

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.