本文译自「Jetpack Compose Metaball Edge Effect --- Final Part」,原文链接proandroiddev.com/jetpack-com...,由Yuriy Skul发布于2026年3月21日。

背景
我探索了几种 Metaball 交互方法------都不算太复杂,只是模糊和 alpha 阈值。
但由于 blur()(API 31+)和 RenderEffect(API 31+)有最低 API 级别要求,而用于 alpha 过滤的运行时着色器(AGSL)仅在 API 33 及更高版本中可用,因此我也探索了适用于旧版 Android 的替代方案。
我还尝试在两个带轮廓的圆形按钮之间实现 Metaball 效果------方法相同,但使用的是描边而不是填充形状。与颜色矩阵不同,AGSL 允许在特定的 alpha 范围内进行过滤,这使得渲染成为可能(参见 Supra 部分的底部):

跨 API 的 Metaball 效果的 GitHub 链接
但后来我突然想到一个简单而优雅的想法------实现滚动内容与屏幕边缘之间的元球交互方式相同。在我看来,它看起来非常简洁美观,就像物体之间的元球交互一样。
这个概念验证非常简单------只需在顶部和底部边缘附近添加垂直渐变带,模糊圆形对象,并应用常规的 alpha 阈值过滤:github 链接"Gooey Edge"选项卡:

对文本进行简单探索后发现,根据模糊半径、alpha 阈值和文本大小的不同,结果会有所不同------从一大块实心斑点到一小块,甚至在 alpha 过滤后完全没有效果。
文本替换动画的一个好方法是,将模糊效果从 0 动画到某个值,交换文本,然后再动画回来。
有趣的是,根据你调整元球参数的方式,你可以得到几种不同的行为:
-
文本先变成元球,然后再变成另一段文本
-
文本变成元球,缩小并消失,然后这个过程反向进行,变成一段新的文本
-
文本立即融化,然后出现一段新的文本(我不喜欢这种效果)

也许很多人(包括我自己)都做过类似的事情,但第一个公开分享的人是...... Sinasamaki 以简洁明了的方式分享了这一概念,因此将这一理念引入 Android 平台是理所应当的。他的所有作品都是杰作------纯粹的艺术作品。
滚动文本的 metaball 效果的主要问题在于,模糊效果是以 RenderEffect 的形式应用于整个可组合对象(包含文本的滚动列)。结果,所有文本都会变得模糊,metaball 的交互不再局限于预期的区域(顶部和底部边缘附近)。
相反,整个内容变成了一大块黑色的色块。你越是想让 metaball 效果更强烈、更吸引眼球,情况就越糟糕------因为文本首先会与自身交互,然后才会(以微弱的方式)与屏幕边缘交互。
首先,我尝试构建一个变通方案------在原始文本上方同步渲染相同的文本,然后叠加顶部和底部区域,对其进行模糊处理,最后应用相同的效果。最终,我得到了一个可行的解决方案,但仍然不够完善,而且充满了各种变通方法。
因此,我得出结论,我需要一个 自定义模糊效果,可以局部应用于所需区域(顶部和底部边缘),并且模糊强度随着靠近边缘而增加。
我围绕仅使用 alpha 通道的模糊效果进行了一系列测试和实验(因为元球效果不需要 RGB 模糊)。我比较了线性模糊和高斯模糊两种方法,并尝试了不同的内核质量------16dp(约17次点击)左右效果不错,但超过这个值就需要多次渲染(约61次点击)。使用更高的值(100次以上点击)很快就会变得过于耗费资源。
对于元球效果,我选择了以下结果:

我已经准备好了......
垂直滚动解决方案
实现起来相对简单。
滚动文本放置在一个 Column 元素内,并仅对顶部和底部区域应用自定义模糊效果。随着内容接近滚动容器边缘,模糊强度逐渐增强。
当处理像文本这样非常小的元素(笔画宽度只有几个 dp)时,所有 metaball 配置参数------例如自定义局部模糊区域的高度或渐变带------也都会变得非常小。

