跳转到内容

Tooltip

Tooltip 是 COUI 中用于简短描述锚点元素的组件。参照 Material 3,统一由 TooltipBox 将 tooltip 锚定到内容上;你在它的 tooltip 槽里填入 PlainTooltip(反色表面短标签)或 RichTooltip(带可选标题与操作的 surfaceContainer 卡片)。tooltip 在悬停(光标)或长按(触摸)时显示,也可通过 TooltipState 以编程方式显示。

引入

kotlin
import io.github.suqi8.coui.kmp.basic.TooltipBox
import io.github.suqi8.coui.kmp.basic.RichTooltipBox
import io.github.suqi8.coui.kmp.basic.PlainTooltip
import io.github.suqi8.coui.kmp.basic.RichTooltip
import io.github.suqi8.coui.kmp.basic.TooltipState
import io.github.suqi8.coui.kmp.basic.rememberTooltipState
import io.github.suqi8.coui.kmp.basic.TooltipDefaults
import io.github.suqi8.coui.kmp.basic.TooltipAnchorPosition

基本用法

为纯图标控件加标签,最快的方式是 TooltipBoxtext 便捷重载:

kotlin
TooltipBox(text = "搜索") {
    IconButton(onClick = { /* ... */ }) {
        Icon(
            imageVector = COUIIcons.Search,
            contentDescription = "搜索",
        )
    }
}

TooltipBox

TooltipBox 将 tooltip 锚定到 content。它的 tooltip 槽是一个 TooltipScope lambda,用 PlainTooltipRichTooltip 填充。

