Jetpack Compose 实现了一个丝滑流畅的页面展开和关闭的效果动画

news2024/11/19 14:31:06

Jetpack Compose 将动画实现的门槛降低了,不过Compose目前还不支持共享元素过渡。

(上篇文章Jetpack Compose开发的本地笔记本)的动画效果的实现

转跳前的准备工作

定义State枚举类来表示页面的三种状态:
Closing(关闭状态)
Closed(关闭完成状态)
Opening(展开状态)\

enum class CreateNoteState {
    Closing, Closed, Opening
}

Jetpack Compose中的mutableStateOf()函数来创建可变状态,并分别初始化了三个变量cardSizecreateNoteUIOffsetcurrentCreateNoteState
cardSize是一个IntSize类型的可变状态,用来表示页面的尺寸,初始值为(0, 0)
createNoteUIOffset是一个IntOffset类型的可变状态,用来表示创建笔记界面的偏移量,初始值为(0, 0)
currentCreateNoteState是一个枚举类型CreateNoteState的可变状态,用来表示创建笔记界面的当前状态,初始值为State.Closed,即关闭状态。这个枚举类型可能包括ClosingClosedOpening等状态。

var cardSize by mutableStateOf(IntSize(0, 0))
var createNoteUIOffset by mutableStateOf(IntOffset(0, 0))
var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)

点击转跳的按钮

onSizeChanged 用于在转跳按钮 的大小发生变化时更新布局,并将新的大小传递给 onSizedChanged 回调函数。
onGloballyPositioned 用于在 转跳按钮 的位置发生变化时更新布局,并将新的位置传递给 intOffset 变量。
最后,当用户点击 转跳按钮 时,会调用 onClick 回调函数,并将 intOffset 变量作为参数传递出去。

@Composable
fun HomeAddButton(
    onSizedChanged: (IntSize) -> Unit,
    onClick: (offset: IntOffset) -> Unit,
) {
    var intOffset: IntOffset? by remember { mutableStateOf(null) }
    FloatingActionButton(onClick = {
        onClick(intOffset!!)
    },
        Modifier
            .padding(16.dp)
            .onSizeChanged { onSizedChanged(it) }
            .onGloballyPositioned {
                val offset = it.localToRoot(Offset(0f, 0f))
                intOffset = IntOffset(offset.x.toInt(), offset.y.toInt())
            }
    ) {
      ......
    }
}
HomeAddButton(
    onSizedChanged = {
        viewModel.cardSize = it
    }
) { offset ->
    //点击事件
    viewModel.currentCreateNoteState = CreateNoteState.Opening
    viewModel.createNoteUIOffset = offset
}

转跳界面

记录页面的大小信息,包括
cardSize(折叠状态大小)、
fullSize(完全展开状态大小)
cardOffset(折叠状态页面在屏幕中的偏移位置)。

CreateNotePage(
    viewModel.currentCreateNoteState,
    viewModel.cardSize,
    viewModel.fullSize,
    viewModel.createNoteUIOffset,
    {
        viewModel.currentCreateNoteState = CreateNoteState.Closing
    },
    {
        viewModel.currentCreateNoteState = CreateNoteState.Closed
    })

定义offsetAnimatable来记录和控制页面在动画过程中在屏幕中的偏移变化。使用animateTo()函数来实现从cardOffset变化到fullOffset的平移动画效果。

var animReady by remember { mutableStateOf(false) }//标记动画准备
var animFinish by remember { mutableStateOf(false) }//标记动完成
val offsetAnimatable = remember { Animatable(IntOffset(0, 0), IntOffset.VectorConverter) }
val DEPLOYMENT_DURATION = 500 //动画速度

val cornerSize by animateDpAsState(if (animFinish) 0.dp  else 16.dp) //圆角

