使用Android Jetpack Compose渲染效果打造酷炫的动画效果

如何在Android Jetpack Compose中使用渲染效果打造令人惊艳的视觉体验

学习示例:如何使用渲染效果来改变UI界面
效果图

引言

Jetpack Compose提供了各种工具和组件来构建引人入胜的UI,而在Compose中较为鲜为人知的一个宝藏是RenderEffect。

在这篇博文中,我们将通过创建一些渲染效果的酷炫示例来探索RenderEffect。

什么是RenderEffect?

RenderEffect允许你向UI组件应用视觉效果。这些效果可以包括模糊、自定义着色器或者你能想象到的任何其他视觉转换。然而,RenderEffect仅适用于API 31及以上版本。

在我们的示例中,我们将使用RenderEffect为我们的可扩展浮动按钮和一些额外组件创建模糊和着色器效果。

开始

BlurContainer

在第一阶段,让我们介绍“BlurContainer”。这个独特的组件为我们的用户界面增添了额外的视觉优雅和吸引力,创造出令人惊叹的视觉效果。

它包含一个自定义模糊修饰符,将我们的渲染效果提升到了新的水平。

@Composable
fun BlurContainer(modifier: Modifier = Modifier,blur: Float = 60f,component: @Composable BoxScope.() -> Unit,content: @Composable BoxScope.() -> Unit = {},
) {Box(modifier, contentAlignment = Alignment.Center) {Box(modifier = Modifier.customBlur(blur),content = component,)Box(contentAlignment = Alignment.Center) {content()}}
}fun Modifier.customBlur(blur: Float) = this.then(graphicsLayer {if (blur > 0f)renderEffect = RenderEffect.createBlurEffect(blur,blur,Shader.TileMode.DECAL,).asComposeRenderEffect()}
)
  • “customBlur”修饰符扩展接受一个模糊参数,用于指定模糊效果的强度。
  • 它被用来向可组合应用graphicsLayer,进而使用RenderEffect. createBlurEffect来应用模糊效果。graphicsLayer被用来将渲染效果应用到可组合中。
    以下是模糊效果的样子:
    BlurContainer’s Effect

通过这个修饰符,我们可以通过将其链接到现有的修饰符,轻松地向任何可组合添加模糊效果。

将渲染效果应用于父容器

为此,我们将使用自定义着色器——RuntimeShader和Jetpack Compose的graphicsLayer来在父容器中实现所需的视觉效果。
在我们深入了解渲染效果是如何应用之前,让我们先了解一下如何初始化RuntimeShader

@Language("AGSL")
const val ShaderSource = """uniform shader composable;uniform float visibility;half4 main(float2 cord) {half4 color = composable.eval(cord);color.a = step(visibility, color.a);return color;}
"""val runtimeShader = remember {RuntimeShader(ShaderSource)
}

在这段代码片段中,我们创建了一个RuntimeShader实例。remember函数确保着色器只被初始化一次,避免不必要的开销。我们将自定义的着色器源代码(ShaderSource)传递给RuntimeShader的构造函数。

我们的ShaderSource是渲染效果的关键部分。它是用一种名为AGSL(Android Graphics Shading Language)的着色器语言编写的。让我们更仔细地看一下:

  • uniform shader composable:这一行声明了一个名为"composable"的统一着色器变量。如果我们想要应用渲染效果,这个变量将被用来取样可组合元素的颜色。
  • uniform float visibility:我们声明了一个名为"visibility"的统一浮点数变量。这个变量通过指定一个阈值来控制着色器效果的强度。
  • half4 main(float2 cord):main函数是着色器的入口点。它接受一个2D坐标(cord),并以half4的形式返回一个颜色,表示具有红色、绿色、蓝色和alpha分量的颜色。
  • half4 color = composable.eval(cord):在这里,我们从给定坐标处的"composable"着色器统一变量中取样颜色。
  • color.a = step(visibility, color.a):我们通过根据"visibility"阈值将alpha分量(color.a)设置为0或1来应用着色器效果。
  • return color:最后,我们返回修改后的颜色。

在JetLagged应用程序的compose-samples中查看AGSL Shader。

https://github.com/android/compose-samples/tree/main/JetLagged

应用渲染效果

