我们如何修复透明/半透明可组合项上的材质阴影故障?

2024-04-22

如果您还不知道,Android 的材质阴影存在一个缺陷,即材质设计及其表面、照明和高度概念带来的阴影。另外,如果您不知道,Compose 使用许多与View框架,包括那些负责所述阴影的框架,因此它具有与View是的,至少现在是这样。

Card(), FloatingActionButton(), ExtendedFloatingActionButton(), and Surface() shown with and without translucent backgrounds.

For reasons I won't get into here,* I don't believe that there is any proper fix for this – i.e., I don't think that the platform offers any method or configuration by which to clip out or otherwise remove that artifact – so we're left with workarounds. Additionally, a main requirement is that the shadows appear exactly as the platform ones normally would, so any method that draws shadows with other techiques, like a uniform gradient or blur or whatnot, are not acceptable.

鉴于此,我们能否在 Compose 中创建一个强大的、普遍适用的解决方案?

我个人采取了一种总体方法,即禁用原始阴影并在其位置绘制剪切的复制品。 (我意识到,简单地在它上面打一个洞并不是阴影实际工作的方式,但这似乎是主要预期的效果。)我在下面的答案中分享了一个 Compose 版本的示例,但主要动机这个问题是为了在将其放入图书馆之前检查是否有更好的想法。

我确信我的示例中存在可以改进的技术细节,但我主要对根本不同的方法或建议感到好奇。例如,我对以某种方式使用不感兴趣drawBehind() or Canvas()相反,做本质上相同的事情,或者重构参数只是为了将内容放入其中,等等。我更多地思考的是:

  • 您能否设计一些其他(性能更高)的方法来修剪该伪像,而无需创建和剪切单独的阴影对象?和Views,我发现的唯一方法就是画View两次,在一次绘制中剪切内容,在另一次绘制中禁用阴影。不过,考虑到开销,我最终决定不这样做。

  • 这可以提取到Modifier和扩展,类似于*GraphicsLayerModifiers and shadow()/graphicsLayer()?我还没有完全理解 Compose 的所有概念和功能,但我不这么认为。

  • 有没有其他方法可以使其普遍适用,而不需要额外的接线?我的示例中的阴影对象取决于三个可选参数以及目标可组合项的默认值,除了用另一个可组合项包装目标之外,我想不出任何方法来获取这些参数。


* Those reasons are outlined in my question here https://stackoverflow.com/q/70227950.


我们将使用FloatingActionButton()对于这个本地示例,因为它支持我们需要考虑的几乎所有选项,但这应该适用于任何可组合项。为了方便起见,我用这个解决方案包装了几个更常见的解决方案,并将它们组装在这个 GitHub 要点 https://gist.github.com/zed-alpha/3dc931720292c1f3ff31fa6a130f52cd,如果你想尝试一些除了FloatingActionButton().

我们希望我们的包装器 Composable 充当直接替代品,因此它的参数列表和默认值是从FloatingActionButton():

@Composable
fun ClippedShadowFloatingActionButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
    backgroundColor: Color = MaterialTheme.colors.secondary,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
    content: @Composable () -> Unit
) {
    Layout(
        {
            ClippedShadow(
                elevation = elevation.elevation(interactionSource).value,
                shape = shape,
                modifier = modifier
            )
            FloatingActionButton(
                onClick = onClick,
                modifier = modifier,
                interactionSource = interactionSource,
                shape = shape,
                backgroundColor = backgroundColor,
                contentColor = contentColor,
                elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
                content = content
            )
        },
        modifier
    ) { measurables, constraints ->
        require(measurables.size == 2)

        val shadow = measurables[0]
        val target = measurables[1]

        val targetPlaceable = target.measure(constraints)
        val width = targetPlaceable.width
        val height = targetPlaceable.height

        val shadowPlaceable = shadow.measure(Constraints.fixed(width, height))

        layout(width, height) {
            shadowPlaceable.place(0, 0)
            targetPlaceable.place(0, 0)
        }
    }
}

我们基本上是在包装一个FloatingActionButton()以及我们的副本影子可组合项Layout()针对设置进行了优化。大多数参数原封不动地传递给包装的FloatingActionButton()除了elevation,我们将其归零以禁用固有阴影。相反,我们直接指向我们的ClippedShadow()适当的原始高程值​​,此处使用以下公式计算FloatingActionButtonElevation and InteractionSource参数。更简单的可组合项,例如Card()将具有无状态海拔值Dp可以直接通过。

