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

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(使用前将#替换为@)

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

  • Android 在打开应用程序时会广播吗?

    例如 如果我想知道Youtube何时打开 是否有与之相关的广播 我当然知道我可以轮询 logcat 消息来检查活动 但我可以通过广播来做到这一点吗 因为它会少得多的耗电 此链接似乎表明这是不可能的 如何跟踪 Android 中的应用程序使用
  • Twitter 登录说明

    我想在 Android 中创建一个 Twitter 应用程序 为此 我想创建一个登录页面并登录到 Twitter 为此 我们需要消费者密钥和消费者密钥 这是什么意思 要创建此登录页面 除了 Twitter 帐户之外 我们还需要其他任何东西吗
  • Android:使用 OAuth 访问 google 任务时出现问题

    由于 google 任务没有公共 api 我想编写解决方法并像浏览器一样请求数据 然后解析结果以进一步显示 为了访问数据 我使用 google 实现了 OAuth 身份验证来访问此 url https mail google com htt
  • Manifest Merger工具:替换失败

    我正在使用一个使用自己的 android theme 的库 因此在构建时收到以下错误 错误 55 9 任务 contacit processDebugManifest 执行失败 清单合并失败 AndroidManifest xml 中的属性
  • 使用 ADB 命令获取 IMEI 号码 Android 12

    对于 11 之前的 Android 版本 我使用以下命令从我的设备获取 IMEI 号码 adb shell service call iphonesubinfo 4 cut c 52 66 tr d space or adb shell s
  • 如何为发布而不是调试创建密钥库?扑

    我按照使用此网站部署 flutter 的步骤进行操作https flutter io android release https flutter io android release 当我运行 flutter build apk 时出现此错
  • 出现错误错误:res/menu/mainMenu.xml:文件名无效:必须仅包含[a-z0-9_。]

    我是安卓新手 刚刚开始使用 我在 res 文件夹中创建了一个文件 menu mainMenu xml 但我得到了错误 Error res menu mainMenu xml invalid file name must contain on
  • 使用 Retrofit2 和 Mockito 或 Robolectric 进行 Android 单元测试

    我可以测试 Retrofit2beta4 的真实响应吗 我需要 Mockito 或 Robolectic 吗 我的项目中没有活动 它将是一个库 我需要测试服务器是否正确响应 现在我有这样的代码并卡住了 Mock ApiManager api
  • AudioTrack、SoundPool 或 MediaPlayer,我应该使用哪个?

    如果我需要能够 播放多个音频文件 具有不同的持续时间 例如 5 到 30 秒 独立设置右 左声道的音量 应用声音效果 如混响 失真 那么 我应该使用哪个 API 另外 我在 AudioTrack API 上找不到太多文档 有谁知道在哪里可以
  • 使用 gradlew assembleRelease 从 React Native 创建发布 apk 时出现错误

    我想发布 apk 但我收到错误 文件已存在 mkdir D mobile 它在 d 驱动器中生成名为 mobile 的文件 删除文件后 再次执行 gradlew assembleRelease 创建该文件并抛出错误 任务 app bundl
  • HERE 地图:更改路线已行驶部分的颜色

    导航时可以改变路线的颜色吗 具体来说 我希望路线中已行驶的部分的颜色与即将行驶的部分的颜色不同 现在都是同一个颜色 将 MapRoute 对象的 TravelColor 变量设置为透明对我来说很有效 mapRoute color Resou
  • Android:监听状态栏通知

    有没有办法在状态栏被下拉时监听通知 1 用于检测状态栏变化 您可以注册一个监听器来获取系统UI可见性变化的通知 因此 要在您的活动中注册侦听器 Detecting if the user swipe from the top down to
  • Android:RecyclerView 不显示片段中的列表项

    有人可以帮我尝试让我的 RecyclerView 出现吗 如果我不在片段中实现它 就会出现这种情况 然而 当我尝试将其实现到片段中时 CarFront 中的其他 XML 代码与 RecyclerView 分开显示 我的日志中收到此错误 E
  • 错误:无法创建新会话,因为找不到需要 HttpClient、InputStream 和 long 的“createSession”

    我正在尝试自动化 Android 混合应用程序 但出现以下错误 1 线程 main org openqa selenium WebDriverException中出现异常 无法创建新会话 因为未找到需要 HttpClient InputSt
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • 在 KitKat 4.4.2 中获取 SDard 路径和大小

    我在 Google Play 上有一个设备信息应用程序 在该应用程序中我有存储信息 我知道 Android 4 4 在访问外部 SD 卡方面发生了一些变化 内部似乎没有给我带来问题 我的问题是 如何可靠地获取 KitKat 上 SD 卡的大
  • Android 中循环事件的星期几和时间选择器

    我想创建一个控件 允许用户在我的 Android 活动中选择一周中的某一天 星期一 和一天中的某个时间 下午 1 00 找不到任何关于此的好帖子 好吧 我想我已经明白了 我只是不喜欢这个解决方案 因为我在一周中的某一天使用的微调器与时间选择
  • 获取其他指针的MotionEvent.getRawX/getRawY

    我可以获取其他指针的MotionEvent getRawX getRawY 值吗 MotionEvent getRawX API 参考 http developer android com reference android view Mo
  • 使用 JobScheduler API 进行位置更新

    下面是我使用 FireBaseJobDispatcher 启动作业的演示代码 public class MainActivity extends AppCompatActivity Override protected void onCre
  • 如何在片段中实现 onBackPressed() 和意图?

    我知道 onBackPressed 是活动中的一种方法 但是 我想在片段中使用该功能 以便当按下后退按钮时 它会通过 Intent 重定向到另一个活动 有什么办法解决这个问题吗 public class News Events fragme

随机推荐