Reputation: 607
For example i have this:
TAKE, O take those lips away
That so sweetly were forsworn,
And those eyes, the break of day,
Lights that do mislead the morn:
But my kisses bring again,
Bring again—
Seals of love, but seal’d in vain,
Seal’d in vain!
– William Shakespeare
And i want to replace some words to TextField(in Compose) or to EditText(in XML). Example:
TAKE, O take those lips textfield
That so were forsworn,
And those eyes, the break of day,
Lights that do mislead the morn:
But my textfield bring again,
Bring again—
textfield of love, textfield seal’d in vain,
Seal’d in vain!
– William Shakespeare
Can you advise me the way to realise it? Maybe specific libraries?
Upvotes: 3
Views: 1679
Reputation: 87774
First of all, I highlighted the words to be replaced by *
so that they can be easily found using a regular expression.
val string = """
TAKE, O take those lips *away*
That so sweetly were forsworn,
And those eyes, the break of day,
Lights that do mislead the morn:
But my *kisses* bring again,
Bring again—
*Seals* of love, *but* seal’d in vain,
Seal’d in vain!
– William Shakespeare
""".trimIndent()
val matches = remember(string) { Regex("\\*\\w+\\*").findAll(string) }
Using the onTextLayout
argument in Compose Text
, you can get a lot of information about the text to be rendered, including the positions of each character. And the indexes of the characters you need to replace are already defined by a regular expression.
All you have to do is place the text fields at the appropriate positions.
I use BasicTextField
because it doesn't have the extra padding that TextField
has, so the size is easy to match with Text
. I set its background to white so that the original text doesn't shine through. If you have an unusual background, a gradient for example, you can also make the text transparent with annotated text as shown in the documentation, then the BasicTextField
can be left transparent.
The SubcomposeLayout
is a great tool for creating such layouts without waiting a next recomposition to use onTextLayout
result.
val textLayoutState = remember { mutableStateOf<TextLayoutResult?>(null) }
val textFieldTexts = remember(matches.count()) { List(matches.count()) { "" }.toMutableStateList() }
val style = MaterialTheme.typography.body1
SubcomposeLayout { constraints ->
val text = subcompose("text") {
Text(
text = string,
style = style,
onTextLayout = {
textLayoutState.value = it
},
)
}[0].measure(constraints)
val textLayout = textLayoutState.value ?: run {
// shouldn't happen as textLayoutState is updated during sub-composition
return@SubcomposeLayout layout(0, 0) {}
}
val wordsBounds = matches.map {
// I expect all selected words to be on a single line
// otherwise path bounds will take both lines
textLayout
.getPathForRange(it.range.first, it.range.last + 1)
.getBounds()
}
val textFields = wordsBounds.mapIndexed { i, wordBounds ->
subcompose("textField$i") {
BasicTextField(
value = textFieldTexts[i],
onValueChange = {
textFieldTexts[i] = it
},
onTextLayout = {
println("${it.size}")
},
textStyle = style,
modifier = Modifier
.border(1.dp, Color.LightGray)
.background(Color.White)
)
}[0].measure(Constraints(
maxWidth = floor(wordBounds.width).toInt(),
)) to wordBounds.topLeft
}
layout(text.width, text.height) {
text.place(0, 0)
textFields.forEach {
val (placeable, position) = it
placeable.place(floor(position.x).toInt(), floor(position.y).toInt())
}
}
}
Result:
Upvotes: 4