为了实现"黏糊糊"效果,添加了高度为 16dp 的小型渐变带,垂直渐变范围为 0.4 到 0。

根据之前对文本及其变换的实验,观察到两种现象:文本要么消失,要么变成一团模糊的色块。
边缘附近的消失可以称为"融化"效果,而与边缘融合也可以称为"黏糊糊"效果。


问题解决了------但我并不满意。这和我预想的完全不一样。它看起来没有我想象中那么干净利落,也没有那么惊艳。
为了获得至少像样的效果,需要对所有这些参数进行大量调整:模糊程度、AGSL 应用区域、透明度阈值以及渐变带中的最大透明度。所有这些都与文本大小有关,而如果文本大小以 sp 为单位定义,则不稳定,甚至行间距也可能导致画面模糊。
此外,由于 RenderEffect 的存在,我们仍然依赖于 API 级别。无论我如何尝试,自定义 alpha 模糊的效果都不如系统自带的模糊效果高效可靠,而且可能会引入瑕疵,尤其是在边缘处。
另外,字母与边缘的融合(这是我们想要的效果)也存在一个缺点------字母之间会相互融合,导致形状变成模糊的色块。遗憾的是,目前还没有彻底消除这些副作用的方法。
虽然无法阻止字母自身融合,但在水平滚动的情况下,相邻字母之间不会发生融合。
水平滚动解决方案
对于相对于模糊半径而言较大的对象,例如带有图标的大型圆形元素,或者更粗更大的图标,可以获得更稳定的效果。此外,即使是巨大的单行文本水平滚动时,效果也会更好。
kotlin
//left/right custom blur local zones width and gradient edge boxes width
val effectMagnitude = 56.dp
val blurRadius = 30.dp
val alphaThresholdEffect = alphaThreshold50PercentEffect
// Max horizontal gradient
val edgeAlpha = 0.5f
val circleSize = 72.dp

对于大型图标,效果仍然对小型和细元素非常敏感------请注意搜索、勾选和加号图标的情况。对于这些图标,需要降低透明度阈值并减小模糊半径。
kotlin
val effectMagnitude = 56.dp
val blurRadius = 30.dp
val alphaThresholdEffect = alphaThreshold50PercentEffect
val edgeAlpha = 0.5f
val iconSize = 56.dp

kotlin
val effectMagnitude = 30.dp
val blurRadius = 20.dp
val alphaThresholdEffect = alphaThreshold30PercentEffect
val edgeAlpha = 0.3f
//use dp instead of sp for stable effect
val HorizontalTextSize = 56.sp

自定义模糊效果可以替换为常规模糊效果吗? 可以,但这会引入一个关键问题:整个内容都会变得模糊,并且到处都会出现不必要的 Metaball 交互。在之前的文章中,我们已经探讨并解决了这个问题。
我们可以避免使用 RenderEffect 模糊或自定义模糊吗?
可以。在我之前的文章中,我探讨了绘制发光光晕边框的六种不同方法,同样的思路也适用于这里------在圆形元素周围绘制模糊蒙版滤镜或光晕渐变。这样,圆形元素的内部就不会变形。
然而,你仍然需要解决相邻元素通过其"透明度"相互交叉的问题,从而导致重叠区域可见。一个简单的办法是检测最左侧和最右侧的元素,并仅对它们应用效果。
结论
是的,局部自定义模糊可以显著简化问题。 然而,它并不能解决视觉上的"污渍"问题。内容往往先变成一团模糊的色块,然后才开始与边缘互动。
元球效果与液态玻璃(玻璃变形)等效果的主要区别在于,元球效果发生在元素之间。这种效果发生在用户界面元素之外,在它们之间的空间中,那里可能存在许多其他元素。
相比之下,玻璃变形在视觉上被包含在单个组件内。
因此,液态玻璃通常看起来干净利落,而元球效果在涉及多个元素时很容易变得杂乱无章。
欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!
保护原创,请勿转载!