对于用过一段时间的 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? = null
Modifier 修饰符定义,主要是监听手势操作:
@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() } } } } }
以上!
本站由以下主机服务商提供服务支持:
0条评论