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

px9o7tmv  于 2024-01-04  发布在  Android
关注(0)|答案(1)|浏览(155)

我已经为Modifier创建了一个扩展函数,当用户点击它时,它会显示一个Balloon弹出窗口。我使用@Composable标签来实现这个函数,因为我将在Balloon中显示的内容也是@Composable的。但是编译器给了我下面的警告:

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

我还应用了建议的更改,但当用户单击视图时,气球根本不显示。
我的问题是:
1.为什么 * 修改器工厂函数不应该标记为@Composable*?
1.对于这样的扩展函数,使用@Composable和composed {...}有什么区别?因为目前为止,我还没有看到使用@Composable标签的任何缺点
1.为什么气球没有显示,即使代码在调试代码时也会传递if(showTooltip)条件。
下面是我使用的函数的代码,包括应用建议之前和之后的代码;
之前:

  1. @Composable
  2. fun Modifier.setPopup(enabled: Boolean = false, content: @Composable BoxScope.() -> Unit): Modifier {
  3. if (enabled) {
  4. var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
  5. var showTooltip by remember { mutableStateOf(false) }
  6. if (showTooltip) {
  7. BalloonPopup(
  8. onDismissRequest = {
  9. showTooltip = false
  10. },
  11. content = content,
  12. anchorCoordinates = anchorOffset
  13. )
  14. }
  15. return this.clickable {
  16. showTooltip = true
  17. }.onGloballyPositioned {
  18. anchorOffset = it
  19. }
  20. } else {
  21. return this
  22. }
  23. }

字符串
之后:

  1. fun Modifier.setPopup(enabled: Boolean = false, content: @Composable BoxScope.() -> Unit): Modifier = composed {
  2. if (enabled) {
  3. var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
  4. var showTooltip by remember { mutableStateOf(false) }
  5. if (showTooltip) {
  6. BalloonPopup(
  7. onDismissRequest = {
  8. showTooltip = false
  9. },
  10. content = content,
  11. anchorCoordinates = anchorOffset
  12. )
  13. }
  14. this.clickable {
  15. showTooltip = true
  16. }.onGloballyPositioned {
  17. anchorOffset = it
  18. }
  19. } else {
  20. this
  21. }
  22. }


