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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/453090.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iPhone, Android 手机是如何收到推送通知的?

本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 iPhone, Android 手机是如何收到推送通知的&#xff1f; 我们的手机或电脑是如何收到推送通知的&#xff1f; 通常我们可以使用消息解决方案 Firebase 来支持通知推送。下图显示了 Fi…

刷题DAY21 | LeetCode 530-二叉搜索树的最小绝对差 501-二叉搜索树中的众数 236-二叉树的最近公共祖先

530 二叉搜索树的最小绝对差&#xff08;easy&#xff09; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 思路&#xff1a;双指针法 代码实现&#xff1a; class So…

ArmSoM Rockchip系列产品 通用教程 之 RTC 使用

1. RTC 简介​ RTC&#xff1a;(Real_Time Clock)&#xff1a;实时时钟 HYM8563是一种低功耗实时时钟&#xff08;RTC&#xff09;芯片&#xff0c;用于提供精确的时间和日期信息。它提供一个可编程的时钟输出&#xff0c;一个中断输出和一个掉电检测器&#xff0c;所有的地址…

从零开始,一步步构建服务网格istio

一、环境情况 环境&#xff1a;Ubuntu20.04 机器数量&#xff1a;单机1台 IP&#xff1a;10.9.2.83 二、准备知识 为什么使用 Istio&#xff1f; Istio提供了一种更高级别的服务网格解决方案&#xff0c;它可以简化和加强 Kubernetes 集群中的服务间通信、流量管理、安全…

SpringBoot配置加载顺序和SpringBoot分离打包:将jar包与lib依赖、配置文件分开

文章目录 一、SpringBoot配置加载顺序1.SpringBoot配置优先级&#xff08;1&#xff09;命令行参数&#xff08;2&#xff09;配置文件 二、SpringBoot分离打包&#xff1a;将jar包与lib依赖、配置文件分开1.pom文件配置2.打包后的目录结构 一、SpringBoot配置加载顺序 官方文…

下载文件,无法获取header中的Content-Disposition

问题&#xff1a;axios跨域请求时&#xff0c;无法获取header中的Content-Disposition&#xff0c;并且network中已显示Content-Disposition 在使用CORS方式跨域时&#xff0c;浏览器只会返回默认的头部Header 解决&#xff1a; 后端在返回时&#xff0c;需要设置公开的响应…

C++第三弹---C++入门(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 C入门 1、内联函数 1.1、概念 1.2、特性 2、auto关键字(C11) 2.1、类型别名思考 2.2、auto简介 2.3、auto的使用细则 2.3、auto不能推导的场景 …

Transformer家族

在《Transformer原理》中我们介绍了&#xff0c;现在很多大模型都是基于Transformer&#xff0c;其中最出名就是GPT和BERT模型&#xff0c;在GPT和BERT模型被提出来之后&#xff0c;NLP领域也出现了基于Transformer结构的模型&#xff0c;按照模型结构基本可以分为三类&#xf…

(2022级)成都工业学院Java程序设计(JAVA)实验一:编写一个简单的Java程序

写在前面 1、基于2022级软件工程/计算机科学与技术实验指导书 2、代码仅提供参考 3、如果代码不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 IntelliJ IDEA 2023.2.2 jdk17.0.6 实验要求 1、 控制台菜单。要求如下&#xff1a; 1&#xff09;…

镭速教你如何解决大数据量串行处理的问题

大数据的高效处理成为企业发展的关键。然而&#xff0c;大数据量串行处理的问题常常困扰着许多企业&#xff0c;尤其是在数据传输方面。本文将探讨大数据量串行处理的常见问题&#xff0c;并介绍企业常用的处理方式&#xff0c;最后重点阐述镭速如何提供创新解决方案&#xff0…

手机如何打开mxf视频?怎么把mxf压缩转换成mp4?

在应用商店中下载并安装支持MXF格式的播放器应用&#xff0c;例如VLC媒体播放器、MX播放器等。这些应用通常支持更多的视频格式&#xff0c;并提供更多的播放和管理功能。而MXF文件通常比较大&#xff0c;尤其是对于高分辨率和高质量的视频。可以通过野葱视频转换器将MXF压缩转…

中国人工心脏五年风云录:谁主沉浮?

在生命的长河中&#xff0c;有些时刻会永远铭记。对于李先生来说&#xff0c;那个转折点是在2019年&#xff0c;当他被诊断为终末期心衰患者&#xff0c;生命进入了倒计时。然而&#xff0c;随着中国人工心脏技术的飞速发展&#xff0c;李先生的命运得以改写。 心衰&#xff0…

【AI绘画教程】AI绘画图生图怎么用?

AI绘画技术已经越来越成熟&#xff0c;越来越多的人开始尝试利用AI进行创作。而AI绘画图生图作为一款优秀的AI绘画工具&#xff0c;正是帮助许多人创作的好帮手。 AI绘画图生图功能可以通过多种软件实现&#xff0c;具体的操作步骤可能因软件而异&#xff0c;但大体流程相似。以…

期货开户之前需要了解什么?

一、期货开户之前需要了解什么&#xff1f; 1、确保期货公司是100%正规&#xff0c;可以在证监会或期货协会官网查询到。 2、是否是通过“期货开户云”系统开户&#xff0c;其他系统开户均为假冒。 3、提前跟期货公司居间人或客户经理谈好手续费收取标准、手续费返还比例是多…

使用IDEA构建SpringBoot程序的镜像

实战&#xff1a;使用IDEA构建SpringBoot程序的镜像 这里就以我之前写的WIT问卷管理系统为例子吧。 首先在之前写好的SpringBoot项目中新建一个DockerFile 在Dockerfile中写入我们的base镜像&#xff0c;之前我已经创建好了并且传到docker仓库了&#xff0c;这里就直接拉取 …

树莓派4B Ubuntu20.04 Python3.9安装ROS踩坑记录

问题描述 在使用sudo apt-get update命令更新时发现无法引入apt-pkg,使用python3 -c "import apt_pkg"发现无法引入&#xff0c;应该是因为&#xff1a;20.04的系统默认python是3.8&#xff0c;但是我换成了3.9所以没有编译文件&#xff0c;于是使用sudo update-alte…

yolo发展历史

yolo系列 一、任务描述二、设计思想三、发展历程1. YOLOv11.1问题背景1.2创新点1.3训练流程1.4检测流程1.4.1 优点1.4.2 缺点 2. YOLOv22.1问题背景2.2创新点2.3提升性能的方法2.4训练流程&#xff1f;&#xff1f;&#xff1f;&#xff1f; 3. YOLOv33.1问题背景3.2模型改进 4…

蓝桥杯倒计时 36天-DFS练习2

文章目录 黄金二叉树混沌之力2 黄金二叉树 思路一&#xff1a;递推做法 #include<bits/stdc.h> using namespace std;const int N 1e510;int A[N]; int B[N]; int n,sum;int main( ){cin>>n;for(int i1;i<n;i)cin>>A[i];int left,right;for(int i1;i<…

[C语言][PTA基础C基础题目集] strtok 函数的理解与应用

一.strtok函数的解释与说明 ①strtok函数的功能 Find the next token in a string. 即查找字符串中的下一个标记. 就是将一个字符串分割成一系列的子串. ②strtok函数的原型 char *strtok( char * strToken, const char * strDelimit ); strToken: 要分割的字符串. strDel…

【Java探索之旅】解密Java中的类型转换与类型提升

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、类型转化1.1 自动类型转换&#xff08;隐式类型转换&#xff09;1.2 强制类型转换…