如何在 android jetpack compose 中显示工具提示

Abh*_*bhi 20 android tooltip android-jetpack-compose

我正在尝试在我的应用程序 UI 中添加一个简单的工具提示,用于FABIconButtonMenu等。

示例工具提示

如何将其添加到jetpack compose中?
我熟悉如何使用 XML 和以编程方式进行添加,如此处所述

这些方法的局限性 - 尝试尽可能避免 XML,对于编程方法,出于明显的原因,compose 中没有 findViewById。

参考Jetpack 文档Codelab示例
没有任何与工具提示相关的内容。

任何帮助表示赞赏。

注意
不要寻找任何自定义项,简单明了的工具提示就可以了。
最好没有第三方库。

更新
任何有相同要求的人,请提出这个问题

Art*_*pov 18

更新

\n

自版本1.1.0(于 2023 年 \xc2\xa0May\xc2\xa010 发布)以来,Jetpack Compose Material 3\xc2\xa0 现在包含官方工具提示。\xc2\xa0 有两种类型:plain\xc2\xa0tooltips ( PlainTooltipBox) 和rich\xc2\xa0tooltips ( RichTooltipBox)。

\n\n

简单的工具提示

\n

简单的工具提示简要描述了 UI 元素。普通工具提示非常适合标记没有文本的 UI 元素,例如仅图标和字段元素。

\n

图标按钮上的普通工具提示

\n

要将普通工具提示应用于 xc2xa0 任何组件,请使用 包裹组件并将组件PlainTooltipBox()添加Modifier.tooltipAnchor()到 xe2x80x99s 修饰符:

