Android Compose Create An ElevatedCard With A Slide In Menu

You are currently viewing Android Compose Create An ElevatedCard With A Slide In Menu

In this post I will show you a sample Composable of an ElevatedCard with a slide in menu in Android Compose.

When the user clicks the ElevatedCard, the menu will slide in from the right with a list of actionable icons that they can click.

This could be a useful tool to add a navigation method in your app where you can pass a specific value held in the ElevatedCard via a LazyColumn to a series of buttons for processing.

First I will give you a blank canvas with minimal content then I will show you a simple example of using it with callbacks to alter the main content.

Code

Below is the empty content with a few simple elements so you can see something in the main content of the custom element we are creating.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            var action by remember { mutableStateOf("") }

            SampleElevatedCardActionsTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    ElevatedCardActions(
                        modifier = Modifier.padding(innerPadding),
                        { MainContent(action) },
                        action,
                        callback = {
                            action = it
                            when(it){
                                "Share" -> {
                                    // share icon button action
                                }
                                "Close" -> {
                                    // close icon button action
                                }
                                "Add" -> {
                                    // add icon button action
                                }
                            }
                        }
                    )
                }
            }
        }
    }
}

@Composable
fun ElevatedCardActions(
    modifier: Modifier,
    content: @Composable () -> Unit,
    items: String,
    callback: (String) -> Unit
) {

    var openMenu by remember { mutableStateOf(false) }

    var boxScope by remember { mutableStateOf(Size.Zero) }
    val containerWidth = boxScope.width * 0.25f
    val animatedWidth by animateFloatAsState(
        targetValue = if (!openMenu) 0f else -containerWidth,
        animationSpec = tween(500)
    )

    ElevatedCard(
        modifier = modifier
            .padding(10.dp)
            .shadow(5.dp, RoundedCornerShape(10.dp))
            .clickable {
                openMenu = !openMenu
            }
    ) {
        BoxWithConstraints {
            boxScope = Size(this.maxWidth.value, this.maxHeight.value)
            Box(modifier = Modifier
                .height(IntrinsicSize.Min)
            ) {
                Column(
                    modifier = Modifier.fillMaxWidth().padding(15.dp)
                ) {
                    content()
                }
                Box(
                    modifier = Modifier
                        .width(containerWidth.dp)
                        .fillMaxHeight()
                        .offset(x = animatedWidth.dp + boxScope.width.dp)
                        .background(Color.Green)
                        .drawBehind {
                            if (openMenu)
                                drawLine(
                                    Color.Black,
                                    Offset(0f, 0f),
                                    Offset(0f, size.height),
                                    Stroke.HairlineWidth
                                )
                        }
                        .padding(5.dp),
                    contentAlignment = Alignment.CenterEnd
                ) {
                    IconButton(modifier = Modifier.align(Alignment.TopCenter), onClick = {
                        callback("Share")
                    }) {
                        Icon(
                            imageVector = Icons.Default.Share,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                    IconButton(modifier = Modifier.align(Alignment.Center), onClick = {
                        callback("Close")
                    }) {
                        Icon(
                            imageVector = Icons.Default.Close,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                    IconButton(modifier = Modifier.align(Alignment.BottomCenter), onClick = {
                        callback("Add")
                    }) {
                        Icon(
                            imageVector = Icons.Default.Add,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                }
            }
        }
    }
}


@Composable
fun MainContent(
    action: String
){

    Row() {
        Text(text = "Title", style = MaterialTheme.typography.titleLarge)
        Image(
            painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
    }
    Text(text = "Sample Content Goes Here")
    Spacer(modifier = Modifier.padding(10.dp))
    Text(text = "Clicked...$action")
}

BONUS

Now I will show you a way you can implement with more functionality utilizing different callbacks.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            var clickItem by remember { mutableStateOf("") }
            var items by remember { mutableStateOf(0) }

            SampleElevatedCardActionsTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    ElevatedCardActions(
                        modifier = Modifier.padding(innerPadding),
                        { MainContent(clickItem, items) },
                        items,
                        callBackItemCount = {
                            items = it
                        },
                        callbackClickItem = {
                            clickItem = it
                        }
                    )
                }
            }
        }
    }
}

@Composable
fun ElevatedCardActions(
    modifier: Modifier,
    content: @Composable () -> Unit,
    items: Int,
    callBackItemCount: (Int) -> Unit,
    callbackClickItem: (String) -> Unit
) {

    var openMenu by remember { mutableStateOf(false) }

    var boxScope by remember { mutableStateOf(Size.Zero) }
    val containerWidth = boxScope.width * 0.25f
    val animatedWidth by animateFloatAsState(
        targetValue = if (!openMenu) 0f else -containerWidth,
        animationSpec = tween(500)
    )

    ElevatedCard(
        modifier = modifier
            .padding(10.dp)
            .shadow(5.dp, RoundedCornerShape(10.dp))
            .clickable {
                openMenu = !openMenu
            }
    ) {
        BoxWithConstraints {
            boxScope = Size(this.maxWidth.value, this.maxHeight.value)
            Box(modifier = Modifier
                .height(IntrinsicSize.Min)
            ) {
                Column(
                    modifier = Modifier.fillMaxWidth().padding(15.dp)
                ) {
                    content()
                }
                Box(
                    modifier = Modifier
                        .width(containerWidth.dp)
                        .fillMaxHeight()
                        .offset(x = animatedWidth.dp + boxScope.width.dp)
                        .background(Color.Green)
                        .drawBehind {
                            if (openMenu)
                                drawLine(
                                    Color.Black,
                                    Offset(0f, 0f),
                                    Offset(0f, size.height),
                                    Stroke.HairlineWidth
                                )
                        }
                        .padding(5.dp),
                    contentAlignment = Alignment.CenterEnd
                ) {
                    IconButton(modifier = Modifier.align(Alignment.TopCenter), onClick = {
                        callbackClickItem("Share Icon")
                    }) {
                        Icon(
                            imageVector = Icons.Default.Share,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                    IconButton(modifier = Modifier.align(Alignment.Center), onClick = {
                        callbackClickItem("Close Icon")
                        callBackItemCount(items - 1)
                    }) {
                        Icon(
                            imageVector = Icons.Default.Close,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                    IconButton(modifier = Modifier.align(Alignment.BottomCenter), onClick = {
                        callbackClickItem("Add Icon")
                        callBackItemCount(items + 1)
                    }) {
                        Icon(
                            imageVector = Icons.Default.Add,
                            contentDescription = null,
                            modifier = Modifier.padding(5.dp)
                        )
                    }
                }
            }
        }
    }
}

@Composable
fun MainContent(
    clickItem: String,
    items: Int
){

    Row() {
        Text(text = "Title", style = MaterialTheme.typography.titleLarge)
        Image(
            painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
    }
    Text(text = "Sample Content Goes Here")
    Spacer(modifier = Modifier.padding(10.dp))
    Text(text = "Clicked...${clickItem}")
    for(i in 0 .. items){
        Text(text = "Clicked...${i}")
    }
}

Hope this was useful.

Leave a Reply