Compose-Animation高级别动画

前言

AnimatedVisibility 驱动可视性相关动画,即布局显隐
animateContentSize 内容变换动画相关
Crossfade 布局(或者页面)切换过渡动画


AnimatedVisibility

需求:插入 FAB(浮动按钮)到 scaffold 布局内,我们需要当列表向下滑动时 FAB 自动收起,向上滑动时展开

完整实现流程:

  1. scaffold 定义状态,检测 lazycolumn 滚动方向
  2. 根据滚动方向来动态指定 extend 变量是否为 true
  3. 将 extend 变量传递给 FAB 函数,动态显隐文本
  4. 结束

isScrollingUp

再开始工作之前,需要为 LazyListState 自己编写一个扩展方法 isScrollingUp,用来检测当前滚动方向

@Composable
private fun LazyListState.isScrollingUp(): Boolean {
    var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
    var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
    return remember(this) {
        derivedStateOf {
            if (previousIndex != firstVisibleItemIndex) {
                previousIndex > firstVisibleItemIndex
            } else {
                previousScrollOffset >= firstVisibleItemScrollOffset
            }.also {
                previousIndex = firstVisibleItemIndex
                previousScrollOffset = firstVisibleItemScrollOffset
            }
        }
    }.value
}

FAB

定义一个 FAB 组件,使用 FloatingActionButton 可以便于自定义

AnimatedVisibility 函数可以使得组件显隐过渡平滑,且能根据 API 自定义显隐过程的持续时间以及过程
最简单的使用方法是将其包裹你想要动态显隐的组件,并使用 visible 属性控制显隐

@Composable
private fun HomeFloatingActionButton(
    // 是否显隐,由上级scaffold定义
    extended: Boolean,
    onClick: () -> Unit
) {
    // 一个标准的带图标与文本的FAB
    FloatingActionButton(onClick = onClick) {
        Row(
            modifier = Modifier.padding(horizontal = 16.dp)
        ) {
            Icon(
                imageVector = Icons.Default.Edit,
                contentDescription = null
            )

            // 根据extend的值判断是否显示隐藏
            AnimatedVisibility(visible = extended) {
                Text(
                    text = stringResource(R.string.edit),
                    modifier = Modifier
                        .padding(start = 8.dp, top = 3.dp)
                )
            }
        }
    }
}

scaffold

按步骤走

@Composable
fun Home() {

    // 第一步:定义lazycolumn状态
    val lazyListState = rememberLazyListState()

    Scaffold(
        ...
        floatingActionButton = {
            HomeFloatingActionButton(
                // 第三步,判断当前滚动方向,将布尔返回值作为形参传递给HomeFloatingActionButton
                extended = lazyListState.isScrollingUp(),
            )
        }
    ) { padding ->
        LazyColumn(
            // 第二步:基于lazycolumn指定状态
            state = lazyListState,
        ) {
            ...
        }
    }
}

animateContentSize

用于变换布局大小

@Composable
fun ContentSizeComp() {
    // 定义收缩状态
    var isExpanded by remember {
        mutableStateOf(false)
    }

    Box(
        Modifier
            .fillMaxWidth()
            .background(Color.LightGray)
            .animateContentSize()   // 在这里注册
            .clickable {
                isExpanded = !isExpanded
            }
    ) {
        Text(
            text = "this is adasdj qpowdj pja sipojd pqoi jpqoj psqoj pqojs poqj  opj po jsopdjqpo jopqj qosp jdopqs jdqsp djqs opdqjs dpqsj dp qs",
            modifier = Modifier.padding(12.dp),

            // 默认显示一行文本,一旦isExpanded变化就显示全部文本
            // 此过程类似于点击卡片自动展开的效果,并且有过渡动画哦!
            maxLines = if (isExpanded) 1000 else 1
        )
    }
}

Crossfade

Crossfade 一般用于切换布局使得过渡柔和平缓

@Composable
fun CrossFadeComp() {
    var layoutState by remember {
        mutableStateOf(false)
    }

    Column(
        Modifier.fillMaxWidth()
    ) {
        Button(onClick = { layoutState = !layoutState }) {
            Text(text = "切换布局")
        }

        // Crossfade包裹需要执行页面切换动画的composable内容
        Crossfade(
            // 需要监听的状态
            targetState = layoutState,
            // 自定义动画
            animationSpec = tween(1000)
        ) {
            // it即被监听的状态哦~
            if (it) {
                Icon(Icons.Default.Delete, "")
            } else {
                Icon(Icons.Default.Favorite, "")
            }
        }
    }
}

顶部气泡下弹

需求:点击按钮后于 app 顶部下弹一个全宽度小卡片,过一段时间自己收回去(类似于顶部弹出气泡通知)

同理,为提供流畅的显示隐藏动画,需要使用 AnimatedVisibility 包裹组件,且这里用到了自定义动画

enter 定义入场动画及起始点;
exit 定义出场动画及动画截止点;

tween 可以设置动画的持续时间与 ease

@Composable
private fun EditMessage(shown: Boolean) {
    AnimatedVisibility(
        visible = shown,

        // 入场动画
        enter = slideInVertically(
            // 初始Y轴位置定义为负的总高度,此时卡片完全隐藏在顶部
            // 则入场动画为 -fullHeight -> 0
            initialOffsetY = { fullHeight -> -fullHeight },
            // 动画扩展设置
            animationSpec = tween(durationMillis = 150, easing = LinearOutSlowInEasing)
        ),

        // 出场动画
        exit = slideOutVertically(
            // 同理,出场动画设置终止位置
            // 故动画为 0 -> -fullHeight
            targetOffsetY = { fullHeight -> -fullHeight },
            animationSpec = tween(durationMillis = 250, easing = FastOutLinearInEasing)
        )
    ) {
        Surface(
            modifier = Modifier.fillMaxWidth(),
            color = MaterialTheme.colors.secondary,
            elevation = 4.dp
        ) {
            Text(
                text = stringResource(R.string.edit_message),
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhillery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值