简体   繁体   English

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

[英]How to show tooltip in android jetpack compose

I am trying to add a simple tooltip in my app UI for FAB , IconButton , Menu , etc.我正在尝试在我的应用程序 UI 中为FABIconButtonMenu等添加一个简单的工具提示。

示例工具提示

How to add this in jetpack compose ?如何在jetpack compose中添加它?
I am familiar with how to add using XML and programmatically as mentioned here .我熟悉如何使用 XML 和 此处提到的编程方式添加。

Limitations with those methods - trying to avoid XML as much as possible and for programmatic approach, there is no findViewById in compose for obvious reasons.这些方法的限制 - 试图尽可能避免 XML 并且对于编程方法,由于显而易见的原因,组合中没有 findViewById。

Refered Jetpack Docs , Codelabs and Samples .参考Jetpack 文档代码实验室示例
Got nothing related to the tooltip.没有与工具提示相关的任何内容。

Any help is appreciated.任何帮助表示赞赏。

Note笔记
Not looking for any customizations, plain and simple tooltip would do.不寻找任何自定义,简单明了的工具提示就可以了。
And preferable no 3rd party libs.最好不要第三方库。

Update更新
Anyone having the same requirement, please bump up this issue created .任何有相同要求的人,请提出这个问题

There is no official tooltip support yet in Jetpack Compose . Jetpack Compose中还没有官方tooltip支持。

You could probably build something on top of androidx.compose.ui.window.Popup(...)您可能可以在androidx.compose.ui.window.Popup(...)

Also I'd check out TextDelegate , to measure the text in order to know where & how to position the tooltip/popup.另外我会检查TextDelegate ,以测量文本以了解 position 工具提示/弹出窗口的位置和方式。

I have found a solution to show a tooltip centered in the screen.我找到了一种解决方案,可以在屏幕中央显示工具提示。 If you don't need the triangle, just remove its row.如果您不需要三角形,只需删除它的行。 It would be nice to add a surface to the tooltip.在工具提示中添加一个表面会很好。 https://i.stack.imgur.com/1WY6i.png https://i.stack.imgur.com/1WY6i.png

@ExperimentalComposeUiApi
@ExperimentalAnimationApi
@Composable
fun tooltip(text: String) {

   Row(
            modifier = Modifier
                .padding(horizontal = 16.dp, vertical = 16.dp)
                .fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center
        ) {
            Column {

                Row(modifier = Modifier
                    .padding(PaddingValues(start = 12.dp))
                    .background(
                        color = colors.xxx,
                        shape = TriangleShape(arrowPosition)
                    )
                    .width(arrowSize.width)
                    .height(arrowSize.height)
                ) {}

                Row(modifier = Modifier
                    .background(
                        color = colors.xxx,
                        shape = RoundedCornerShape(size = 3.dp)
                    )
                ) {
                    Text(
                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
                        text = text,
                        alignment = TextAlign.Center,
                    )
                }

            }
        }
}

Function to draw the triangle: Function 绘制三角形:

class TriangleEdge(val position: ArrowPosition) : Shape {

override fun createOutline(
    size: Size,
    layoutDirection: LayoutDirection,
    density: Density
): Outline {

val trianglePath = Path()
trianglePath.apply {
            moveTo(x = size.width/2, y = 0f)
            lineTo(x = size.width, y = size.height)
            lineTo(x = 0f, y = size.height)
        }

return Outline.Generic(path = trianglePath)

} }

At this moment, there is still no official tooltip composable in Jetpack Compose.目前,Jetpack Compose 中仍然没有可组合的官方工具提示。

But a nice tooltip can be effectively constructed with androidx.compose.ui.window.Popup .但是使用androidx.compose.ui.window.Popup可以有效地构建一个漂亮的工具提示。
We can take material DropdownMenu implementation as a starting point.我们可以以 Material DropdownMenu的实现为出发点。

Result example (see source code below):结果示例(参见下面的源代码):

AndroidX Jetpack Compose 的工具提示示例

How to show Tooltip on long click (usage example):如何在长按时显示工具提示(使用示例):

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role

@Composable
@OptIn(ExperimentalFoundationApi::class)
fun TooltipOnLongClickExample(onClick: () -> Unit = {}) {
  // Commonly a Tooltip can be placed in a Box with a sibling
  // that will be used as the 'anchor' for positioning.
  Box {
    val showTooltip = remember { mutableStateOf(false) }

    // Buttons and Surfaces don't support onLongClick out of the box,
    // so use a simple Box with combinedClickable
    Box(
      modifier = Modifier
        .combinedClickable(
          interactionSource = remember { MutableInteractionSource() },
          indication = rememberRipple(),
          onClickLabel = "Button action description",
          role = Role.Button,
          onClick = onClick,
          onLongClick = { showTooltip.value = true },
        ),
    ) {
      Text("Click Me (will show tooltip on long click)")
    }

    Tooltip(showTooltip) {
      // Tooltip content goes here.
      Text("Tooltip Text!!")
    }
  }
}

Tooltip composable source code: Tooltip 可组合源代码:

@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")

import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.core.graphics.ColorUtils
import kotlinx.coroutines.delay


