Jetpack Compose 动画正式开始学习

1. 简单值动画

Kotlin 复制代码
   //将一个Color简单值 从一个值 变化到另一个 另一个简单值 就用 animateColorAsState
    val backgroundColor by animateColorAsState(if (tabPage == TabPage.Home) Purple100 else Green300)

动画其实就是 一个状态不停发生改变导致 组件不断重组产生的过程

2. 可见性动画

2.1 按钮展开收缩

LazyColumn

Kotlin 复制代码
val lazyListState = rememberLazyListState()
Kotlin 复制代码
/**
 * Returns whether the lazy list is currently scrolling up.
 * 返回LazyColumn是否 当前向上滚动
 */
@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
}
Kotlin 复制代码
     // Toggle the visibility of the content with animation.
            AnimatedVisibility(visible = extended) {
                Text(
                    text = stringResource(R.string.edit),
                    modifier = Modifier
                        .padding(start = 8.dp, top = 3.dp)
                )
            }

可见性动画 AnimatedVisibility

2.2 消息从顶部滑入和滑出

复制代码
Kotlin 复制代码
@Composable
private fun EditMessage(shown: Boolean) {

    Log.d("ning","".plus(shown))
    AnimatedVisibility(
        visible = shown,
        //垂直出来
        enter = slideInVertically(
            // 通过从 initialOffsetY 向下滑动到 0 来进入
            initialOffsetY = { fullHeight -> fullHeight },
            //这里要传入一个带参数的函数,返回的是你需要告诉动画系统控件初始位置或结束位置,参数是动画系统告诉你的控件的高度
            //LinearOutSlowInEasing:传入元素使用减速缓和设置动画,减速缓和以峰值速度(元素移动的最快点)开始过渡 , 慢慢减速 ,直到停止
            animationSpec = tween(durationMillis = 150, easing = LinearOutSlowInEasing)//帧动画
        ),
        //垂直回去
        exit = slideOutVertically(
            // 通过从 initialOffsetY 向上滑动到 targetOffsetY 来退出
            targetOffsetY = { fullHeight -> -fullHeight },
            //FastOutLinearInEasing :退出屏幕的元素使用加速度缓和,从静止开始,以峰值速度结束
            animationSpec = tween(durationMillis = 150, easing = FastOutLinearInEasing)
        )
    ) {
        Surface(
            modifier = Modifier.fillMaxWidth(),
            color = MaterialTheme.colorScheme.secondary,
            shadowElevation = 4.dp
        ) {
            Text(
                text = stringResource(R.string.edit_message),
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

3.内容大小动画

Kotlin 复制代码
      Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                // This `Column` animates its size when its content changes.
                //加了,当内容发生变化的时候,会慢慢的撑开
                .animateContentSize()
        ) {
            Row {
                Icon(
                    imageVector = Icons.Default.Info,
                    contentDescription = null
                )
                Spacer(modifier = Modifier.width(16.dp))
                Text(
                    text = topic,
                    style = MaterialTheme.typography.bodyLarge
                )
            }
            if (expanded) {
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = stringResource(R.string.lorem_ipsum),
                    textAlign = TextAlign.Justify
                )
            }
        }

4.多值动画

Kotlin 复制代码
@Composable
private fun HomeTabBar(
    backgroundColor: Color,
    tabPage: TabPage,
    onTabSelected: (tabPage: TabPage) -> Unit
) {
    TabRow(
        selectedTabIndex = tabPage.ordinal,
        containerColor = backgroundColor,
        indicator = { tabPositions ->
            HomeTabIndicator(tabPositions, tabPage)
        }
    ) {
        HomeTab(
            icon = Icons.Default.Home,
            title = stringResource(R.string.home),
            onClick = { onTabSelected(TabPage.Home) }
        )
        HomeTab(
            icon = Icons.Default.AccountBox,
            title = stringResource(R.string.work),
            onClick = { onTabSelected(TabPage.Work) }
        )
    }
}
Kotlin 复制代码
@Composable
private fun HomeTabIndicator(
    //TabPosition 当前选项卡的位置信息
    //对应tabrow里面的组件集合 有顺序索引的
    tabPositions: List<TabPosition>,
    tabPage: TabPage
) {

    // 表示当前选择的选项卡的指示器。
    // 默认情况下,这将是一个 TabRowDefaults.Indicator,
    // 使用 TabRowDefaults.tabIndicatorOffset 修饰符对其位置进行动画处理。
    // 请注意,此指示器将强制填满整个选项卡,
    // 因此您应该使用 TabRowDefaults.tabIndicatorOffset 或类似工具在此空间内对实际绘制的指示器进行动画处理,并从头开始提供偏移量。

    //自定义选项卡指示器
    //多值动画 :在状态发生改变时,有多个动画值要一起发生改变
    //设置一个Transition  并使用 targetState 提供的目标 对其进行更新.
    //当 targetState 更改时,Transition 将朝着为 新 targetState 指定的目标值 运行其所有子动画
    //可以使用 Transition 动态添加子动画 :Transition.animateFloat 、animateColor、 animateValue 等。
    val transition = updateTransition(
        tabPage,//目标状态
        label = "Tab indicator"
    )

    //当前选项卡的位置信息的左侧  用动画的方式描绘当前选项卡位置信息的左侧
    val indicatorLeft by transition.animateDp(
        //实现弹性效果
        transitionSpec = {
            //指标向右移动,左边缘比右边缘移动得慢
            if (TabPage.Home isTransitioningTo TabPage.Work) {
                spring(
                    //刚度
                    // 右边比左边快
                    stiffness = Spring.StiffnessVeryLow
                )
            } else {
                spring(
                    //刚度
                    stiffness = Spring.StiffnessMedium
                )
            }
        },
        label = "Indicator left"
    ) { tabPage ->//参数就是那个枚举状态
        tabPositions[tabPage.ordinal].left
    }

    //当前选项卡的位置信息的右侧
    val indicatorRight by transition.animateDp(
        //实现弹性效果
        transitionSpec = {
            //指标向右移动,左边缘比右边缘移动得慢
            if (TabPage.Home isTransitioningTo TabPage.Work) {
                spring(
                    //刚度
                    // 右边比左边快
                    stiffness = Spring.StiffnessMedium
                )
            } else {
                spring(
                    //刚度
                    stiffness = Spring.StiffnessVeryLow
                )
            }
        },
        label = "Indicator right"
    ) { tabPage ->//参数就是那个枚举状态
        tabPositions[tabPage.ordinal].right
    }


    val color by transition.animateColor(
        label = "Border color"
    ) { page ->
        if (page == TabPage.Home) Purple700 else Green800
    }
    Box(
        Modifier
            //这个修饰符用于包裹 Box 的内容,使其尺寸与内容尺寸相匹配,并将内容对齐到底部的起始位置。
            .wrapContentSize(align = Alignment.BottomStart)
            .offset(x = indicatorLeft)//偏移量
            .width(indicatorRight - indicatorLeft)
            .padding(4.dp)
            //这个一定要放下面,要不然撑不开
            .fillMaxSize()
            .border(
                BorderStroke(2.dp, color),
                RoundedCornerShape(4.dp)
            )
    )
}

5.重复动画

Kotlin 复制代码
@Composable
private fun LoadingRow() {
    // Creates an `InfiniteTransition` that runs infinite child animation values.
    //无限

    val infiniteTransition = rememberInfiniteTransition()
    val alpha by infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        // `infiniteRepeatable` repeats the specified duration-based `AnimationSpec` infinitely.
        animationSpec = infiniteRepeatable(
            // The `keyframes` animates the value by specifying multiple timestamps.
            animation = keyframes {
                //关键字
                // One iteration is 1000 milliseconds.
                durationMillis = 1000 //一次动画的时间  就是从0-1的时间
                // 0.7f at the middle of an iteration.
                //500毫秒的时候 alpha的值是1f
                1f at 1000 //外面延迟 协程3秒 所以 执行了三次
            },
            // When the value finishes animating from 0f to 1f, it repeats by reversing the
            // animation direction.
            repeatMode = RepeatMode.Reverse // 0 - 1 -> 0 ->1
        ), label = ""
    )
    Row(
        modifier = Modifier
            .heightIn(min = 64.dp)
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Box(
            modifier = Modifier
                .size(48.dp)
                .clip(CircleShape)
                .background(Color.LightGray.copy(alpha = alpha))
        )
        Spacer(modifier = Modifier.width(16.dp))
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(32.dp)
                .background(Color.LightGray.copy(alpha = alpha))
        )
    }
}