ClippedShadow()本身就是另一种习俗Layout(),但没有内容:

@Composable
fun ClippedShadow(elevation: Dp, shape: Shape, modifier: Modifier = Modifier) {
    Layout(
        modifier
            .drawWithCache {
                // Naive cache setup similar to foundation's Background.
                val path = Path()
                var lastSize: Size? = null

                fun updatePathIfNeeded() {
                    if (size != lastSize) {
                        path.reset()
                        path.addOutline(
                            shape.createOutline(size, layoutDirection, this)
                        )
                        lastSize = size
                    }
                }

                onDrawWithContent {
                    updatePathIfNeeded()
                    clipPath(path, ClipOp.Difference) {
                        [email protected] /cdn-cgi/l/email-protection()
                    }
                }
            }
            .shadow(elevation, shape)
    ) { _, constraints ->
        layout(constraints.minWidth, constraints.minHeight) {}
    }
}

我们只需要它的影子和Canvas访问,我们通过两个简单的方法获得Modifier扩展。drawWithCache()让我们保持简单Path我们使用缓存来剪辑和恢复整个内容绘制,以及shadow()这是不言自明的。将此可组合项分层放置在目标后面,并禁用其自身的阴影,我们就可以获得所需的效果:

正如问题中,前三个是Card(), FloatingActionButton(), and ExtendedFloatingActionButton(),但包含在我们的修复中。为了证明InteractionSource/elevation 重定向按预期工作,这个简短的动图 https://i.stack.imgur.com/48QVo.gif显示两个FloatingActionButton()并排具有完全透明的背景;右边的已经应用了我们的修复。

对于上面修复图像中的第四个示例,我们使用了独奏ClippedShadow(),只是为了说明它也可以单独工作:

ClippedShadow(
    elevation = 10.dp,
    shape = RoundedCornerShape(10.dp),
    modifier = Modifier.size(FabSize)
)