有了我们的RuntimeShaderShaderSource准备就绪,现在我们可以使用graphicsLayer来应用渲染效果了:

Box(modifier.graphicsLayer {runtimeShader.setFloatUniform("visibility", 0.2f)renderEffect = RenderEffect.createRuntimeShaderEffect(runtimeShader, "composable").asComposeRenderEffect()},content = content,
)

以下是它的工作原理:

  • runtimeShader.setFloatUniform("visibility", 0.2f):我们设置着色器中的"visibility"统一变量,以控制效果的强度。在这种情况下,我们将其设置为0.2f,但您可以调整此值以实现您期望的效果。
  • renderEffect = RenderEffect.createRuntimeShaderEffect(...):我们使用createRuntimeShaderEffect方法创建一个RenderEffect。该方法接受我们的runtimeShader和名称"composable",该名称对应于ShaderSource中的着色器变量。
  • asComposeRenderEffect():我们使用asComposeRenderEffect()RenderEffect转换为适用于Compose的格式。

通过在graphicsLayer中应用这个渲染效果,我们可以在包含在Box中的UI组件上实现着色器效果。

为了将所有这些元素整合在一起并无缝地应用我们的渲染效果,我们将创建一个名为ShaderContainer的可组合元素,具体如下:

@Language("AGSL")
const val Source = """uniform shader composable;uniform float visibility;half4 main(float2 cord) {half4 color = composable.eval(cord);color.a = step(visibility, color.a);return color;}
"""@Composable
fun ShaderContainer(modifier: Modifier = Modifier,content: @Composable BoxScope.() -> Unit,
) {val runtimeShader = remember {RuntimeShader(Source)}Box(modifier.graphicsLayer {runtimeShader.setFloatUniform("visibility", 0.2f)renderEffect = RenderEffect.createRuntimeShaderEffect(runtimeShader, "composable").asComposeRenderEffect()},content = content)
}

这是BlurContainer包裹在ShaderContainer中产生的视觉效果:
Parent

现在我们已经成功地通过ShaderContainerBlurContainer为我们的渲染效果奠定了基础,是时候通过打造ExtendedFabRenderEffect将它们整合在一起了。这个可组合元素将成为我们的可展开浮动按钮和动态渲染效果的核心。

ExtendedFabRenderEffect

ExtendedFabRenderEffect可组合元素负责协调整个用户界面、按钮展开的动画和处理渲染效果。让我们深入探讨它的工作原理,以及它如何创造视觉上令人愉悦的用户体验。

流畅的动画

创建流畅而流畅的动画对于精致的用户体验至关重要。我们应用alpha动画来实现这一点:

alpha动画管理按钮的透明度。当展开为true时,按钮变得完全不透明;否则,它们逐渐消失。与偏移动画一样,我们使用animateFloatAsState函数和适当的参数来确保平滑的过渡。

var expanded: Boolean by remember {mutableStateOf(false)
}val alpha by animateFloatAsState(targetValue = if (expanded) 1f else 0f,animationSpec = tween(durationMillis = 1000, easing = LinearEasing),label = ""
)

组合效果

现在,我们将渲染效果ShaderContainer与按钮组合在一起,以创建一个统一的用户界面。在ShaderContaine内部,我们放置了几个ButtonComponent可组合元素,每个代表一个具有特定图标和交互的按钮。

ShaderContainer(modifier = Modifier.fillMaxSize()
) {ButtonComponent(Modifier.padding(paddingValues = PaddingValues(bottom = 80.dp) * FastOutSlowInEasing.transform((alpha))),onClick = {expanded = !expanded}) {Icon(imageVector = Icons.Default.Edit,contentDescription = null,tint = Color.White,modifier = Modifier.alpha(alpha))}ButtonComponent(Modifier.padding(paddingValues = PaddingValues(bottom = 160.dp) * FastOutSlowInEasing.transform(alpha)),onClick = {expanded = !expanded}) {Icon(imageVector = Icons.Default.LocationOn,contentDescription = null,tint = Color.White,modifier = Modifier.alpha(alpha))}ButtonComponent(Modifier.padding(paddingValues = PaddingValues(bottom = 240.dp) * FastOutSlowInEasing.transform(alpha)),onClick = {expanded = !expanded}) {Icon(imageVector = Icons.Default.Delete,contentDescription = null,tint = Color.White,modifier = Modifier.alpha(alpha))}ButtonComponent(Modifier.align(Alignment.BottomEnd),onClick = {expanded = !expanded},) {val rotation by animateFloatAsState(targetValue = if (expanded) 45f else 0f,label = "",animationSpec = tween(1000, easing = FastOutSlowInEasing))Icon(imageVector = Icons.Default.Add,contentDescription = null,modifier = Modifier.rotate(rotation),tint = Color.White)}
}

