gpt4 book ai didi

android - 修饰符工厂函数不应标记为@Composable

转载 作者:行者123 更新时间:2023-12-05 01:51:53 25 4
gpt4 key购买 nike

我为 Modifier 创建了一个扩展函数,它会在用户单击时向用户显示 BalloonPopup。我为此功能使用了@Composable 标记,因为我将在气球内显示的内容也是@Composable。但是编译器给我下面的警告;

修饰符工厂函数不应标记为@Composable,而应使用 composed 代替

我也应用了建议的更改,但是当用户点击 View 时气球根本不显示。

我的问题是;

  1. 为什么修饰符工厂函数不应标记为@Composable
  2. 对于这样的扩展函数,使用@Composable 和组合{ ... } 有什么区别。因为目前,我还没有看到使用@Composable 标签的任何缺点
  3. 为什么不显示气球,即使我在调试代码时代码也通过了 if (showTooltip) 条件。

下面是我使用的函数的代码,在应用建议之前和之后;

之前:

@Composable
fun Modifier.setPopup(enabled: Boolean = false, content: @Composable BoxScope.() -> Unit): Modifier {
if (enabled) {
var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
var showTooltip by remember { mutableStateOf(false) }
if (showTooltip) {
BalloonPopup(
onDismissRequest = {
showTooltip = false
},
content = content,
anchorCoordinates = anchorOffset
)
}
return this.clickable {
showTooltip = true
}.onGloballyPositioned {
anchorOffset = it
}
} else {
return this
}
}

之后:

fun Modifier.setPopup(enabled: Boolean = false, content: @Composable BoxScope.() -> Unit): Modifier = composed {
if (enabled) {
var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
var showTooltip by remember { mutableStateOf(false) }
if (showTooltip) {
BalloonPopup(
onDismissRequest = {
showTooltip = false
},
content = content,
anchorCoordinates = anchorOffset
)
}
this.clickable {
showTooltip = true
}.onGloballyPositioned {
anchorOffset = it
}
} else {
this
}
}

这就是我调用扩展函数的方式;

Image(
modifier = Modifier
.setPopup(enabled = true) {
Text(
modifier = Modifier.padding(4.dp),
text = "-30 rssi",
fontSize = 13.sp
)
},
painter = painterResource(id = android.R.drawable.ic_secure),
contentDescription = "Signal Strength"
)

这是在扩展函数中使用的 BalloonPopup 类;

suspend fun initTimer(time: Long, onEnd: () -> Unit) {
delay(timeMillis = time)
onEnd()
}

@Composable
fun BalloonPopup(
cornerRadius: Float = 8f,
arrowSize: Float = 32f,
dismissTime: Long = 3,
onDismissRequest: (() -> Unit)? = null,
anchorCoordinates: LayoutCoordinates? = null,
content: @Composable BoxScope.() -> Unit
) {
if (anchorCoordinates != null) {
var arrowPosition by remember { mutableStateOf(BalloonShape.ArrowPosition.TOP_RIGHT) }

/**
* copied from AlignmentOffsetPositionProvider of android sdk and added the calculation for
* arrowPosition
* */
class BalloonPopupPositionProvider(
val alignment: Alignment,
val offset: IntOffset
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
// TODO: Decide which is the best way to round to result without reimplementing Alignment.align
var popupPosition = IntOffset(0, 0)

// Get the aligned point inside the parent
val parentAlignmentPoint = alignment.align(
IntSize.Zero,
IntSize(anchorBounds.width, anchorBounds.height),
layoutDirection
)
// Get the aligned point inside the child
val relativePopupPos = alignment.align(
IntSize.Zero,
IntSize(popupContentSize.width, popupContentSize.height),
layoutDirection
)

// Add the position of the parent
popupPosition += IntOffset(anchorBounds.left, anchorBounds.top)

// Add the distance between the parent's top left corner and the alignment point
popupPosition += parentAlignmentPoint

// Subtract the distance between the children's top left corner and the alignment point
popupPosition -= IntOffset(relativePopupPos.x, relativePopupPos.y)

// Add the user offset
val resolvedOffset = IntOffset(
offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
offset.y
)
popupPosition += resolvedOffset

arrowPosition =
if (anchorBounds.left > popupPosition.x) {
BalloonShape.ArrowPosition.TOP_RIGHT
} else {
BalloonShape.ArrowPosition.TOP_LEFT
}

return popupPosition
}
}

var isVisible by remember { mutableStateOf(false) }

AnimatedVisibility(visible = isVisible) {
Popup(
popupPositionProvider = BalloonPopupPositionProvider(
alignment = Alignment.TopCenter,
offset = IntOffset(
x = anchorCoordinates.positionInParent().x.roundToInt(),
y = anchorCoordinates.positionInParent().y.roundToInt() + anchorCoordinates.size.height
)
),
onDismissRequest = onDismissRequest
) {
Box(
modifier = Modifier
.wrapContentSize()
.shadow(
dimensionResource(id = R.dimen.cardview_default_elevation),
shape = BalloonShape(
cornerRadius = cornerRadius,
arrowSize = arrowSize,
arrowPosition = arrowPosition,
anchorCoordinates = anchorCoordinates
)
)
.border(
Dp.Hairline, CCTechAppDefaultTheme.primary,
shape = BalloonShape(
cornerRadius = cornerRadius,
arrowSize = arrowSize,
arrowPosition = arrowPosition,
anchorCoordinates = anchorCoordinates
)
)
.background(
shape = BalloonShape(
cornerRadius = cornerRadius,
arrowSize = arrowSize,
arrowPosition = arrowPosition,
anchorCoordinates = anchorCoordinates
),
color = CCTechAppDefaultTheme.surface
)
.padding(top = arrowSize.toDP())
) {
content()
}
}
}

val coroutineScope = rememberCoroutineScope()
LaunchedEffect(key1 = Unit, block = {
isVisible = true
coroutineScope.launch {
initTimer(dismissTime * 1000) {
isVisible = false
(onDismissRequest ?: {}).invoke()
}
}
})

}
}