kotlin
@Composable
fun TooltipBox(
    positionProvider: PopupPositionProvider,
    tooltip: @Composable TooltipScope.() -> Unit,
    state: TooltipState,
    modifier: Modifier = Modifier,
    focusable: Boolean = false,
    enableUserInput: Boolean = true,
    content: @Composable () -> Unit,
)
参数名类型说明默认值是否必须
positionProviderPopupPositionProvider定位 tooltip,见 TooltipDefaults.rememberTooltipPositionProvider-
tooltip@Composable TooltipScope.() -> Unittooltip 内容(PlainTooltip / RichTooltip-
stateTooltipState控制显隐的状态-
modifierModifier应用于锚点包裹层的修饰符Modifier
focusableBooleantooltip 是否 focusable(可交互的 rich tooltip 需为 true)false
enableUserInputBoolean悬停 / 长按锚点是否显示 tooltiptrue
content@Composable () -> Unit锚点内容-
kotlin
TooltipBox(
    positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
    tooltip = { PlainTooltip { Text("添加到收藏") } },
    state = rememberTooltipState(),
) {
    IconButton(onClick = { /* ... */ }) {
        Icon(imageVector = COUIIcons.Basic.Check, contentDescription = "添加到收藏")
    }
}

PlainTooltip

PlainTooltip 是渲染在 COUI 反色表面上的短标签、不可交互。它是 TooltipScope 扩展,用于 TooltipBoxtooltip 槽内。

kotlin
@Composable
fun TooltipScope.PlainTooltip(
    modifier: Modifier = Modifier,
    caretShape: Shape? = null,
    maxWidth: Dp = TooltipDefaults.PlainTooltipMaxWidth,
    cornerRadius: Dp = TooltipDefaults.PlainTooltipCornerRadius,
    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
    insideMargin: PaddingValues = TooltipDefaults.PlainTooltipInsideMargin,
    content: @Composable () -> Unit,
)

默认情况下 plain tooltip 采用反色表面(浅色主题为深色气泡,深色主题为浅色气泡)。传入 caretShape = TooltipDefaults.caretShape() 可绘制指向锚点的箭头;默认无箭头。

RichTooltip

RichTooltip 是带可选标题与操作的持久、可交互卡片,渲染在普通 surfaceContainer 上。它是 TooltipScope 扩展。

kotlin
@Composable
fun TooltipScope.RichTooltip(
    modifier: Modifier = Modifier,
    title: (@Composable () -> Unit)? = null,
    action: (@Composable () -> Unit)? = null,
    caretShape: Shape? = null,
    maxWidth: Dp = TooltipDefaults.RichTooltipMaxWidth,
    cornerRadius: Dp = TooltipDefaults.RichTooltipCornerRadius,
    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
    insideMargin: PaddingValues = TooltipDefaults.RichTooltipInsideMargin,
    text: @Composable () -> Unit,
)

rich tooltip 可交互,因此需在 TooltipBox 上设 focusable = true(便捷的 RichTooltipBox 已替你设好),以便点外部关闭、操作可达。

kotlin
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()

TooltipBox(
    positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
    tooltip = {
        RichTooltip(
            title = { Text("新功能") },
            action = {
                TextButton(text = "知道了", onClick = { tooltipState.dismiss() })
            },
        ) {
            Text("Rich tooltip 支持标题、说明文字与一个操作。")
        }
    },
    state = tooltipState,
    focusable = true,
) {
    IconButton(onClick = { scope.launch { tooltipState.show() } }) {
        Icon(imageVector = COUIIcons.Basic.Check, contentDescription = "新功能")
    }
}

TooltipState

TooltipState 控制 tooltip 的显隐。全局 MutatorMutex 保证同一时刻只有一个 tooltip 可见。

kotlin
@Stable
interface TooltipState {
    val transition: MutableTransitionState<Boolean>
    val isVisible: Boolean
    val isPersistent: Boolean
    suspend fun show(mutatePriority: MutatePriority = MutatePriority.Default)
    fun dismiss()
    fun onDispose()
}

@Composable
fun rememberTooltipState(
    initialIsVisible: Boolean = false,
    isPersistent: Boolean = false,
    mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex,
): TooltipState
参数名类型说明默认值是否必须
initialIsVisibleBooleantooltip 是否初始可见false
isPersistentBooleantooltip 是否一直显示直到被关闭false
mutatorMutexMutatorMutex保证同一时刻只显示一个 tooltip 的互斥锁BasicTooltipDefaults.GlobalMutatorMutex

isPersistent 为 false(plain tooltip)时,tooltip 在短暂超时后自动消失;为 true(rich tooltip)时,会一直显示直到 dismiss()、点外部或按返回键。show() 是 suspend 函数,需在协程中调用(例如 scope.launch { state.show() })。

TooltipDefaults

TooltipDefaults 提供定位 provider、颜色、caret 与尺寸常量。

定位 provider

kotlin
@Composable
fun TooltipDefaults.rememberTooltipPositionProvider(
    positioning: TooltipAnchorPosition = TooltipAnchorPosition.Below,
    spacingBetweenTooltipAndAnchor: Dp = TooltipDefaults.SpacingBetweenTooltipAndAnchor,
): PopupPositionProvider

TooltipAnchorPosition 选择优先方位:AboveBelowLeftRightStartEnd。tooltip 在交叉轴上居中,空间不足时翻转到对侧。

颜色

kotlin
// plain tooltip(反色表面)
val plainContainer = TooltipDefaults.plainTooltipContainerColor   // onSecondaryVariant
val plainContent = TooltipDefaults.plainTooltipContentColor       // secondaryVariant

// rich tooltip(surface container)
val richColors = TooltipDefaults.richTooltipColors(
    containerColor = COUITheme.colorScheme.surfaceContainer,
    contentColor = COUITheme.colorScheme.onSurfaceContainerVariant,
    titleContentColor = COUITheme.colorScheme.onSurfaceContainer,
    actionContentColor = COUITheme.colorScheme.primary,
)

RichTooltipColors 是包含 containerColorcontentColortitleContentColoractionContentColor 的数据类。

Caret

kotlin
fun TooltipDefaults.caretShape(): Shape
val TooltipDefaults.caretSize: DpSize // 16 x 8 dp

PlainTooltip / RichTooltip 传入 caretShape = TooltipDefaults.caretShape() 即可绘制指向锚点的箭头(针对 Above / Below 方位)。默认无箭头,与 COUI 其余浮层保持一致。

常量

常量名类型说明默认值
SpacingBetweenTooltipAndAnchorDptooltip 与锚点之间的间距8.dp
PlainTooltipMaxWidthDpplain tooltip 的最大宽度200.dp
PlainTooltipCornerRadiusDpplain tooltip 的圆角半径12.dp
PlainTooltipInsideMarginPaddingValuesplain tooltip 的内边距PaddingValues(horizontal = 12.dp, vertical = 8.dp)
RichTooltipMaxWidthDprich tooltip 的最大宽度320.dp
RichTooltipCornerRadiusDprich tooltip 的圆角半径16.dp
RichTooltipInsideMarginPaddingValuesrich tooltip 的内边距PaddingValues(all = 16.dp)
RichTooltipActionCornerRadiusDprich tooltip 操作按钮的圆角半径8.dp
RichTooltipActionInsideMarginPaddingValuesrich tooltip 操作按钮的内边距PaddingValues(horizontal = 12.dp, vertical = 6.dp)
caretSizeDpSizecaret 的尺寸DpSize(16.dp, 8.dp)

便捷重载

常见场景下可以省去定位 provider 与内容组合函数。

Plain tooltip

kotlin
@Composable
fun TooltipBox(
    text: String,
    modifier: Modifier = Modifier,
    state: TooltipState = rememberTooltipState(isPersistent = false),
    enabled: Boolean = true,
    positioning: TooltipAnchorPosition = TooltipAnchorPosition.Below,
    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
    content: @Composable () -> Unit,
)

Rich tooltip

kotlin
@Composable
fun RichTooltipBox(
    text: String,
    modifier: Modifier = Modifier,
    state: TooltipState = rememberTooltipState(isPersistent = true),
    title: String? = null,
    actionText: String? = null,
    onActionClick: (() -> Unit)? = null,
    enabled: Boolean = true,
    positioning: TooltipAnchorPosition = TooltipAnchorPosition.Below,
    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
    content: @Composable () -> Unit,
)

RichTooltipBox 是持久且 focusable 的。通过长按 / 悬停触发,或提升 state 并在锚点自身的点击里调用 state.show()。操作会先调用 onActionClick 再关闭 tooltip。

kotlin
val richState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()

RichTooltipBox(
    title = "Rich tooltip",
    text = "Rich tooltip 支持标题、说明文字与一个操作。",
    actionText = "知道了",
    onActionClick = { /* ... */ },
    state = richState,
) {
    TextButton(
        text = "显示 rich tooltip",
        onClick = { scope.launch { richState.show() } },
    )
}

变更日志

基于 Apache-2.0 许可发布