有了这个设置,ShaderContainer充当了我们按钮的背景,渲染效果通过ButtonComponent可组合元素无缝地应用到按钮上。alpha修饰符确保按钮根据展开状态变得可见或不可见,从而创建出精致而动态的用户界面。

按钮组件结构

ButtonComponent旨在封装可展开菜单中的每个按钮,它提供了定制按钮外观和行为的灵活性。

以下是ButtonComponent的结构:

@Composable
fun BoxScope.ButtonComponent(modifier: Modifier = Modifier,background: Color = Color.Black,onClick: () -> Unit,content: @Composable BoxScope.() -> Unit
) {// Applying the Blur Effect with the BlurContainerBlurContainer(modifier = modifier.clickable(interactionSource = remember {MutableInteractionSource()},indication = null,onClick = onClick,).align(Alignment.BottomEnd),component = {Box(Modifier.size(40.dp).background(color = background, CircleShape))}) {// Content (Icon or other elements) inside the buttonBox(Modifier.size(80.dp),content = content,contentAlignment = Alignment.Center,)}
}

这样,我们已经从以上的代码中实现了期望的效果!
ExtendedFabRenderEffect

TextRenderEffect

TextRenderEffect的核心是动态文本显示。我们将使用一系列激励性的短语和引语来呈现给用户。这些短语将包括"实现你的目标"、"追逐你的梦想"等情感。

val animateTextList =listOf("\"Reach your goals\"","\"Achieve your dreams\"","\"Be happy\"","\"Be healthy\"","\"Get rid of depression\"","\"Overcome loneliness\"")

我们将创建一个textToDisplay状态变量来保存并显示这些短语,从而创建一个动态的序列。

文本动画化

为了使文本显示更吸引人,我们将利用一些关键动画:

  • 模糊效果:我们将对文本应用模糊效果。模糊值从0到30再返回0进行动画处理,采用线性缓动动画。这会创造出微妙而迷人的视觉效果,增强了文本的外观。
  • 文本转换:我们将使用LaunchedEffect循环显示短语列表中的每个短语,每个短语显示一定时间。当textToDisplay发生变化时,将发生一个scaleIn动画,呈现新文本的放大效果;随着过渡的结束,会应用一个scaleOut效果。这提供了一个视觉上令人愉悦的方式来引入和退出文本。

与ShaderContainer的完全集成

@Composable
fun TextRenderEffect() {val animateTextList =listOf("\"Reach your goals\"","\"Achieve your dreams\"","\"Be happy\"","\"Be healthy\"","\"Get rid of depression\"","\"Overcome loneliness\"")var index by remember {mutableIntStateOf(0)}var textToDisplay by remember {mutableStateOf("")}val blur = remember { Animatable(0f) }LaunchedEffect(textToDisplay) {blur.animateTo(30f, tween(easing = LinearEasing))blur.animateTo(0f, tween(easing = LinearEasing))}LaunchedEffect(key1 = animateTextList) {while (index <= animateTextList.size) {textToDisplay = animateTextList[index]delay(3000)index = (index + 1) % animateTextList.size}}ShaderContainer(modifier = Modifier.fillMaxSize()) {BlurContainer(modifier = Modifier.fillMaxSize(),blur = blur.value,component = {AnimatedContent(targetState = textToDisplay,modifier = Modifier.fillMaxWidth(),transitionSpec = {(scaleIn()).togetherWith(scaleOut())}, label = "") { text ->Text(modifier = Modifier.fillMaxWidth(),text = text,style = MaterialTheme.typography.headlineLarge,color = MaterialTheme.colorScheme.onPrimaryContainer,textAlign = TextAlign.Center)}}) {}}
}

TextRenderEffect