6.手势动画

Kotlin 复制代码
/**
 * The modified element can be horizontally swiped away.
 * 修改后的元素可以水平滑动
 * 当元素轻扫到屏幕边缘时调用。
 * @param onDismissed Called when the element is swiped to the edge of the screen.
 */
private fun Modifier.swipeToDismiss(
    onDismissed: () -> Unit
    //composed 合成的意思
): Modifier = composed {

    val offsetX = remember {
        Animatable(0f)
    }


    pointerInput(Unit) {
        //衰减动画通常在投掷姿势后使用,用于计算投掷动画最后的固定位置  样条衰减  指数衰减 两个衰减器  这个是样条衰减
        val decay = splineBasedDecay<Float>(this)

        coroutineScope {
            //可以做一些手势相关的操作
            //创建一个修饰符,用于处理修改元素区域内的光标输入
            //pointerInputs 可以调用 PointerInputScope.awaitPointerEventScope,
            //以安装可以等待PointerEventScope 的光标输入处理程序

            while (true) {
                //等待触摸按下事件
                //awaitPointerEventScope :挂起并安装指针输入块,该块可以等待输入事件并立即响应他们
                //awaitFirstDown: 读取事件,直到收到第一个 down
                val pointerId = awaitPointerEventScope {
                    awaitFirstDown().id
                }

                val velocityTracker = VelocityTracker()//专门计算速度的类


                //等待拖动事件
                awaitPointerEventScope {
                    //监听水平滑动
                    horizontalDrag(pointerId) { change ->
                        val horizontalDragOffset = offsetX.value + change.positionChange().x

                        launch {
                            offsetX.snapTo(horizontalDragOffset)//平滑过渡的效果
                        }
                        //记录滑动的位置
                        velocityTracker.addPosition(
                            change.uptimeMillis,
                            change.position
                        )//偏移量 与 所用时间

                        //消费掉手势事件,而不是传递给外部

                        change.consume()

                    }
                }
                // 拖动完成,计算投掷的速度
                val velocity = velocityTracker.calculateVelocity().x
                //我们需要计算投掷的最终位置,以决定是将元素划回原始位置,还是将其划开并调用回调
                val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)

                offsetX.updateBounds(lowerBound = -size.width.toFloat(),upperBound = size.width.toFloat())
                launch {

                    if (targetOffsetX.absoluteValue <= size.width) {
                        //元素范围之内  划回来
                        offsetX.animateTo(targetValue = 0f, initialVelocity = velocity) //以原来的速度划回来
                    } else {
                        //启动衰减动画
                        offsetX.animateDecay(velocity,decay)
                        onDismissed()
                    }
                }

            }
        }


    }.offset {
        IntOffset(offsetX.value.roundToInt(), 0)
    }

}
Kotlin 复制代码
/**
 * Shows a row for one task.
 *
 * @param task The task description.
 * @param onRemove Called when the task is swiped away and removed.
 */
@Composable
private fun TaskRow(task: String, onRemove: () -> Unit) {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .swipeToDismiss(onRemove),
        shadowElevation = 2.dp
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            Icon(
                imageVector = Icons.Default.Check,
                contentDescription = null
            )
            Spacer(modifier = Modifier.width(16.dp))
            Text(
                text = task,
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}
相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习