这就是我如何调用扩展函数;

  1. Image(
  2. modifier = Modifier
  3. .setPopup(enabled = true) {
  4. Text(
  5. modifier = Modifier.padding(4.dp),
  6. text = "-30 rssi",
  7. fontSize = 13.sp
  8. )
  9. },
  10. painter = painterResource(id = android.R.drawable.ic_secure),
  11. contentDescription = "Signal Strength"
  12. )


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

  1. suspend fun initTimer(time: Long, onEnd: () -> Unit) {
  2. delay(timeMillis = time)
  3. onEnd()
  4. }
  5. @Composable
  6. fun BalloonPopup(
  7. cornerRadius: Float = 8f,
  8. arrowSize: Float = 32f,
  9. dismissTime: Long = 3,
  10. onDismissRequest: (() -> Unit)? = null,
  11. anchorCoordinates: LayoutCoordinates? = null,
  12. content: @Composable BoxScope.() -> Unit
  13. ) {
  14. if (anchorCoordinates != null) {
  15. var arrowPosition by remember { mutableStateOf(BalloonShape.ArrowPosition.TOP_RIGHT) }
  16. /**
  17. * copied from AlignmentOffsetPositionProvider of android sdk and added the calculation for
  18. * arrowPosition
  19. * */
  20. class BalloonPopupPositionProvider(
  21. val alignment: Alignment,
  22. val offset: IntOffset
  23. ) : PopupPositionProvider {
  24. override fun calculatePosition(
  25. anchorBounds: IntRect,
  26. windowSize: IntSize,
  27. layoutDirection: LayoutDirection,
  28. popupContentSize: IntSize
  29. ): IntOffset {
  30. // TODO: Decide which is the best way to round to result without reimplementing Alignment.align
  31. var popupPosition = IntOffset(0, 0)
  32. // Get the aligned point inside the parent
  33. val parentAlignmentPoint = alignment.align(
  34. IntSize.Zero,
  35. IntSize(anchorBounds.width, anchorBounds.height),
  36. layoutDirection
  37. )
  38. // Get the aligned point inside the child
  39. val relativePopupPos = alignment.align(
  40. IntSize.Zero,
  41. IntSize(popupContentSize.width, popupContentSize.height),
  42. layoutDirection
  43. )
  44. // Add the position of the parent
  45. popupPosition += IntOffset(anchorBounds.left, anchorBounds.top)
  46. // Add the distance between the parent's top left corner and the alignment point
  47. popupPosition += parentAlignmentPoint
  48. // Subtract the distance between the children's top left corner and the alignment point
  49. popupPosition -= IntOffset(relativePopupPos.x, relativePopupPos.y)
  50. // Add the user offset
  51. val resolvedOffset = IntOffset(
  52. offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
  53. offset.y
  54. )
  55. popupPosition += resolvedOffset
  56. arrowPosition =
  57. if (anchorBounds.left > popupPosition.x) {
  58. BalloonShape.ArrowPosition.TOP_RIGHT
  59. } else {
  60. BalloonShape.ArrowPosition.TOP_LEFT
  61. }
  62. return popupPosition
  63. }
  64. }
  65. var isVisible by remember { mutableStateOf(false) }
  66. AnimatedVisibility(visible = isVisible) {
  67. Popup(
  68. popupPositionProvider = BalloonPopupPositionProvider(
  69. alignment = Alignment.TopCenter,
  70. offset = IntOffset(
  71. x = anchorCoordinates.positionInParent().x.roundToInt(),
  72. y = anchorCoordinates.positionInParent().y.roundToInt() + anchorCoordinates.size.height
  73. )
  74. ),
  75. onDismissRequest = onDismissRequest
  76. ) {
  77. Box(
  78. modifier = Modifier
  79. .wrapContentSize()
  80. .shadow(
  81. dimensionResource(id = R.dimen.cardview_default_elevation),
  82. shape = BalloonShape(
  83. cornerRadius = cornerRadius,
  84. arrowSize = arrowSize,
  85. arrowPosition = arrowPosition,
  86. anchorCoordinates = anchorCoordinates
  87. )
  88. )
  89. .border(
  90. Dp.Hairline, CCTechAppDefaultTheme.primary,
  91. shape = BalloonShape(
  92. cornerRadius = cornerRadius,
  93. arrowSize = arrowSize,
  94. arrowPosition = arrowPosition,
  95. anchorCoordinates = anchorCoordinates
  96. )
  97. )
  98. .background(
  99. shape = BalloonShape(
  100. cornerRadius = cornerRadius,
  101. arrowSize = arrowSize,
  102. arrowPosition = arrowPosition,
  103. anchorCoordinates = anchorCoordinates
  104. ),
  105. color = CCTechAppDefaultTheme.surface
  106. )
  107. .padding(top = arrowSize.toDP())
  108. ) {
  109. content()
  110. }
  111. }
  112. }
  113. val coroutineScope = rememberCoroutineScope()
  114. LaunchedEffect(key1 = Unit, block = {
  115. isVisible = true
  116. coroutineScope.launch {
  117. initTimer(dismissTime * 1000) {
  118. isVisible = false
  119. (onDismissRequest ?: {}).invoke()
  120. }
  121. }
  122. })
  123. }
  124. }
  125. class BalloonShape(
  126. private val cornerRadius: Float,
  127. private val arrowSize: Float,
  128. var arrowPosition: ArrowPosition = ArrowPosition.TOP_RIGHT,
  129. private val anchorCoordinates: LayoutCoordinates
  130. ) : Shape {
  131. enum class ArrowPosition {
  132. TOP_LEFT, TOP_RIGHT
  133. }
  134. override fun createOutline(
  135. size: Size,
  136. layoutDirection: LayoutDirection,
  137. density: Density
  138. ): Outline {
  139. val balloonLeft = 0f
  140. val balloonRight = size.width
  141. val balloonTop = 0f + arrowSize
  142. val balloonBottom = size.height
  143. val arrowTopY = 0f
  144. val arrowTopX =
  145. if (arrowPosition == ArrowPosition.TOP_LEFT) balloonLeft + anchorCoordinates.size.width / 2
  146. else balloonRight - anchorCoordinates.size.width / 2
  147. val arrowLeftX = arrowTopX - (arrowSize / 2f)
  148. val arrowLeftY = arrowTopY + arrowSize
  149. val arrowRightX = arrowTopX + (arrowSize / 2f)
  150. val arrowRightY = arrowTopY + arrowSize
  151. val path = Path().apply {
  152. moveTo(
  153. x = arrowTopX,
  154. y = arrowTopY
  155. ) // Start point on the top of the arrow
  156. lineTo(
  157. x = arrowLeftX,
  158. y = arrowLeftY
  159. ) // Left edge of the arrow
  160. lineTo(
  161. x = balloonLeft,
  162. y = balloonTop
  163. ) // TopLeft edge of the rectangle
  164. arcTo(
  165. rect = Rect(
  166. left = balloonLeft,
  167. top = balloonTop,
  168. right = balloonLeft + cornerRadius * 2f,
  169. bottom = balloonTop + cornerRadius * 2f
  170. ),
  171. startAngleDegrees = 270f,
  172. sweepAngleDegrees = -90f,
  173. forceMoveTo = false
  174. )
  175. lineTo(
  176. x = balloonLeft,
  177. y = balloonBottom
  178. ) // Left edge of the rectangle
  179. arcTo(
  180. rect = Rect(
  181. left = balloonLeft,
  182. top = balloonBottom - cornerRadius * 2f,
  183. right = balloonLeft + cornerRadius * 2f,
  184. bottom = balloonBottom
  185. ),
  186. startAngleDegrees = 180f,
  187. sweepAngleDegrees = -90f,
  188. forceMoveTo = false
  189. )
  190. lineTo(
  191. x = balloonRight,
  192. y = balloonBottom
  193. ) // Bottom edge of the rectangle
  194. arcTo(
  195. rect = Rect(
  196. left = balloonRight - cornerRadius * 2f,
  197. top = balloonBottom - cornerRadius * 2f,
  198. right = balloonRight,
  199. bottom = balloonBottom
  200. ),
  201. startAngleDegrees = 90f,
  202. sweepAngleDegrees = -90f,
  203. forceMoveTo = false
  204. )
  205. lineTo(
  206. x = balloonRight,
  207. y = balloonTop
  208. ) // Right edge of the rectangle
  209. arcTo(
  210. rect = Rect(
  211. left = balloonRight - cornerRadius * 2f,
  212. top = balloonTop,
  213. right = balloonRight,
  214. bottom = balloonTop + cornerRadius * 2f
  215. ),
  216. startAngleDegrees = 0f,
  217. sweepAngleDegrees = -90f,
  218. forceMoveTo = false
  219. )
  220. lineTo(
  221. x = arrowRightX,
  222. y = arrowRightY
  223. ) // TopRight edge of the rectangle
  224. close()
  225. }
  226. return Outline.Generic(path)
  227. }
  228. }


先谢了。

iaqfqrcu

iaqfqrcu1#

不再建议使用composable构建修饰符,因此此警告不应出现在新的Compose版本中。
请注意,您仍然应该小心,不要向视图层次结构添加任何视图,并且只使用可组合范围来存储/动画数据。
根据“合成”修改器准则:
因此,Jetpack Compose框架开发和Library开发SHOULD使用Modifier.composed {}来实现组合感知修饰符,而SHOULD NOT将修饰符扩展工厂函数声明为@Composable函数本身。
为什么
复合修饰符可以在复合之外创建,在元素之间共享,并声明为顶级常量,使它们比只能通过@Composable函数调用创建的修饰符更灵活,更容易避免意外地在元素之间共享状态。
在修改器中使用视图是一种很奇怪的做法,修改器应该改变它所应用的视图的状态,而不应该影响环境。
您的代码可以与@Composable一起工作,因为它是即时调用的,并且弹出窗口被添加到视图树中,就像它被添加到调用视图之前一样。
对于composed,当它在渲染之前开始测量视图的位置时,内容会在稍后被调用-因为这段代码不是视图树的一部分,所以你的弹出窗口不会被添加到其中。
使用composed代码是为了让您可以使用remember保存状态,也可以使用本地值(如LocalDensity),但不能用于添加视图。
你可以在你的代码库中做任何你想做的事情,毕竟,你可以让这个警告静音,但是大多数看到你的代码的人不会想到这样的修饰符在视图树中添加一个视图--这就是指南的目的。
我认为实现这样一个功能的预期方式如下(不确定命名,通过):

  1. @Composable
  2. fun BalloonPopupRequester(
  3. requesterView: @Composable (Modifier) -> Unit,
  4. popupContent: @Composable BoxScope.() -> Unit
  5. ) {
  6. var anchorOffset by remember { mutableStateOf<LayoutCoordinates?>(null) }
  7. var showTooltip by remember { mutableStateOf(false) }
  8. if (showTooltip) {
  9. BalloonPopup(
  10. onDismissRequest = {
  11. showTooltip = false
  12. },
  13. content = popupContent,
  14. anchorCoordinates = anchorOffset
  15. )
  16. }
  17. requesterView(
  18. Modifier
  19. .clickable {
  20. println("clickable")
  21. showTooltip = true
  22. }
  23. .onGloballyPositioned {
  24. anchorOffset = it
  25. }
  26. )
  27. }

字符串
使用方法:

  1. BalloonPopupRequester(
  2. requesterView = { modifier ->
  3. Image(
  4. modifier = modifier,
  5. painter = painterResource(id = android.R.drawable.ic_secure),
  6. contentDescription = "Signal Strength"
  7. )
  8. },
  9. popupContent = {
  10. Text(
  11. modifier = Modifier.padding(4.dp),
  12. text = "-30 rssi",
  13. fontSize = 13.sp
  14. )
  15. }
  16. )

展开查看全部

相关问题