Jetpack Compose 动画正式开始学习

1. 简单值动画   

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

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

2. 可见性动画

2.1 按钮展开收缩

LazyColumn

val lazyListState = rememberLazyListState()
/**
 * 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
}
     // 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 消息从顶部滑入和滑出

 

 

@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.内容大小动画

      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.多值动画

 

@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) }
        )
    }
}

 

@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.重复动画

@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.手势动画

/**
 * 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)
    }

}

 

/**
 * 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
            )
        }
    }
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值