对于用过一段时间的 Compose 开发者来说,Compose 目前在完善性上还有一些差距,比如原生的 ToolTips,当然网上也有很多库用来支持,但我觉得有点大材小用。
毕竟,一个 ToolTips 只需要排列一下,然后一定时间内消失就行,于是自己写一下吧,不考虑边界还是灰常简单的~
拆解
主要拆解为四个部分:
一些基础属性定义:
private var basePositionMap = mutableMapOf<String, Offset>()
private var baseSizeMap = mutableMapOf<String, IntSize>()
private var tips by mutableStateOf("")
private var tipsDismissJob: Job? = nullModifier 修饰符定义,主要是监听手势操作:
@Stable
fun Modifier.toolTips(
onClick: () -> Unit, desc: String
) = this.then(
Modifier
.onGloballyPositioned {
basePositionMap[desc] = it.positionInRoot()
baseSizeMap[desc] = it.size
}
.pointerInput(Unit) {
detectTapGestures(onTap = {
onClick.invoke()
}, onLongPress = {
tips = desc
})
}
)ToolTips 控件本身:
@Composable
fun ToolTips(content: @Composable () -> Unit = { DefaultToolTips() }) {
if (tips.isNotEmpty()) {
ToolTipsBox(Modifier) {
content()
}
}
val scope = LocalLifecycleOwner.current.lifecycleScope
LaunchedEffect(tips) {
tipsDismissJob?.cancel()
tipsDismissJob = scope.launch {
delay(2000)
if (isActive) {
tips = ""
}
}
}
}默认 ToolTips 样式:
@Composable
fun DefaultToolTips() {
Box(
modifier = Modifier
.clip(Shapes2)
.background(color = colorResource(id = R.color.colorOnContent))
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Text(text = tips, fontSize = 12.sp, color = colorResource(id = R.color.colorContent))
}
}用于布局 TipsBox 位置:
@Composable
private fun ToolTipsBox(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val position = basePositionMap[tips] ?: return
val size = baseSizeMap[tips] ?: return
val y = with(LocalDensity.current) { position.y + 32.dp.toPx() }
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
layout(constraints.minWidth, constraints.minHeight) {
placeables.forEach { placeable ->
placeable.placeRelative(x = (position.x - placeable.width / 2f + size.width / 2f).toInt(), y = y.toInt())
}
}
}
}用法:
setContent {
Box(modifier = Modifier.fillMaxWidth()) {
val contentDescription = "描述文字"
Icon(
modifier = Modifier
//...
.toolTips(onClick = {
//...
}, contentDescription)
)
ToolTips()
}
}至于思路,就是存下了长按控件的位置、大小,然后再根据这个位置布局一下 ToolTips 的位置,最后这个 ToolTips 会在一定时间后消失,就这么简单。
但真用起来,其实问题不少,比如可能超过父布局边界,比如可能需要换个方位,就自行调整或者直接用别人设计的更具备实用性的库吧,毕竟我这就图个轻便~
更新
修整得规范了些,越界问题也处理了,用法类似,只是说 需要多传一个 rememberToolTipsState,避免离开界面了,这玩意儿还没消失。
@Composable
fun rememberToolTipsState(marginTop: Int = 0, marginHorizontal: Int = 0): ToolTipState {
return remember {
ToolTipState().apply {
this.marginTop = marginTop
this.marginHorizontal = marginHorizontal
}
}
}
class ToolTipState {
val positionMap = mutableMapOf<String, Offset>()
val sizeMap = mutableMapOf<String, IntSize>()
var tips by mutableStateOf("")
private set
var tipsDismissJob: Job? = null
var marginTop: Int = 0
var marginHorizontal: Int = 0
fun show(tips: String) {
this.tips = tips
}
fun dismiss() {
tips = ""
}
}
@Stable
fun Modifier.toolTips(
onClick: () -> Unit = {}, desc: String, state: ToolTipState
) = this.then(
Modifier
.onGloballyPositioned {
state.positionMap[desc] = it.positionInRoot()
state.sizeMap[desc] = it.size
}
.pointerInput(Unit) {
detectTapGestures(onTap = {
onClick.invoke()
}, onLongPress = {
state.show(desc)
})
}
)
@Composable
fun ToolTips(modifier: Modifier = Modifier, state: ToolTipState, content: @Composable () -> Unit = { DefaultToolTips(state) }) {
val visible = state.tips.isNotEmpty()
state.tipsDismissJob?.cancel()
AnimatedVisibility(enter = fadeIn(), exit = fadeOut(), visible = visible) {
ToolTipsBox(modifier, state) {
content()
}
}
if (visible) {
LaunchedEffect(state.tips) {
state.tipsDismissJob = launch {
delay(3000)
if (isActive) {
state.dismiss()
}
}
}
}
}以上!
本站广告由 Google AdSense 提供
0条评论