Jetpack Compose:如何创建带有动画的多项FAB?

3

我正在尝试创建一个带有以下动画的多项浮动操作按钮:

我创建了一个多项浮动操作按钮,但无法实现预期的动画效果。

我有一个名为 FilterFabMenuButton 的可组合项,我将其显示为菜单项:

@Composable
fun FilterFabMenuButton(
    item: FilterFabMenuItem,
    onClick: (FilterFabMenuItem) -> Unit,
    modifier: Modifier = Modifier
) {

    FloatingActionButton(
        modifier = modifier,
        onClick = {
            onClick(item)
        },
        backgroundColor = colorResource(
            id = R.color.primary_color
        )
    ) {
        Icon(
            painter = painterResource(item.icon), contentDescription = null, tint = colorResource(
                id = R.color.white
            )
        )
    }
}


我有一个名为FilterFabMenuLabel的可组合部件,它是FilterFabMenuButton的标签:

FilterFabMenuLabel

@Composable
fun FilterFabMenuLabel(
    label: String,
    modifier: Modifier = Modifier
) {

    Surface(
        modifier = modifier,
        shape = RoundedCornerShape(6.dp),
        color = Color.Black.copy(alpha = 0.8f)
    ) {
        Text(
            text = label, color = Color.White,
            modifier = Modifier.padding(horizontal = 20.dp, vertical = 2.dp),
            fontSize = 14.sp,
            maxLines = 1
        )
    }

}


我有一个名为FilterFabMenuItem的可组合项,它是一个包含FilterFabMenuLabel和FilterFabMenuButton可组合项的行:

FilterFabMenuItem

@Composable
fun FilterFabMenuItem(
    menuItem: FilterFabMenuItem,
    onMenuItemClick: (FilterFabMenuItem) -> Unit,
    modifier: Modifier = Modifier
) {

    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(10.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {

        //label
        FilterFabMenuLabel(label = menuItem.label)

        //fab
        FilterFabMenuButton(item = menuItem, onClick = onMenuItemClick)

    }

}


我有一个名为FilterFabMenu的可组合对象,它是一个列,用于显示菜单项: < p > < code > FilterFabMenu

@Composable
fun FilterFabMenu(
    visible: Boolean,
    items: List<FilterFabMenuItem>,
    modifier: Modifier = Modifier
) {

    val enterTransition = remember {
        expandVertically(
            expandFrom = Alignment.Bottom,
            animationSpec = tween(150, easing = FastOutSlowInEasing)
        ) + fadeIn(
            initialAlpha = 0.3f,
            animationSpec = tween(150, easing = FastOutSlowInEasing)
        )
    }

    val exitTransition = remember {
        shrinkVertically(
            shrinkTowards = Alignment.Bottom,
            animationSpec = tween(150, easing = FastOutSlowInEasing)
        ) + fadeOut(
            animationSpec = tween(150, easing = FastOutSlowInEasing)
        )
    }


    AnimatedVisibility(visible = visible, enter = enterTransition, exit = exitTransition) {
        Column(
            modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.End,
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            items.forEach { menuItem ->
                FilterFabMenuItem(
                    menuItem = menuItem,
                    onMenuItemClick = {}
                )
            }
        }
    }
}


我有一个名为FilterFab的可组合件,它可以展开/折叠筛选菜单: FilterFab
@Composable
fun FilterFab(
    state: FilterFabState,
    rotation:Float,
    onClick: (FilterFabState) -> Unit,
    modifier: Modifier = Modifier
) {
       FloatingActionButton(
           modifier = modifier
               .rotate(rotation),
           elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp),
           onClick = {
               onClick(
                   if (state == FilterFabState.EXPANDED) {
                       FilterFabState.COLLAPSED
                   } else {
                       FilterFabState.EXPANDED
                   }
               )
           },
           backgroundColor = colorResource(
               R.color.primary_color
           ),
           shape = CircleShape
       ) {
           Icon(
               painter = painterResource(R.drawable.fab_add),
               contentDescription = null,
               tint = Color.White
           )
       }

}


最后但并非最不重要的,我有一个FilterView组合,它是一个包含FilterFabMenu和FilterFab组合的Column: FilterView
@SuppressLint("UnusedTransitionTargetStateParameter")
@Composable
fun FilterView(
    items: List<FilterFabMenuItem>,
    modifier: Modifier = Modifier
) {

    var filterFabState by rememberSaveable() {
        mutableStateOf(FilterFabState.COLLAPSED)
    }

    val transitionState = remember {
        MutableTransitionState(filterFabState).apply {
            targetState = FilterFabState.COLLAPSED
        }
    }

    val transition = updateTransition(targetState = transitionState, label = "transition")

    val iconRotationDegree by transition.animateFloat({
        tween(durationMillis = 150, easing = FastOutSlowInEasing)
    }, label = "rotation") {
        if (filterFabState == FilterFabState.EXPANDED) 230f else 0f
    }

    Column(
        modifier = modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.End,
        verticalArrangement = Arrangement.spacedBy(16.dp,Alignment.Bottom)
    ) {
        FilterFabMenu(items = items, visible = filterFabState == FilterFabState.EXPANDED)
        FilterFab(
            state = filterFabState,
            rotation = iconRotationDegree, onClick = { state ->
                filterFabState = state
            })
    }
}

这将产生以下结果:

1个回答

0

expandVertically 在你的 enterTransition 中不是这种动画的正确方法。根据文档,它会从上到下或从下到上地动画显示剪辑中的内容。你将此动画应用于整个列(因此所有项目同时),导致了你向我们展示的 gif。

相反,你应该使用不同的进入/退出动画类型,也许是自定义动画,在其中使用项目缩放来模拟“弹出”效果,如下所示:

scaleFactor.animateTo(2f, tween(easing = FastOutSlowInEasing, durationMillis = 50))
scaleFactor.animateTo(1f, tween(easing = FastOutSlowInEasing, durationMillis = 70))

在这种情况下,scaleFactor是一种类型为Animatable<Float,AnimationVector1D>的可动画对象。

然后,您为每个列项(即菜单项)创建这样的可动画对象。之后,只需在协程范围内的for循环中运行每个菜单项的动画(由于Compose动画默认挂起,它们将按顺序运行,如果要并行运行,请使用async / awaitAll)。

此外,不要忘记将您的可动画对象放入remember {}中,然后仅在您单击展开/关闭菜单时在LaunchedEffect中触发您正在动画化的值,例如scaleFactor,并在列项的修饰符中使用它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接