就像常规的可组合项一样,它应该适用于任何Shape这对于当前 API 级别有效。任意凸Paths 适用于所有相关版本,从 API 级别 29 (Q) 开始,凹面版本也适用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我们如何修复透明/半透明可组合项上的材质阴影故障? 的相关文章

  • 这个方法比 Math.random() 更快吗?

    我是一名初学者 目前已经开始开发一款使用粒子群优化算法的 Android 游戏 我现在正在尝试稍微优化我的代码 并且 for 循环中有相当多的 Math random 几乎一直在运行 所以我正在考虑一种方法来绕过并跳过所有 Math ran
  • Android上如何模拟后台Activity因内存不足而被系统杀死的过程?

    我正在处理 内存不足 不再有后台进程 问题 当这种情况发生时 我的活动处于后台并被杀死 我正在尝试保存并加载实例状态来解决它 但因为它并不是每次都会发生 在这种情况下我应该如何测试我的活动 Thanks 您可以通过 adb 强制进程终止 g
  • 从历史堆栈中删除活动

    我的应用程序在用户第一次运行应用程序时显示注册活动 如下所示 活动启动画面 欢迎来到游戏 注册帐户 ActivitySplashScreenSignUp 很好 填写此信息 ActivityGameMain 游戏主屏幕 因此 当用户单击每个屏
  • 从 arraylist 和 hashmap 中删除重复项

    我有一个数组列表 其中包含付款人的姓名 另一个数组列表包含每次付款的费用 例如 nameArray 尼古拉 劳尔 洛伦佐 劳尔 劳尔 洛伦佐 尼古拉 价格数组 24 12 22 18 5 8 1 我需要将每个人的费用相加 所以数组必须变成
  • 需要使用手机后退按钮返回 Web 视图的帮助

    这是我的代码 package com testappmobile import android app Activity import android os Bundle import android view KeyEvent impor
  • 如何访问android库项目中的资源

    我正在构建一个 android 库项目 它内部需要一些静态资源 图像 xml 等 然后我想知道我可以把这些资源放在哪里以及如何访问它们 既然我把资源放到了assets文件夹 我使用 AssetManager 来访问资源 public cla
  • Android:我可以创建一个不是矩形的视图/画布吗?圆形的?

    我有一个圆形视图 悬停在主要内容上方 gt 从屏幕出来的 z 轴方向 当有人点击屏幕时 我希望选择主要内容或悬停在上方的视图 当它覆盖主视图时 到目前为止效果很好 我在透明画布上有一个圆形物品 这意味着您可以看到该圆圈之外的背景的所有内容
  • 具有自定义源集的 Android Gradle 风格 - gradle 文件应该是什么样子?

    我有一个旧的 eclipse 项目 我已经转移到 android studio 并设置为使用flavor 它似乎工作得很好 直到我开始尝试在我的风格之间使用不同的 java 文件 我的项目设置是这样的 ProjectRoot acitonb
  • Android:使 Dialog 周围的所有内容都比默认值更暗

    我有一个具有以下样式的自定义对话框 它显示了一个无边框对话框 后面的任何内容都会 稍微 变暗 我的设计师希望背后的一切都比 Android 的默认设置更暗 但不是完全黑色 有这样的设置吗 我能想到的唯一解决方法是使用全屏活动而不是对话框 只
  • 如何在 NumberPicker 中一次显示 3 个以上的值

    我正在创建一个数字选择器 如下图所示 但如果有可用空间 我想显示 3 个以上的值 该选择器有 20 个项目 并且有足够的空间来显示 3 个以上的值 这可以使用 NumberPicker 来完成吗 只需以编程方式设置numberPicker
  • Android 纹理仅显示纯色

    我正在尝试在四边形上显示单个纹理 我有一个可用的 VertexObject 它可以很好地绘制一个正方形 或任何几何对象 现在我尝试扩展它来处理纹理 但纹理不起作用 我只看到一种纯色的四边形 坐标数据位于 arrayList 中 the ve
  • android中listview显示数据库中的数据

    我是安卓新手 我想知道如何在列表视图中显示数据库中的数据 它不会向数据库添加数据 我只是显示我们存储在数据库中的任何内容 请帮助我实现这一目标 提前致谢 使用这些课程可能会对您有所帮助 用于数据库创建 package com example
  • 如何使用应用程序接口将蓝牙套接字传递给另一个活动

    因此 根据我收集的信息 套接字连接既不可序列化 也不可分割 但我需要将蓝牙连接传递给另一个活动 我不想作为中间人编写服务 所以请不要将此作为解决方案发布 我听说有一种方法可以使用自定义应用程序接口来传递这些类型的对象 但我一生都找不到这样的
  • Jetpack 导航:如何从一个嵌套图的子级导航到另一个嵌套图的子级?

    导航结构 MainActivity nav root HomeFragment AuthNestedGraph nav auth BeforeOtpFragment home OtpFragment ProfileNestedGraph n
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • SDK >=26 仍需要 mipmap/ic_launcher.png?

    在 Android 中 有两种指定启动器图标 可以说是应用程序图标 的方法 老 方式 在 mipmap 文件夹中指定不同的 png 文件 通常命名为 ic launcher png 但可以通过以下方式设置名称android icon mip
  • Activity 类型中的方法 showDialog(int) 在 Android 中已被弃用?

    方法showDialog int 从类型Activity is 已弃用 什么原因 以及如何解决 什么原因 http developer android com reference android app Activity html show
  • 剪切评级栏中的图像

    我制作了自己的评级栏 花朵图像有 4 种尺寸 xdpi hdpi 等 从 24px24px 到 64x64px
  • 为什么带处理程序的连续自动对焦相机不允许切换相机闪光灯?

    到目前为止我所做的 我已经实现了用于读取二维码的自定义相机 需要继续聚焦相机以获得更好的二维码读取 我的问题当我使用处理程序每 秒聚焦一次时 相机闪光灯开 关按钮不起作用 或者打开和关闭相机闪光灯需要太多时间 当我删除每秒自动对焦相机的代码
  • 如何在布局编辑器中模拟沉浸式模式

    我想在布局编辑器中全屏查看我的布局 我正在使用 eclipse 插件 我已经通过选择隐藏了 ActionBar NoActionBar组合中的主题 但导航栏是一个不同的故事 AFAIK 它只能使用代码中的标志来隐藏 我需要在活动 xml 文

随机推荐