Kotlin

Kotlin Multiplatform – Float Action Button with Menu Buttons

Create Scaffold with the following FAB settings:

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        },
        topBar = {
            TopAppBar(
                windowInsets = AppBarDefaults.topAppBarWindowInsets,
                title = { Text(text = "app bar title") },
                navigationIcon = {
                    IconButton(onClick = { onClick() }) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = "Back"
                        )
                    }
                }
            )
        },
        floatingActionButton = {
            MultiFloatingButton(
                multiFloatingState = multiFloatingState,
                onMultiFabStateChange = {
                    multiFloatingState = it
                },
                items = items,
                hostState = snackbarHostState
            )

        }
    )

Create FAB Menu items and additional variables with state:

var multiFloatingState by remember {
        mutableStateOf(MultiFloatingState.Colapsed)
    }

    val items = listOf(
        MinFabItem(
            icon = Icons.Filled.Add,
            label = "Attachment",
            identifier = Identifier.Attachment.name
        ),
        MinFabItem(
            icon = Icons.Filled.Home,
            label = "Home",
            identifier = Identifier.Home.name
        ),
        MinFabItem(
            icon = Icons.Filled.Cloud,
            label = "Cloud",
            identifier = Identifier.Cloud.name
        )
    )

    val snackbarHostState = remember { SnackbarHostState() }

Create Composable functions for FAB:

enum class MultiFloatingState {
    Expanded,
    Colapsed
}

enum class Identifier {
    Attachment,
    Home,
    Cloud
}

@Composable
fun MultiFloatingButton(
    multiFloatingState: MultiFloatingState,
    onMultiFabStateChange: (MultiFloatingState) -> Unit,
    items: List<MinFabItem>,
    hostState: SnackbarHostState
) {
    val transition = updateTransition(targetState = multiFloatingState, label = "transition")
    val rotate by transition.animateFloat(label = "rotate") {
        if (it == MultiFloatingState.Expanded) 315f else 0f;
    }

    val fabScale by transition.animateFloat(label = "FabScale") {
        if (it == MultiFloatingState.Expanded) 56f else 0f
    }

    val alpha by transition.animateFloat(
        label = "alhpa",
        transitionSpec = { tween(durationMillis = 50) }) {
        if (it == MultiFloatingState.Expanded) 1f else 0f
    }

    val textShadow by transition.animateDp(
        label = "textShadow",
        transitionSpec = { tween(durationMillis = 50) }) {
        if (it == MultiFloatingState.Expanded) 2.dp else 0.dp
    }

    val scope = rememberCoroutineScope()

    Column(
        horizontalAlignment = Alignment.End
    ) {
        if (transition.currentState == MultiFloatingState.Expanded) {
            items.forEach {
                MinFab(
                    item = it,
                    onItemMinFabItemClick = { minFabItem ->
                        when (minFabItem.identifier) {
                            Identifier.Attachment.name -> {
                                scope.launch {
                                    val result = hostState
                                        .showSnackbar(
                                            message = "Attachment",
                                            actionLabel = "Action",
                                            // Defaults to SnackbarDuration.Short
                                            duration = SnackbarDuration.Indefinite
                                        )
                                    when (result) {
                                        SnackbarResult.ActionPerformed -> {
                                            /* Handle snackbar action performed */
                                        }

                                        SnackbarResult.Dismissed -> {
                                            /* Handle snackbar dismissed */
                                        }
                                    }
                                }
                            }

                            Identifier.Home.name -> {
                                scope.launch {
                                    val result = hostState
                                        .showSnackbar(
                                            message = "Home",
                                            actionLabel = "Action",
                                            // Defaults to SnackbarDuration.Short
                                            duration = SnackbarDuration.Indefinite
                                        )
                                    when (result) {
                                        SnackbarResult.ActionPerformed -> {
                                            /* Handle snackbar action performed */
                                        }

                                        SnackbarResult.Dismissed -> {
                                            /* Handle snackbar dismissed */
                                        }
                                    }
                                }
                            }

                            Identifier.Cloud.name -> {

                            }
                        }
                    },
                    alpha = alpha,
                    textShadow = textShadow,
                    fabScale = fabScale
                )

                Spacer(modifier = Modifier.size(16.dp))
            }
        }

        FloatingActionButton(onClick = {
            onMultiFabStateChange(
                if (transition.currentState == MultiFloatingState.Expanded) {
                    MultiFloatingState.Colapsed
                } else {
                    MultiFloatingState.Expanded
                }
            )
        }) {
            Icon(
                imageVector = Icons.Filled.Add,
                contentDescription = null,
                modifier = Modifier.rotate(rotate)
            )
        }

    }
}

class MinFabItem(
    val icon: ImageVector,
    val label: String,
    val identifier: String
)

@Composable
fun MinFab(
    item: MinFabItem,
    alpha: Float,
    textShadow: Dp,
    fabScale: Float,
    showLabel: Boolean = true,
    onItemMinFabItemClick: (MinFabItem) -> Unit
) {

    val buttonColor = MaterialTheme.colors.secondary
    val shadow = Color.Black.copy(.5f)
    val painter = rememberVectorPainter(image = item.icon)

    Row() {
        if (showLabel) {
            Text(
                item.label,
                fontSize = 12.sp,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.alpha(
                    animateFloatAsState(targetValue = alpha, animationSpec = tween(50)).value
                )
                    .shadow(textShadow)
                    .background(MaterialTheme.colors.background)
                    .padding(start = 6.dp, end = 6.dp, top = 4.dp)
            )
            Spacer(modifier = Modifier.size(16.dp))
        }
        Canvas(
            modifier = Modifier.size(42.dp).clickable(
                interactionSource = MutableInteractionSource(),
                onClick = {
                    onItemMinFabItemClick.invoke(item)
                },
                indication = rememberRipple(
                    bounded = false,
                    radius = 20.dp,
                    color = MaterialTheme.colors.onSurface
                )
            )
        ) {

            drawCircle(
                color = shadow,
                radius = fabScale,
                center = Offset(
                    center.x + 2f,
                    center.y + 2f
                )
            )

            drawCircle(
                color = buttonColor,
                radius = fabScale
            )

            translate(top = 25f, left = 25f) {
                with(painter) {
                    draw(painter.intrinsicSize, alpha = alpha)
                }
            }
        }

    }
}
Hi, I’m Vlad

Leave a Reply

Your email address will not be published. Required fields are marked *