Jetpack Compose 带缩进的文本背景
Jetpack Compose text background with indent
我是 Jetpack Compose 的新手,我想知道这是否可以通过 Jetpack Compose 实现。使用 Compose 可以很容易地为文本添加背景,但是如果您想根据文本位置为背景添加缩进,我不知道从哪里开始才能实现这种效果。
Text background with indent
我这样做了:
@Composable
fun IBgText(
text: String,
modifier: Modifier = Modifier,
iBgStrokeWidth: Float? = null,
iBgStrokeColor: Color = Color.DarkGray,
iBgVerticalPadding: Dp = 0.dp,
iBgHorizontalPadding: Dp = 0.dp,
iBgCornerRadius: Dp = 0.dp,
iBgColor: Color = Color.Gray,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
style: TextStyle = LocalTextStyle.current
) {
val vSpace = with(LocalDensity.current) { iBgVerticalPadding.toPx() }
val hSpace = with(LocalDensity.current) { iBgHorizontalPadding.toPx() }
val corner = with(LocalDensity.current) { iBgCornerRadius.toPx() }
var path by remember { mutableStateOf(Path()) }
fun computePath(layoutResult: TextLayoutResult): Path {
fun isInnerCorner(
lr: TextLayoutResult,
i: Int,
top: Boolean = false,
right: Boolean
): Boolean {
if (top && i == 0) return false
if (!top && i == lr.lineCount - 1) return false
if (top && right) return lr.getLineRight(i - 1) > lr.getLineRight(i)
if (!top && right) return lr.getLineRight(i + 1) > lr.getLineRight(i)
if (top && !right) return lr.getLineLeft(i - 1) < lr.getLineLeft(i)
return lr.getLineLeft(i + 1) < lr.getLineLeft(i)
}
val nbLines = layoutResult.lineCount
for (i in 0 until nbLines) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val right = layoutResult.getLineRight(i) + hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = true)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = true)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (i == 0) {
moveTo(right - corner, top)
} else {
if (topInner) {
lineTo(right + corner, top)
} else {
lineTo(right - corner, top)
}
}
quadraticBezierTo(right, top, right, top + corner)
lineTo(right, bottom - corner)
if (bottomInner) {
quadraticBezierTo(right, bottom, right + corner, bottom)
} else {
quadraticBezierTo(right, bottom, right - corner, bottom)
}
}
}
for (i in (nbLines - 1) downTo 0) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val left = layoutResult.getLineLeft(i) - hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = false)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = false)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (bottomInner) {
lineTo(left - corner, bottom)
} else {
lineTo(left + corner, bottom)
}
quadraticBezierTo(left, bottom, left, bottom - corner)
lineTo(left, top + corner)
if (topInner) {
quadraticBezierTo(left, top, left - corner, top)
} else {
quadraticBezierTo(left, top, left + corner, top)
}
}
}
path.close()
return path
}
Text(
text,
onTextLayout = { layoutResult ->
path = computePath(layoutResult = layoutResult)
},
modifier = modifier.drawBehind {
drawPath(path, style = Fill, color = iBgColor)
if (iBgStrokeWidth != null) {
drawPath(path, style = Stroke(width = iBgStrokeWidth), color = iBgStrokeColor)
}
},
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
style = style
)
}
用法:
@Preview
@Composable
fun Preview() {
Column (modifier = Modifier.fillMaxSize().background(Color.Black)){
IBgText(
text = "test\ntest test\ntest\ntest test",
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 6.dp, start = 10.dp, end = 10.dp, bottom = 6.dp),
iBgColor = Color.Blue.copy(alpha = .4f),
iBgStrokeWidth = 3f,
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 5.dp,
iBgStrokeColor = Color.Red,
iBgVerticalPadding = 1.dp,
color = Color.White
)
IBgText(
text = "This is a sample\ntext",
textAlign = TextAlign.Center,
modifier = Modifier.padding(20.dp, bottom = 6.dp).rotate(-15f),
iBgColor = Color(0xFFFFFFFF),
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 8.dp,
iBgVerticalPadding = 5.dp
)
IBgText(
text = "line 1\n-- line 2 --",
textAlign = TextAlign.End,
modifier = Modifier.padding(10.dp)
)
}
}
结果:
它不是很漂亮,但我想它可以完成工作。
onTextLayout = { layoutResult ->给出每行的边界。
然后你可以path遍历每一行。
我从右上角(第一行结束)到右下角(最后一行结束)做了一个循环
然后从左下角(最后一行开始)到左上角(第一行开始)再循环一次。
然后我添加了一些圆角来匹配图片。
Ps:我这周开始使用 Kotlin 和 Jetpack compose,整个星期天都在回答你的问题
我是 Jetpack Compose 的新手,我想知道这是否可以通过 Jetpack Compose 实现。使用 Compose 可以很容易地为文本添加背景,但是如果您想根据文本位置为背景添加缩进,我不知道从哪里开始才能实现这种效果。
Text background with indent
我这样做了:
@Composable
fun IBgText(
text: String,
modifier: Modifier = Modifier,
iBgStrokeWidth: Float? = null,
iBgStrokeColor: Color = Color.DarkGray,
iBgVerticalPadding: Dp = 0.dp,
iBgHorizontalPadding: Dp = 0.dp,
iBgCornerRadius: Dp = 0.dp,
iBgColor: Color = Color.Gray,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
style: TextStyle = LocalTextStyle.current
) {
val vSpace = with(LocalDensity.current) { iBgVerticalPadding.toPx() }
val hSpace = with(LocalDensity.current) { iBgHorizontalPadding.toPx() }
val corner = with(LocalDensity.current) { iBgCornerRadius.toPx() }
var path by remember { mutableStateOf(Path()) }
fun computePath(layoutResult: TextLayoutResult): Path {
fun isInnerCorner(
lr: TextLayoutResult,
i: Int,
top: Boolean = false,
right: Boolean
): Boolean {
if (top && i == 0) return false
if (!top && i == lr.lineCount - 1) return false
if (top && right) return lr.getLineRight(i - 1) > lr.getLineRight(i)
if (!top && right) return lr.getLineRight(i + 1) > lr.getLineRight(i)
if (top && !right) return lr.getLineLeft(i - 1) < lr.getLineLeft(i)
return lr.getLineLeft(i + 1) < lr.getLineLeft(i)
}
val nbLines = layoutResult.lineCount
for (i in 0 until nbLines) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val right = layoutResult.getLineRight(i) + hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = true)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = true)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (i == 0) {
moveTo(right - corner, top)
} else {
if (topInner) {
lineTo(right + corner, top)
} else {
lineTo(right - corner, top)
}
}
quadraticBezierTo(right, top, right, top + corner)
lineTo(right, bottom - corner)
if (bottomInner) {
quadraticBezierTo(right, bottom, right + corner, bottom)
} else {
quadraticBezierTo(right, bottom, right - corner, bottom)
}
}
}
for (i in (nbLines - 1) downTo 0) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val left = layoutResult.getLineLeft(i) - hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = false)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = false)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (bottomInner) {
lineTo(left - corner, bottom)
} else {
lineTo(left + corner, bottom)
}
quadraticBezierTo(left, bottom, left, bottom - corner)
lineTo(left, top + corner)
if (topInner) {
quadraticBezierTo(left, top, left - corner, top)
} else {
quadraticBezierTo(left, top, left + corner, top)
}
}
}
path.close()
return path
}
Text(
text,
onTextLayout = { layoutResult ->
path = computePath(layoutResult = layoutResult)
},
modifier = modifier.drawBehind {
drawPath(path, style = Fill, color = iBgColor)
if (iBgStrokeWidth != null) {
drawPath(path, style = Stroke(width = iBgStrokeWidth), color = iBgStrokeColor)
}
},
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
style = style
)
}
用法:
@Preview
@Composable
fun Preview() {
Column (modifier = Modifier.fillMaxSize().background(Color.Black)){
IBgText(
text = "test\ntest test\ntest\ntest test",
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 6.dp, start = 10.dp, end = 10.dp, bottom = 6.dp),
iBgColor = Color.Blue.copy(alpha = .4f),
iBgStrokeWidth = 3f,
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 5.dp,
iBgStrokeColor = Color.Red,
iBgVerticalPadding = 1.dp,
color = Color.White
)
IBgText(
text = "This is a sample\ntext",
textAlign = TextAlign.Center,
modifier = Modifier.padding(20.dp, bottom = 6.dp).rotate(-15f),
iBgColor = Color(0xFFFFFFFF),
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 8.dp,
iBgVerticalPadding = 5.dp
)
IBgText(
text = "line 1\n-- line 2 --",
textAlign = TextAlign.End,
modifier = Modifier.padding(10.dp)
)
}
}
结果:
它不是很漂亮,但我想它可以完成工作。
onTextLayout = { layoutResult ->给出每行的边界。
然后你可以path遍历每一行。
我从右上角(第一行结束)到右下角(最后一行结束)做了一个循环
然后从左下角(最后一行开始)到左上角(第一行开始)再循环一次。
然后我添加了一些圆角来匹配图片。
Ps:我这周开始使用 Kotlin 和 Jetpack compose,整个星期天都在回答你的问题