\n
import androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.PlainTooltipBox\nimport androidx.compose.material3.Text\n\nPlainTooltipBox(\n   tooltip = { Text("Present now") }\n) {\n   IconButton(\n       onClick = { /* Icon button\'s click event */ },\n       modifier = Modifier.tooltipTrigger()\n   ) {\n       Icon(\n           imageVector = Icons.Filled.FilePresent,\n           contentDescription = "Present now"\n       )\n   }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

丰富的工具提示

\n

丰富的工具提示非常适合较长的文本,例如\xc2\xa0definitions 或\xc2\xa0explanations。丰富的工具提示为\xc2\xa0a\xc2\xa0UI 元素提供了额外的上下文,并且可以包含\xc2\xa0 按钮或\xc2\xa0 超链接。

\n

丰富的工具提示,包含描述和文本按钮作为操作

\n

您可以通过\xc2\xa0向 提供参数来获得持久或\xc2\xa0非持久的丰富工具isPersistent提示RichTooltipState()。要\xc2\xa0关闭持久工具提示,您需要\xc2\xa0点击工具提示区域之外或\xc2\xa0调用工具提示状态上的\xc2\xa0关闭操作。

\n

非持久性工具提示会在短暂的持续时间后自动消失。

\n

要添加丰富的工具提示,您可以使用可RichTooltipBox()组合项并修改工具提示状态来控制工具提示的可见性。

\n
val tooltipState = remember { RichTooltipState() }\nval scope = rememberCoroutineScope()\nRichTooltipBox(\n   title = { Text("Add others") },\n   action = {\n       TextButton(\n           onClick = { scope.launch { tooltipState.dismiss() } }\n       ) { Text("Learn More") }\n   },\n   text = { Text("Share this collection with friends...") },\n   tooltipState = tooltipState\n) {\n   IconButton(\n       onClick = { /* Icon button\'s click event */ },\n       modifier = Modifier.tooltipTrigger()\n   ) {\n       Icon(\n           imageVector = Icons.Filled.People,\n           contentDescription = "Add others"\n       )\n   }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

旧答案,对于手动实施仍然有用。

\n

截至 2021 年 10 月 21 日,Jetpack Compose 中没有可组合的官方工具提示。

\n

但是可以使用 有效地构建一个漂亮的工具提示androidx.compose.ui.window.Popup
\n我们可以以具体的DropdownMenu实现作为起点。

\n

结果示例(参见下面的源代码):

\nAndroidX Jetpack Compose 的工具提示示例\n

\n

如何在长按时显示工具提示(使用示例):

\n
import androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.material.Text\nimport androidx.compose.material.ripple.rememberRipple\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.semantics.Role\n\n@Composable\n@OptIn(ExperimentalFoundationApi::class)\nfun TooltipOnLongClickExample(onClick: () -> Unit = {}) {\n  // Commonly a Tooltip can be placed in a Box with a sibling\n  // that will be used as the \'anchor\' for positioning.\n  Box {\n    val showTooltip = remember { mutableStateOf(false) }\n\n    // Buttons and Surfaces don\'t support onLongClick out of the box,\n    // so use a simple Box with combinedClickable\n    Box(\n      modifier = Modifier\n        .combinedClickable(\n          interactionSource = remember { MutableInteractionSource() },\n          indication = rememberRipple(),\n          onClickLabel = "Button action description",\n          role = Role.Button,\n          onClick = onClick,\n          onLongClick = { showTooltip.value = true },\n        ),\n    ) {\n      Text("Click Me (will show tooltip on long click)")\n    }\n\n    Tooltip(showTooltip) {\n      // Tooltip content goes here.\n      Text("Tooltip Text!!")\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

工具提示可组合源代码:

\n
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")\n\nimport androidx.compose.animation.core.MutableTransitionState\nimport androidx.compose.animation.core.animateFloat\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.core.updateTransition\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.takeOrElse\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.unit.DpOffset\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.Popup\nimport androidx.compose.ui.window.PopupProperties\nimport androidx.core.graphics.ColorUtils\nimport kotlinx.coroutines.delay\n\n\n/**\n * Tooltip implementation for AndroidX Jetpack Compose.\n * Based on material [DropdownMenu] implementation\n *\n * A [Tooltip] behaves similarly to a [Popup], and will use the position of the parent layout\n * to position itself on screen. Commonly a [Tooltip] will be placed in a [Box] with a sibling\n * that will be used as the \'anchor\'. Note that a [Tooltip] by itself will not take up any\n * space in a layout, as the tooltip is displayed in a separate window, on top of other content.\n *\n * The [content] of a [Tooltip] will typically be [Text], as well as custom content.\n *\n * [Tooltip] changes its positioning depending on the available space, always trying to be\n * fully visible. It will try to expand horizontally, depending on layout direction, to the end of\n * its parent, then to the start of its parent, and then screen end-aligned. Vertically, it will\n * try to expand to the bottom of its parent, then from the top of its parent, and then screen\n * top-aligned. An [offset] can be provided to adjust the positioning of the menu for cases when\n * the layout bounds of its parent do not coincide with its visual bounds. Note the offset will\n * be applied in the direction in which the menu will decide to expand.\n *\n * @param expanded Whether the tooltip is currently visible to the user\n * @param offset [DpOffset] to be added to the position of the tooltip\n *\n * @see androidx.compose.material.DropdownMenu\n * @see androidx.compose.material.DropdownMenuPositionProvider\n * @see androidx.compose.ui.window.Popup\n *\n * @author Artyom Krivolapov\n */\n@Composable\nfun Tooltip(\n  expanded: MutableState<Boolean>,\n  modifier: Modifier = Modifier,\n  timeoutMillis: Long = TooltipTimeout,\n  backgroundColor: Color = Color.Black,\n  offset: DpOffset = DpOffset(0.dp, 0.dp),\n  properties: PopupProperties = PopupProperties(focusable = true),\n  content: @Composable ColumnScope.() -> Unit,\n) {\n  val expandedStates = remember { MutableTransitionState(false) }\n  expandedStates.targetState = expanded.value\n\n  if (expandedStates.currentState || expandedStates.targetState) {\n    if (expandedStates.isIdle) {\n      LaunchedEffect(timeoutMillis, expanded) {\n        delay(timeoutMillis)\n        expanded.value = false\n      }\n    }\n\n    Popup(\n      onDismissRequest = { expanded.value = false },\n      popupPositionProvider = DropdownMenuPositionProvider(offset, LocalDensity.current),\n      properties = properties,\n    ) {\n      Box(\n        // Add space for elevation shadow\n        modifier = Modifier.padding(TooltipElevation),\n      ) {\n        TooltipContent(expandedStates, backgroundColor, modifier, content)\n      }\n    }\n  }\n}\n\n\n/** @see androidx.compose.material.DropdownMenuContent */\n@Composable\nprivate fun TooltipContent(\n  expandedStates: MutableTransitionState<Boolean>,\n  backgroundColor: Color,\n  modifier: Modifier,\n  content: @Composable ColumnScope.() -> Unit,\n) {\n  // Tooltip open/close animation.\n  val transition = updateTransition(expandedStates, "Tooltip")\n\n  val alpha by transition.animateFloat(\n    label = "alpha",\n    transitionSpec = {\n      if (false isTransitioningTo true) {\n        // Dismissed to expanded\n        tween(durationMillis = InTransitionDuration)\n      } else {\n        // Expanded to dismissed.\n        tween(durationMillis = OutTransitionDuration)\n      }\n    }\n  ) { if (it) 1f else 0f }\n\n  Card(\n    backgroundColor = backgroundColor.copy(alpha = 0.75f),\n    contentColor = MaterialTheme.colors.contentColorFor(backgroundColor)\n      .takeOrElse { backgroundColor.onColor() },\n    modifier = Modifier.alpha(alpha),\n    elevation = TooltipElevation,\n  ) {\n    val p = TooltipPadding\n    Column(\n      modifier = modifier\n        .padding(start = p, top = p * 0.5f, end = p, bottom = p * 0.7f)\n        .width(IntrinsicSize.Max),\n      content = content,\n    )\n  }\n}\n\nprivate val TooltipElevation = 16.dp\nprivate val TooltipPadding = 16.dp\n\n// Tooltip open/close animation duration.\nprivate const val InTransitionDuration = 64\nprivate const val OutTransitionDuration = 240\n\n// Default timeout before tooltip close\nprivate const val TooltipTimeout = 2_000L - OutTransitionDuration\n\n\n// Color utils\n\n/**\n * Calculates an \'on\' color for this color.\n *\n * @return [Color.Black] or [Color.White], depending on [isLightColor].\n */\nfun Color.onColor(): Color {\n  return if (isLightColor()) Color.Black else Color.White\n}\n\n/**\n * Calculates if this color is considered light.\n *\n * @return true or false, depending on the higher contrast between [Color.Black] and [Color.White].\n *\n */\nfun Color.isLightColor(): Boolean {\n  val contrastForBlack = calculateContrastFor(foreground = Color.Black)\n  val contrastForWhite = calculateContrastFor(foreground = Color.White)\n  return contrastForBlack > contrastForWhite\n}\n\nfun Color.calculateContrastFor(foreground: Color): Double {\n  return ColorUtils.calculateContrast(foreground.toArgb(), toArgb())\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

使用 AndroidX Jetpack Compose 版本进行测试1.1.0-alpha06

\n

请参阅带有完整示例的要点:
\n https://gist.github.com/amal/aad53791308e6edb055f3cf61f881451

\n


Gab*_*tti 9

通过 M3,您可以使用可PlainTooltipBox组合项:

就像是:

    PlainTooltipBox(
        tooltip = { Text("Add to favorites" ) },
        contentColor = White,
    ) {
        IconButton(
            onClick = { /* Icon button's click event */ },
            modifier = Modifier.tooltipAnchor()
        ) {
            Icon(
                imageVector = Icons.Filled.Favorite,
                contentDescription = "Localized Description"
            )
        }
    }
Run Code Online (Sandbox Code Playgroud)

长按时会调用工具提示anchor

在此输入图像描述

如果您想通过事件单击显示工具提示,您可以使用:

    val tooltipState = remember { PlainTooltipState() }
    val scope = rememberCoroutineScope()

    PlainTooltipBox(
        tooltip = { Text("Add to favorites" ) },
        tooltipState = tooltipState
    ) {
        IconButton(
            onClick = { /* Icon button's click event */ },
            modifier = Modifier.tooltipAnchor()
        ) {
            Icon(
                imageVector = Icons.Filled.Favorite,
                contentDescription = "Localized Description"
            )
        }
    }

    Spacer(Modifier.requiredHeight(30.dp))
    OutlinedButton(
        onClick = { scope.launch { tooltipState.show() } }
    ) {
        Text("Display tooltip")
    }
Run Code Online (Sandbox Code Playgroud)


Nag*_*obi 2

Jetpack Compose尚无官方tooltip支持

你可能可以在上面构建一些东西androidx.compose.ui.window.Popup(...)

另外,我还会查看TextDelegate来测量文本,以便了解工具提示/弹出窗口的位置和方式。