ImageRenderEffect

在Jetpack Compose中,我们继续探索RenderEffect,这次着眼于引人入胜的ImageRenderEffect。这个可组合项通过引入动态图像过渡和迷人的渲染效果,将图像渲染提升到了一个新的层次。让我们深入了解其构造方式以及它如何增强视觉体验。

动态图像过渡

ImageRenderEffect的核心在于其能够以视觉上吸引人的方式在图像之间进行过渡。为了演示这一点,我们将设定一个基本情景,即在点击事件中,两个图像ic_firstic_second将交替显示。

var image by remember {mutableIntStateOf(R.drawable.ic_first)
}

图像状态变量保存当前显示的图像,用户只需简单点击按钮,就可以在两者之间切换。

打造引人入胜的效果

模糊效果:就像我们之前的示例一样,我们对图像应用模糊效果。模糊值从0到100再返回0进行动画处理,创造出令人着迷的视觉效果,增强了图像过渡效果。

val blur = remember { Animatable(0f) }
LaunchedEffect(image) {blur.animateTo(100f, tween(easing = LinearEasing))blur.animateTo(0f, tween(easing = LinearEasing))
}
  • 图像过渡:图像过渡的核心是AnimatedContent可组合项。它处理图像之间的平滑过渡,结合了fadeInscaleIn效果,用于进入场景的图像,以及fadeOutscaleOut效果,用于退出场景的图像。
AnimatedContent(targetState = image,modifier = Modifier.fillMaxWidth(),transitionSpec = {(fadeIn(tween(easing = LinearEasing)) + scaleIn(tween(1_000, easing = LinearEasing))).togetherWith(fadeOut(tween(1_000, easing = LinearEasing)) + scaleOut(tween(1_000, easing = LinearEasing)))}, label = ""
) { image ->Image(painter = painterResource(id = image),modifier = Modifier.size(200.dp),contentDescription = "")
}

ShaderContainer的完美集成

就像我们之前的示例一样,ImageRenderEffect也集成在ShaderContainer中。这使我们能够融合图像过渡和渲染效果,创造出引人入胜、沉浸式的视觉体验。