使用LaunchedEffect来监听CreateNoteState的变化,并根据不同的状态触发相应的动画效果: - Opening状态:调用offsetAnimatableanimateTo()函数实现展开动画,将页面偏移从cardOffset变化到fullOffset;设置animFinish为true。 - Closing状态:调用offsetAnimatableanimateTo()函数实现关闭动画,将页面偏移从fullOffset变化到cardOffset;设置animFinish为false和animReady为false。 - Closed状态:页面关闭完成,无需执行任何操作。

LaunchedEffect(pageState) {
    when (pageState) {
        CreateNoteState.Opening -> {
            animReady = true
            offsetAnimatable.snapTo(cardOffset)
            offsetAnimatable.animateTo(fullOffset,animationSpec = tween(DEPLOYMENT_DURATION))
            animFinish = true
        }
        CreateNoteState.Closing -> {
            animFinish = false
            offsetAnimatable.snapTo(fullOffset)
            offsetAnimatable.animateTo(cardOffset,animationSpec = tween(DEPLOYMENT_DURATION))
            animReady = false
            onPageClosed()
        }
        else -> {}
    }
}

使用Box组件及其Modifier应用offsetAnimatable.value、大小变化size和圆角cornerSize的动画效果在页面上显示。

if (pageState != CreateNoteState.Closed && animReady) {
    Box(
        Modifier
            .offset { offsetAnimatable.value }
            .clip(RoundedCornerShape(cornerSize))
            .width(with(LocalDensity.current) { size.width.toDp() })
            .height(with(LocalDensity.current) { size.height.toDp() })

    ) {
       ...
       你的界面
       ...
    }
}

请添加图片描述

完整效果图

完整代码

转跳按钮

HomeAddButton(
    Modifier
        .navigationBarsPadding()
        .align(Alignment.BottomEnd),
    onSizedChanged = {
        viewModel.cardSize = it
    }
) { offset ->
    //点击事件
    viewModel.currentCreateNoteState = CreateNoteState.Opening
    viewModel.createNoteUIOffset = offset
    //震动
    feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
@Composable
fun HomeAddButton(
    modifier: Modifier,
    onSizedChanged: (IntSize) -> Unit,
    onClick: (offset: IntOffset) -> Unit,
) {
    var intOffset: IntOffset? by remember { mutableStateOf(null) }
    FloatingActionButton(onClick = {
        onClick(intOffset!!)
    },
        modifier
            .padding(16.dp)
            .onSizeChanged { onSizedChanged(it) }
            .onGloballyPositioned {
                val offset = it.localToRoot(Offset(0f, 0f))
                intOffset = IntOffset(offset.x.toInt(), offset.y.toInt())
            }
    ) {
        Icon(
         ......
        )
    }
}

记录页面的大小信息

/** 创建笔记 */
var cardSize by mutableStateOf(IntSize(0, 0))
var createNoteUIOffset by mutableStateOf(IntOffset(0, 0))
var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)

