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.