class BalloonShape(
private val cornerRadius: Float,
private val arrowSize: Float,
var arrowPosition: ArrowPosition = ArrowPosition.TOP_RIGHT,
private val anchorCoordinates: LayoutCoordinates
) : Shape {

enum class ArrowPosition {
TOP_LEFT, TOP_RIGHT
}

override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
val balloonLeft = 0f
val balloonRight = size.width
val balloonTop = 0f + arrowSize
val balloonBottom = size.height

val arrowTopY = 0f
val arrowTopX =
if (arrowPosition == ArrowPosition.TOP_LEFT) balloonLeft + anchorCoordinates.size.width / 2
else balloonRight - anchorCoordinates.size.width / 2
val arrowLeftX = arrowTopX - (arrowSize / 2f)
val arrowLeftY = arrowTopY + arrowSize
val arrowRightX = arrowTopX + (arrowSize / 2f)
val arrowRightY = arrowTopY + arrowSize


val path = Path().apply {
moveTo(
x = arrowTopX,
y = arrowTopY
) // Start point on the top of the arrow
lineTo(
x = arrowLeftX,
y = arrowLeftY
) // Left edge of the arrow
lineTo(
x = balloonLeft,
y = balloonTop
) // TopLeft edge of the rectangle
arcTo(
rect = Rect(
left = balloonLeft,
top = balloonTop,
right = balloonLeft + cornerRadius * 2f,
bottom = balloonTop + cornerRadius * 2f
),
startAngleDegrees = 270f,
sweepAngleDegrees = -90f,
forceMoveTo = false
)
lineTo(
x = balloonLeft,
y = balloonBottom
) // Left edge of the rectangle
arcTo(
rect = Rect(
left = balloonLeft,
top = balloonBottom - cornerRadius * 2f,
right = balloonLeft + cornerRadius * 2f,
bottom = balloonBottom
),
startAngleDegrees = 180f,
sweepAngleDegrees = -90f,
forceMoveTo = false
)
lineTo(
x = balloonRight,
y = balloonBottom
) // Bottom edge of the rectangle
arcTo(
rect = Rect(
left = balloonRight - cornerRadius * 2f,
top = balloonBottom - cornerRadius * 2f,
right = balloonRight,
bottom = balloonBottom
),
startAngleDegrees = 90f,
sweepAngleDegrees = -90f,
forceMoveTo = false
)
lineTo(
x = balloonRight,
y = balloonTop
) // Right edge of the rectangle
arcTo(
rect = Rect(
left = balloonRight - cornerRadius * 2f,
top = balloonTop,
right = balloonRight,
bottom = balloonTop + cornerRadius * 2f
),
startAngleDegrees = 0f,
sweepAngleDegrees = -90f,
forceMoveTo = false
)
lineTo(
x = arrowRightX,
y = arrowRightY
) // TopRight edge of the rectangle
close()
}
return Outline.Generic(path)
}
}

提前致谢。

最佳答案

根据 Compose 修饰符 guidelines :

As a result, Jetpack Compose framework development and Library development SHOULD use Modifier.composed {} to implement composition-aware modifiers, and SHOULD NOT declare modifier extension factory functions as @Composable functions themselves.

为什么

Composed modifiers may be created outside of composition, shared across elements, and declared as top-level constants, making them more flexible than modifiers that can only be created via a @Composable function call, and easier to avoid accidentally sharing state across elements.

在修饰符中使用 View 有点老套。修饰符应更改应用它的 View 的状态,并且不应影响环境。

您的代码适用于 @Composable,因为它会被立即调用,并且弹出窗口会添加到 View 树中,就好像它是在调用 View 之前添加的一样。

使用 composed,当它在渲染之前开始测量 View 的位置时,稍后会调用内容 - 因为此代码不是 View 树的一部分,所以您的弹出窗口不会添加到其中。

使用 composed 代码,以便您可以使用 remember 保存状态,还可以使用 LocalDensity 等局部值,但不能用于添加意见。

你几乎可以在你的代码库中做任何你想做的事情,毕竟你可以让这个警告静音,但是大多数看到你代码的人不会期望这样的修饰符在 View 树中添加一个 View ——这就是指南的目的.

我认为实现这样一个特性的预期方式如下(不确定命名,through):

@Composable
fun BalloonPopupRequester(
requesterView: @Composable (Modifier) -> Unit,
popupContent: @Composable BoxScope.() -> Unit
) {
var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
var showTooltip by remember { mutableStateOf(false) }
if (showTooltip) {
BalloonPopup(
onDismissRequest = {
showTooltip = false
},
content = popupContent,
anchorCoordinates = anchorOffset
)
}
requesterView(
Modifier
.clickable {
println("clickable")
showTooltip = true
}
.onGloballyPositioned {
anchorOffset = it
}
)
}

用法:

BalloonPopupRequester(
requesterView = { modifier ->
Image(
modifier = modifier,
painter = painterResource(id = android.R.drawable.ic_secure),
contentDescription = "Signal Strength"
)
},
popupContent = {
Text(
modifier = Modifier.padding(4.dp),
text = "-30 rssi",
fontSize = 13.sp
)
}
)

关于android - 修饰符工厂函数不应标记为@Composable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71910699/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com