/**
 * Tooltip implementation for AndroidX Jetpack Compose.
 * Based on material [DropdownMenu] implementation
 *
 * A [Tooltip] behaves similarly to a [Popup], and will use the position of the parent layout
 * to position itself on screen. Commonly a [Tooltip] will be placed in a [Box] with a sibling
 * that will be used as the 'anchor'. Note that a [Tooltip] by itself will not take up any
 * space in a layout, as the tooltip is displayed in a separate window, on top of other content.
 *
 * The [content] of a [Tooltip] will typically be [Text], as well as custom content.
 *
 * [Tooltip] changes its positioning depending on the available space, always trying to be
 * fully visible. It will try to expand horizontally, depending on layout direction, to the end of
 * its parent, then to the start of its parent, and then screen end-aligned. Vertically, it will
 * try to expand to the bottom of its parent, then from the top of its parent, and then screen
 * top-aligned. An [offset] can be provided to adjust the positioning of the menu for cases when
 * the layout bounds of its parent do not coincide with its visual bounds. Note the offset will
 * be applied in the direction in which the menu will decide to expand.
 *
 * @param expanded Whether the tooltip is currently visible to the user
 * @param offset [DpOffset] to be added to the position of the tooltip
 *
 * @see androidx.compose.material.DropdownMenu
 * @see androidx.compose.material.DropdownMenuPositionProvider
 * @see androidx.compose.ui.window.Popup
 *
 * @author Artyom Krivolapov
 */
@Composable
fun Tooltip(
  expanded: MutableState<Boolean>,
  modifier: Modifier = Modifier,
  timeoutMillis: Long = TooltipTimeout,
  backgroundColor: Color = Color.Black,
  offset: DpOffset = DpOffset(0.dp, 0.dp),
  properties: PopupProperties = PopupProperties(focusable = true),
  content: @Composable ColumnScope.() -> Unit,
) {
  val expandedStates = remember { MutableTransitionState(false) }
  expandedStates.targetState = expanded.value

  if (expandedStates.currentState || expandedStates.targetState) {
    if (expandedStates.isIdle) {
      LaunchedEffect(timeoutMillis, expanded) {
        delay(timeoutMillis)
        expanded.value = false
      }
    }

    Popup(
      onDismissRequest = { expanded.value = false },
      popupPositionProvider = DropdownMenuPositionProvider(offset, LocalDensity.current),
      properties = properties,
    ) {
      Box(
        // Add space for elevation shadow
        modifier = Modifier.padding(TooltipElevation),
      ) {
        TooltipContent(expandedStates, backgroundColor, modifier, content)
      }
    }
  }
}


/** @see androidx.compose.material.DropdownMenuContent */
@Composable
private fun TooltipContent(
  expandedStates: MutableTransitionState<Boolean>,
  backgroundColor: Color,
  modifier: Modifier,
  content: @Composable ColumnScope.() -> Unit,
) {
  // Tooltip open/close animation.
  val transition = updateTransition(expandedStates, "Tooltip")

  val alpha by transition.animateFloat(
    label = "alpha",
    transitionSpec = {
      if (false isTransitioningTo true) {
        // Dismissed to expanded
        tween(durationMillis = InTransitionDuration)
      } else {
        // Expanded to dismissed.
        tween(durationMillis = OutTransitionDuration)
      }
    }
  ) { if (it) 1f else 0f }

  Card(
    backgroundColor = backgroundColor.copy(alpha = 0.75f),
    contentColor = MaterialTheme.colors.contentColorFor(backgroundColor)
      .takeOrElse { backgroundColor.onColor() },
    modifier = Modifier.alpha(alpha),
    elevation = TooltipElevation,
  ) {
    val p = TooltipPadding
    Column(
      modifier = modifier
        .padding(start = p, top = p * 0.5f, end = p, bottom = p * 0.7f)
        .width(IntrinsicSize.Max),
      content = content,
    )
  }
}

private val TooltipElevation = 16.dp
private val TooltipPadding = 16.dp

// Tooltip open/close animation duration.
private const val InTransitionDuration = 64
private const val OutTransitionDuration = 240

// Default timeout before tooltip close
private const val TooltipTimeout = 2_000L - OutTransitionDuration


// Color utils

/**
 * Calculates an 'on' color for this color.
 *
 * @return [Color.Black] or [Color.White], depending on [isLightColor].
 */
fun Color.onColor(): Color {
  return if (isLightColor()) Color.Black else Color.White
}

/**
 * Calculates if this color is considered light.
 *
 * @return true or false, depending on the higher contrast between [Color.Black] and [Color.White].
 *
 */
fun Color.isLightColor(): Boolean {
  val contrastForBlack = calculateContrastFor(foreground = Color.Black)
  val contrastForWhite = calculateContrastFor(foreground = Color.White)
  return contrastForBlack > contrastForWhite
}

fun Color.calculateContrastFor(foreground: Color): Double {
  return ColorUtils.calculateContrast(foreground.toArgb(), toArgb())
}

Tested with AndroidX Jetpack Compose version 1.1.0-alpha06使用 AndroidX Jetpack Compose 版本1.1.0-alpha06

See a Gist with full example:请参阅带有完整示例的要点:
https://gist.github.com/amal/aad53791308e6edb055f3cf61f881451 https://gist.github.com/amal/aad53791308e6edb055f3cf61f881451

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM