Я работаю над созданием всплывающей подсказки с курсором, который может иметь разную высоту. Однако Popup
обрезает что-либо за пределами своих границ, если размеры превышают 16.dp по вертикали. Я также попробовал установить PopProperties(clipEnabled = false).
Я начал с ToolTipBox, а затем понял, что проблема связана с самим PopUp. Почему это происходит и есть ли способ снять это ограничение?
Если вы измените высоту курсора выше 16.dp, вы увидите, что часть, превышающая эту высоту, обрезается.
@Preview
@Composable
fun TooltipSample() {
var caretSize by remember {
mutableStateOf(0f)
}
Column(
modifier = Modifier.fillMaxSize()
) {
Text("Caret width: ${caretSize}dp", fontSize = 18.sp)
Slider(
value = caretSize,
onValueChange = {
caretSize = it
},
valueRange = 0f..100f
)
Spacer(modifier = Modifier.height(150.dp))
val state = rememberTooltipState(
isPersistent = true
)
val coroutineScope = rememberCoroutineScope()
Row {
Spacer(modifier = Modifier.width(200.dp))
TooltipBox(
positionProvider = rememberPlainTooltipPositionProvider(
spacingBetweenTooltipAndAnchor = caretSize.dp
),
state = state,
tooltip = {
PlainTooltip(
caretProperties = CaretProperties(
caretWidth = caretSize.dp,
caretHeight = caretSize.dp
),
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
containerColor = Color.Red
) {
Text(
text = "Tooltip Content for testing...",
modifier = Modifier.padding(16.dp)
)
}
},
content = {
Icon(
modifier = Modifier.clickable {
if (state.isVisible) {
state.dismiss()
} else {
coroutineScope.launch {
state.show()
}
}
},
imageVector = Icons.Default.Info,
contentDescription = null
)
}
)
}
}
}
Чтобы отследить, в чем проблема, я также поэкспериментировал с самим PopUp
, где он обрезает синие и желтые круги и прямоугольник с обводкой, которые я собираюсь заменить своей собственной реализацией курсора или стрелки.
@Composable
private fun AnchorContent(
modifier: Modifier
) {
// This is content
Icon(
modifier = modifier,
imageVector = Icons.Default.Info,
contentDescription = null
)
}
@Composable
private fun PopUpBox(
isVisible: Boolean,
onDismissRequest: () -> Unit,
content: @Composable (LayoutCoordinates?) -> Unit,
anchor: @Composable () -> Unit
) {
var anchorBounds: LayoutCoordinates? by remember { mutableStateOf(null) }
val wrappedAnchor: @Composable () -> Unit = {
Box(
modifier = Modifier.onGloballyPositioned { anchorBounds = it }
) {
anchor()
}
}
Box(
modifier = Modifier.border(2.dp, Color.Blue)
) {
if (isVisible) {
Popup(
properties = PopupProperties(clippingEnabled = true),
popupPositionProvider = rememberPlainTooltipPositionProvider(
spacingBetweenTooltipAndAnchor = 20.dp
),
onDismissRequest = onDismissRequest
) {
content(anchorBounds)
}
}
Box {
wrappedAnchor()
}
}
}
И демо для воспроизведения, чтобы выпустить Popup
@Preview
@Composable
private fun PopupTest() {
var showPopup by remember {
mutableStateOf(false)
}
var padding by remember {
mutableFloatStateOf(0f)
}
Column(
modifier = Modifier.fillMaxSize().background(backgroundColor)
) {
Slider(
value = padding,
onValueChange = {
padding = it
},
valueRange = 0f..350f
)
Spacer(modifier = Modifier.height(150.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text("Info")
Spacer(modifier = Modifier.width(padding.dp))
Box {
PopUpBox(
onDismissRequest = {
showPopup = false
},
isVisible = showPopup,
anchor = {
AnchorContent(
modifier = Modifier
.size(80.dp)
.border(2.dp, Color.Green)
.clickable {
showPopup = showPopup.not()
}
)
},
content = { anchorLayoutCoordinates: LayoutCoordinates? ->
Box(
modifier = Modifier
.drawWithContent {
drawContent()
drawCircle(
color = Color.Blue,
radius = 60.dp.toPx(),
center = Offset(
x = size.width / 2,
y = 0f
)
)
drawCircle(
color = Color.Yellow,
radius = 60.dp.toPx(),
center = Offset(
x = size.width / 2,
y = size.height + 30.dp.toPx()
)
)
anchorLayoutCoordinates?.boundsInWindow()?.let {
val width = 60.dp.toPx()
drawRect(
color = Color.Red,
size = Size(width, width),
topLeft = Offset(
x = it.center.x - width / 2,
y = size.height
),
style = Stroke(
2.dp.toPx()
)
)
}
}
.padding(horizontal = 16.dp)
.fillMaxWidth()
.background(
Color.White, RoundedCornerShape(16.dp)
)
.padding(16.dp)
) {
Text(
"This is PopUp Content something something something",
fontSize = 16.sp
)
}
}
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.background(Color.Gray)
)
}
}
Popup
использует PopupLayout
для отображения своего контента. Если вы изучите реализацию PopupLayout
, пользовательского представления Android, созданного с использованием пользовательского интерфейса Jetpack Compose, вы обнаружите следующий код:
class PopupLayout {
...
// On systems older than Android S, there is a bug in the surface insets matrix math used by
// elevation, so high values of maxSupportedElevation break accessibility services: b/232788477.
private val maxSupportedElevation = 8.dp
...
init {
...
// Enable children to draw their shadow by not clipping them
clipChildren = false
// Allocate space for elevation
with(density) { elevation = maxSupportedElevation.toPx() }
// Simple outline to force window manager to allocate space for shadow.
// Note that the outline affects clickable area for the dismiss listener. In case of shapes
// like circle the area for dismiss might be to small (rectangular outline consuming clicks
// outside of the circle).
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, result: Outline) {
result.setRect(0, 0, view.width, view.height)
// We set alpha to 0 to hide the view's shadow and let the composable to draw its
// own shadow. This still enables us to get the extra space needed in the surface.
result.alpha = 0f
}
}
}
}
Код говорит сам за себя, но вот мое объяснение. Каждое представление по умолчанию обрезается до размера своего содержимого, поскольку любое расстояние, превышающее фактический размер, является неожиданным. Однако Popup
обеспечивает 8.dp
интервал, эквивалентный 2x
пикселям на xhdpi
экранах, для рисования теней. Вы рисуете внутри этой области, но не можете выйти за ее пределы, поскольку значение maxSupportedElevation
в PopupLayout
жестко закодировано в 8.dp
.
Вот результаты с повышением 50.dp
.
P.S. Поскольку maxSupportedElevation
является частной собственностью, доступной только для чтения, в PopupLayout
. Я скопировал его реализацию, чтобы проверить ответ.
Ваш ответ не решает эту проблему. Вы помещаете Column
внутрь контента, и он уже находится внутри Popup
. Если вы попытаетесь щелкнуть слева или справа от курсора, вы увидите, что он неактивен. Вы также можете проверить свое решение с помощью рамки вокруг Колонны. То, что я прошу, все еще актуально: вы не можете вытянуть Popup
после 16dp в вертикальном направлении.
Вы можете убедиться в этом, нарисовав что-нибудь внутри холста, например drawCircle(color = Color.Green, radius = 40.dp.toPx(), center = Offset(center.x, size.height))
.
Я проголосовал за этот ответ, потому что именно так я думал, что решу проблему, и я реализовал это в своем руководстве, за исключением того, что я использовал Modifier.layout
вместо Column
и думал об использовании пользовательского модификатора щелчка, чтобы закрыть Popup
, когда пользователь нажимает где-нибудь внутри Popup
, но не в контенте, но не отвечает, почему контент из Popup
обрезается после 16.dp. И вы можете легко проверить, что на Canvas нарисовано что-либо, выходящее за эту границу.
Мое решение по преодолению этой проблемы с зажимом находится здесь github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/…
И причина PlaintToolTip
каретки обрезается после 16dp та же самая причина, по которой она не увеличивает Popup
высоту в зависимости от каретки при рисовании ее за ее пределами, и поэтому она обрезается.
@Thracian Ответ обновлен. Дайте мне знать, если вам нужен код Popup
, который я скопировал из реализации Compose, для проверки ответа.
Спасибо, Аднан. Это отличный ответ. Не могли бы вы опубликовать всю реализацию с помощью PopupLayout для второго изображения. Я уже принимаю ответ, потому что вопрос в том, почему контент обрезается, и на него очень хорошо отвечают.
@Thracian Всегда пожалуйста! Используйте Popup
в своем PopUpBox
из этого файла вместо материала.
Спасибо за ваш ответ. Что вы подразумеваете под
This same offset is applied in your PopupTest example, making it appear clipped.
?