转跳的界面

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun CreateNotePage(
    pageState: CreateNoteState,
    cardSize: IntSize,
    fullSize: IntSize,
    cardOffset: IntOffset,
    onPageClosing: () -> Unit,
    onPageClosed: () -> Unit
) {
    var animReady by remember { mutableStateOf(false) }
    var animFinish by remember { mutableStateOf(false) }
    val background by animateColorAsState(
        if (pageState == CreateNoteState.Closing) AppColor.themeColor else Color.Transparent)
    val alpha by animateFloatAsState(
        targetValue = if (pageState == CreateNoteState.Closing) 1f else 0.6f,
        animationSpec = tween(durationMillis = 300)
    )

    val DEPLOYMENT_DURATION = 500
    val size by animateIntSizeAsState(if (pageState > CreateNoteState.Closed) fullSize else cardSize,
        animationSpec = tween(DEPLOYMENT_DURATION))

    val fullOffset = remember { IntOffset(0, 0) }
    val offsetAnimatable = remember { Animatable(IntOffset(0, 0), IntOffset.VectorConverter) }
    val cornerSize by animateDpAsState(if (animFinish) 0.dp  else 16.dp)


    LaunchedEffect(pageState) {
        when (pageState) {
            CreateNoteState.Opening -> {
                animReady = true
                offsetAnimatable.snapTo(cardOffset)
                offsetAnimatable.animateTo(fullOffset,animationSpec = tween(DEPLOYMENT_DURATION))
                animFinish = true
            }
            CreateNoteState.Closing -> {
                animFinish = false
                offsetAnimatable.snapTo(fullOffset)
                offsetAnimatable.animateTo(cardOffset,animationSpec = tween(DEPLOYMENT_DURATION))
                animReady = false
                onPageClosed()
            }
            else -> {}
        }
    }
    if (pageState != CreateNoteState.Closed && animReady) {
        Box(
            Modifier
                .offset { offsetAnimatable.value }
                .clip(RoundedCornerShape(cornerSize))
                .width(with(LocalDensity.current) { size.width.toDp() })
                .height(with(LocalDensity.current) { size.height.toDp() })

        ) {
            CreateNoteUI(onBack = onPageClosing) // 真正的界面
            if (pageState == CreateNoteState.Closing){
                Box(Modifier.fillMaxSize()
                    .alpha(alpha)
                    .background(background))
            }
        }
    }

完整源码

JIULANG9/WordsFairyNote: 词仙笔记源码 (github.com)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

找不到vcruntime140.dll,无法继续执行代码?多种解决方法解析

找不到vcruntime140.dll,无法继续执行代码?当你在尝试运行某个程序时,突然弹出一条错误提示框,告诉你无法继续执行代码,因为找不到vcruntime140.dll。这个问题很常见,但是它可能会让你感到困惑和疑惑。这篇文章将详细介…

chatgpt赋能Python-python_numpy遍历

Python NumPy遍历:使用高效的方式为数据科学家节省时间和精力 Python语言在数据科学领域中的地位越来越重要,并成为了数据科学家的首选语言之一。在解决数据问题时,NumPy模块是Python程序员经常使用的一个重要库。NumPy提供了快速的数组操作…

【大数据实训】—Hadoop开发环境搭建(一)

【大数据实训】—Hadoop开发环境搭建(一) 第一关、任务描述 本关任务:配置JavaJDK。 相关知识 配置开发环境是我们学习一门IT技术的第一步,Hadoop是基于Java开发的,所以我们学习Hadoop之前需要在Linux系统中配置Jav…

Flowable钉钉对接005-完成钉钉任务

企业中有自己的业务系统,审批都在业务系统中审批,如何结合移动办公的开放平台实现统一审批至关重要。 场景很简单,自己的系统中可以审批,钉钉上也可以审批,使用H5来适配,统一待办任务 统一待办审批 目标&am…

python获取tx弹幕数据并制作词云图

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 知识点介绍: 爬虫基本思路流程 requests模块的使用 pandas读取表格数据 环境介绍: 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 模块使用: requests >>> pip install requests pa…

​年轻人的情绪,都藏在知乎的短故事里

“谢邀,与世界分享我刚编的故事。” 这是一句在知乎被调侃的老梗。它源自于知乎上有众多隐匿的大佬,他们经历过各种奇闻轶事,也乐于分享传奇的人生经历,而这其间,很多真假难辨的事迹,也被很多用户笑称可以当…

这几款好用的软件分享给你

软件一:Handbrake Handbrake是一款免费开源的视频转码软件,适用于Mac、Windows和Linux系统。它可以将几乎所有视频格式转换为其他格式,包括MP4、MKV、AVI等等。作为一个强大的视频编码器,它可以压缩视频大小,并提供多…

Android Qcom USB Driver学习(十一)

该系列文章总目录链接与各部分简介: Android Qcom USB Driver学习(零) 基于TI的Firmware Update固件升级的流程分析usb appliction layers的数据 USB Protocol Package ①/② map to check password correct Package Format: Byte[0] Report Id Byte[1] Valid L…

玩转SpringCloud Alibaba,看阿里大佬的笔记是真香

大家都知道,SpringCloudAlibaba 风靡 Java 开发行业,各个公司都在用这套技术,所以咱们 Java 工程师不管是日常工作或是出去面试,都会用到或者被问到关于SpringCloudAlibaba的应用以及底层原理 所以说,小编下面带来一份…

用于视频编辑和渲染的最佳GPU是什么?

购买新的图形卡(GPU)可能很困难,尤其是如果涉及您所不熟悉的所有技术问题。 显卡市场上的大多数消费者只需要了解显卡在自己喜欢的游戏中的性能,并确定购买决定即可。但是,如果您想购买GPU进行视频编辑或3D渲染&#…

网络安全工程师考证指南

已经到2023年了,那么信息安全类证书最有前途的有哪些呢?今天和大家一起聊聊这个话题! 1.CISP(国家登记的信息安全专业人员) 就CISP而言,安全实践者基本耳闻,算是国内权威认证,毕竟有政府背景为认证做背书&…

微服务环境搭建(spring cloud Alibaba)(一)

模块: 商品模块, 订单模块, 用户模块 一. 1. 技术选型以及准备工作 maven : 3.8.8版本 数据库: MySQL 8.0.32 持久层: springData , jpa 其他java环境 : SpringCloud Alibaba 技术栈 2. 模块 设计 springcloud- alibaba 父工程 shop - common 公共模块 [ 实体类 ] sh…

【技术】多端能力服务统一是一种关键的技术和架构

多端能力服务统一是一种关键的技术和服务架构,旨在为不同终端设备提供一致的用户体验和功能。通过采用前端和后端技术的组合,如响应式 Web 设计、PWA、跨平台移动应用开发框架、RESTful API、GraphQL、WebSocket、Serverless 架构和微服务架构&#xff0…

实现并测试协同滤波算法

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 本次实验实现了基于用户和基于项的协同滤波算法,并在 Movielens 两个较小的数据集上进行了测试,测试采用 RMSE 进行评估 一、问题简述 1.1 推荐系统问题 推荐系统问题旨在用户推荐相关项,项…

如何利用CiteSpace快速锁定领域内最新研究热点并制作精美的可视化专题图

在科研工作中,我们常常需要面对海量的文献进行阅读和分析,如何在这些文献当中找出值得精读、细读的关键文献,挖掘学科前沿,找到研究热点就成为了开展研究之前首先需要解决的问题。CiteSpace作为一款优秀的文献计量学软件&#xff…

六、数据仓库详细介绍(ETL)经验篇

0x00 前言 日常工作中大多数时候都是在做数据开发,ETL 无处不在。虽然最近两年主要做的大数据开发,但感觉日常干的这些还是 ETL 那点事儿,区别只是技术组件全换了、数据量大了很多。 前几年数仓势微,是因为传统的那些工具数据库等…

每个程序员必备的基础设施--Codeium

自从 chartGPT3 横空出世以后,AI 技术越来越深入到每个人的生活中,这里不谈 AI 到底是不是真的存在着类人的思想和智慧,也不谈是否取代人类的问题。本着实用主义的原则,先让它能为我所用。 我在工作中已经深入使用了 chartGPT 和…

【详解栈Stack与队列Queue】

🌠作者:TheMythWS. 🎆专栏:《集合与数据结构》 🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。 目录 栈 ( Stack ) 栈的概念 栈的使用 栈的模拟实现 栈…

idea的这款代码提示插件爱了

前言 Idea 是一款功能强大的集成开发环境(IDE),它可以帮助开发人员更加高效地编写、调试和部署软件应用程序,Idea 还具有许多插件和扩展,可以根据开发人员的需要进行定制和扩展,从而提高开发效率,今天我们就来介绍一款…

Fourier分析入门——第7章——采样理论

目录 第 7 章 采样定理 7.1 引言 7.2 采样定理 7.3 错误识别(aliasing) 7.4 Parseval定理(Parseval[pzeifa:l]) 7.5 截断Fourier级数和回归理论(Truncated Fourier Series & Regression Theory) 第 7 章 采样定理 7.1 引言 在第 6 章中,我们发现有限区…