@Composable
fun ImageRenderEffect() {var image by remember {mutableIntStateOf(R.drawable.ic_first)}val blur = remember { Animatable(0f) }LaunchedEffect(image) {blur.animateTo(100f, tween(easing = LinearEasing))blur.animateTo(0f, tween(easing = LinearEasing))}Column(modifier = Modifier.wrapContentSize(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,) {ShaderContainer(modifier = Modifier.animateContentSize().clipToBounds().fillMaxWidth()) {BlurContainer(modifier = Modifier.fillMaxWidth(),blur = blur.value,component = {AnimatedContent(targetState = image,modifier = Modifier.fillMaxWidth(),transitionSpec = {(fadeIn(tween(easing = LinearEasing)) + scaleIn(tween(1_000,easing = LinearEasing))).togetherWith(fadeOut(tween(1_000,easing = LinearEasing)) + scaleOut(tween(1_000,easing = LinearEasing)))}, label = "") { image ->Image(painter = painterResource(id = image),modifier = Modifier.size(200.dp),contentDescription = "")}}) {}}Spacer(modifier = Modifier.height(20.dp))Button(onClick = {image =if (image == R.drawable.ic_first) R.drawable.ic_second else R.drawable.ic_first},colors = ButtonDefaults.buttonColors(containerColor = Color.Black)) {Text("Change Image")}}
}

ImageRenderEffect

结论

通过了解ShaderContainerBlurContainerShaderSource以及customBlur修饰符,您拥有了在Jetpack Compose应用程序中创建令人惊叹的渲染效果的工具。这些元素为探索和尝试各种视觉效果和自定义着色器提供了基础,为您的UI设计打开了创意可能性的世界。

Github

https://github.com/cp-megh-l/render-effect-jetpack-compose

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统引论(二)

操作系统发展动力及技术基础 推动操作系统的发展和资源利用率的提高是相关联的。 让输入输出相对独立&#xff0c;通道技术&#xff0c;通道相当于简单的处理器&#xff0c;通过输入输出指令&#xff0c;控制外设完成输入输出。 输入和输出过程不是由主机控制的&#xff0c;是…

【深度学习】pytorch——神经网络工具箱nn

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——神经网络工具箱nn 简介nn.Modulenn.Module实现全连接层nn.Module实现多层感知机 常用神经网络层图像相关层卷积层&#xff08;Conv&#xff…

Flink--Data Source 介绍

Data Source 简介 Flink 做为一款流式计算框架&#xff0c;它可用来做批处理&#xff0c;即处理静态的数据集、历史的数据集&#xff1b;也可以用来做流处理&#xff0c;即实时的处理些实时数据流&#xff0c;实时的产生数据流结果&#xff0c;只要数据源源不断的过来&#xff…

「Verilog学习笔记」多功能数据处理器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 注意题目要求输入信号为有符号数&#xff0c;另外输出信号可能是输入信号的和&#xff0c;所以需要拓展一位&#xff0c;防止溢出。 timescale 1ns/1ns module data_…

Java 正则表达式分组匹配

前几篇文章都是简单判断是否满足匹配规则&#xff0c;当需要提取匹配结果时就用到分组匹配。 分组匹配 可以判断是否满足正则表达式&#xff0c;然后提取出子串。 有些时候电话号码是以 123-4567-8899 这样显示的&#xff0c;我们要判断某个字符串是这种形式的并分别提起三段…

Redis 扩展 RedisBloom 插件,解决缓存击穿、穿透

文章目录 一、概述二、编译准备2.1 升级 make2.2 安装 Python3 三、编译 RedisBloom四、测试 RedisBloom五、应用场景5.1 缓存击穿5.2 缓存穿透5.3 原理总结 六、存在的问题 如果您对Redis的了解不够深入请关注本栏目&#xff0c;本栏目包括Redis安装&#xff0c;Redis配置文件…

两个栈实现队列

要用两个栈实现队列&#xff0c;就需要了解栈和队列的特性&#xff0c;栈是先进后出&#xff0c;队列是先进先出。基本思路是&#xff0c;把数据先压入栈1中&#xff0c;然后数据在栈1中输出再压入栈2&#xff0c;输出后就能实现队列的先进先出。 package Package;import java.…

SPASS-数据收集及预处理

统计数据的收集 问卷设计 问卷构成 &#xff08;1&#xff09;标题 &#xff08;2&#xff09;导语&#xff08;前言&#xff09; &#xff08;3&#xff09;正文 &#xff08;4&#xff09;结束语 问卷的问题类型 &#xff08;1&#xff09;封闭型问题 &#xff08;2&…

成员变量为动态数据时不可轻易使用

问题描述 业务验收阶段&#xff0c;遇到了一个由于成员变量导致的线程问题 有一个kafka切面&#xff0c;用来处理某些功能在调用前后的发送消息&#xff0c;资产类型type是成员变量定义&#xff1b; 资产1类型推送消息是以zichan1为节点&#xff1b;资产2类型推送消息是以zi…

【Java】IntelliJ IDEA使用JDBC连接MySQL数据库并写入数据

目录 0 准备工作1 创建Java项目2 添加JDBC 驱动程序3 创建数据库连接配置文件4 创建一个 Java 类来连接和操作数据库5 运行应用程序 在 IntelliJ IDEA 中连接 MySQL 数据库并将数据存储在数据表中&#xff0c;使用 Java 和 JDBC&#xff08;Java Database Connectivity&#xf…

老电脑升级内存、固态硬盘、重新装机过程记录

基础环境&#xff1a; 电脑型号&#xff1a;联想XiaoXin700-15ISK系统版本&#xff1a;Windows10 家庭中文版 版本22H2内存&#xff1a;硬盘&#xff1a; 升级想法&#xff1a; 内存升级&#xff0c;固态硬盘升级&#xff0c;系统重装&#xff08;干净一点&#xff09; 升级内存…

【NLP】特征提取: 广泛指南和 3 个操作教程 [Python、CNN、BERT]

什么是机器学习中的特征提取&#xff1f; 特征提取是数据分析和机器学习中的基本概念&#xff0c;是将原始数据转换为更适合分析或建模的格式过程中的关键步骤。特征&#xff0c;也称为变量或属性&#xff0c;是我们用来进行预测、对对象进行分类或从数据中获取见解的数据点的…