I need to get the selected text from the Text component. I tried using the SelectionContainer, but it does not have an onSelectionChange callback or something similar.
Edit: I have to highlight the text the user selected
Edit 2: I have asked in the issue tracker, and it's not possible to do that yet. https://issuetracker.google.com/issues/195441731
If you are not forced to use Text() ...
An easy way is to use Composable TextField() and change readOnly to true
because you need a TextFieldValue argument to get selected text
for example if i want that selected text in custom TextField , my code will be look like this:
#Composable fun TextWithSelectedText(
text:String = "This is a plain text",
selectedText:MutableState<String>){
var textInput by remember{ mutableStateOf(TextFieldValue(text))}
TextField(
value = textInput,
onValueChange = { newValue ->
textInput = newValue
selectedText.value = textInput.getSelectedText().text
} ,
readOnly = true ,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.White , // your background color
focusedIndicatorColor = Color.Transparent ,
unfocusedIndicatorColor = Color.Transparent
)
)
}
Related
I have a compose OutlinedTextField which shouldn't be manually editable but filled using input from something that happens on clicking the text field. But setting the field as readOnly=true makes the clickable modifier not work. So a workaround I found is to set it as enabled=false which lets clickable work.
OutlinedTextField(
value = text,
onValueChange = { text = it},
enabled = false,
modifier = Modifier.clickable { text= "Clicked"}
)
How can I make this look as if its enabled based on whatever theme is being followed without setting a fixed color?
You can use the TextFieldDefaults.outlinedTextFieldColors applying in the disabled colors the same value of the enabled colors without using hardcoded values:
OutlinedTextField(
//...
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledTextColor = LocalContentColor.current.copy(LocalContentAlpha.current),
backgroundColor = Color.Transparent,
disabledBorderColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
disabledLabelColor = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
)
)
Im not sure what this is called so I call it a indicator bulb, its the image thats hanging off of the indicator. It appears above my bottom modal and I think that looks funky, how do we remove this?
You can hide the cursor using cursorBrush = SolidColor(Unspecified).
You have also to define a custom TextSelectionColors to override the default color provided by LocalTextSelectionColors.current that it is applied to the text selection and the TextFieldCursorHandle.
val customTextSelectionColors = TextSelectionColors(
handleColor = Color.Transparent,
backgroundColor = Color.Transparent
)
CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
BasicTextField(
value = text,
onValueChange = {text = it},
cursorBrush = SolidColor(Unspecified)
)
}
Many Android apps feature a search box in the app bar / toolbar, that looks somewhat like this screenshot:
How do I recreate something similar in Jetpack Compose + Material3? I would like the text field to have these features:
support showing a hint when the content string is empty (e.g. "Type your search...")
auto-focus on the first composition, so that the keyboard is opened
put the cursor at the end of the content string on the first composition
I tried to reproduce the same behavior in Jetpack Compose + Material3 by putting a TextField in the title of a CenterAlignedTopAppBar but the result does not look good. The text field uses the whole height of the app bar and has a grey background, and both of these things look odd.
I came up with a AppBarTextField after some engineering, see the code below. I had to use the lower-level BasicTextField since the normal TextField is not customizable enough. The code having to do with theming and color was copied directly from TextField's implementation, so that the theme's customizations apply normally to the components of the text field.
The parameters the AppBarTextField composable accepts are:
value: the content string to show in the text field
onValueChange: new values are passed here (remember to update value!)
hint: the hint to show when the text field is empty
modifier, keyboardOptions and keyboardActions: they are passed directly to BasicTextField and they behave the same as they would in a normal TextField. If you need to customize other TextField parameters just add them to the function signature and then pass them to BasicTextField.
The requested features are implemented:
the focus acquisition was achieved with a SideEffect, so that it would only happen on the first composition
putting the cursor at the end on the first composition required using a TextFieldValue
the strange-looking background is not present anymore, since no .background() modifier is present (while it is in the normal TextField)
the hint was added using by passing a placeholder to TextFieldDecorationBox in the decorationBox parameter (note that this was also possible with TextField)
TextFieldDecorationBox's padding is also now only 4dp. Padding was added here (and not with a modifier on BasicTextField) since otherwise the bottom line indicator (which is, instead, displayed using the .indicatorLine() modifier) would not be shown correctly.
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun AppBarTextField(
value: String,
onValueChange: (String) -> Unit,
hint: String,
modifier: Modifier = Modifier,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
) {
val interactionSource = remember { MutableInteractionSource() }
val textStyle = LocalTextStyle.current
// make sure there is no background color in the decoration box
val colors = TextFieldDefaults.textFieldColors(containerColor = Color.Unspecified)
// If color is not provided via the text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
MaterialTheme.colorScheme.onSurface
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor, lineHeight = 50.sp))
// request focus when this composable is first initialized
val focusRequester = FocusRequester()
SideEffect {
focusRequester.requestFocus()
}
// set the correct cursor position when this composable is first initialized
var textFieldValue by remember {
mutableStateOf(TextFieldValue(value, TextRange(value.length)))
}
textFieldValue = textFieldValue.copy(text = value) // make sure to keep the value updated
CompositionLocalProvider(
LocalTextSelectionColors provides LocalTextSelectionColors.current
) {
BasicTextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
// remove newlines to avoid strange layout issues, and also because singleLine=true
onValueChange(it.text.replace("\n", ""))
},
modifier = modifier
.fillMaxWidth()
.heightIn(32.dp)
.indicatorLine(
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors
)
.focusRequester(focusRequester),
textStyle = mergedTextStyle,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = true,
decorationBox = { innerTextField ->
// places text field with placeholder and appropriate bottom padding
TextFieldDefaults.TextFieldDecorationBox(
value = value,
visualTransformation = VisualTransformation.None,
innerTextField = innerTextField,
placeholder = { Text(text = hint) },
singleLine = true,
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors,
contentPadding = PaddingValues(bottom = 4.dp)
)
}
)
}
}
Here is an example usage:
var value by rememberSaveable { mutableStateOf("initial content") }
CenterAlignedTopAppBar(
title = {
AppBarTextField(
value = value,
onValueChange = { newValue -> value = newValue },
hint = "A hint..."
)
},
navigationIcon = /* the back icon */,
actions = /* the search icon */
)
The solution I have so far is to use a Transparent Color for the cursor.
I am looking for a better way to hide it if there is any.
cursorBrush = SolidColor(Transparent)
TextField should be focused, the keyboard should be open and the user should be able to type input.
The problem with this is I can still see the TextFieldCursorHandle after entering text.
In the BasicTextField you can hide the cursor using cursorBrush = SolidColor(Unspecified).
In the TextFieldyou can use the attribute colors = TextFieldDefaults.textFieldColors(cursorColor = Color.Unspecified)
The TextFieldCursorHandle and the selected text use the color provided by LocalTextSelectionColors.current
You override this color defining a custom TextSelectionColors:
val customTextSelectionColors = TextSelectionColors(
handleColor = Color.Transparent,
backgroundColor = Color.Transparent
)
CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
BasicTextField(
value = text,
onValueChange = {text = it},
cursorBrush = SolidColor(Unspecified)
)
TextField(
value = text,
onValueChange = {text = it},
colors = TextFieldDefaults.textFieldColors(cursorColor = Color.Unspecified)
)
}
In material design TextField page TextField has properties such as
Assistive elements provide additional detail about text entered into
text fields.
Helper text Helper text conveys additional guidance about the input field, such as how it will be used. It should only take up a single
line, being persistently visible or visible only on focus.
Error message When text input isn't accepted, an error message can display instructions on how to fix it. Error messages are displayed
below the input line, replacing helper text until fixed.
Icons Icons can be used to message alerts as well. Pair them with error messages to provide redundant alerts, which are useful when you
need to design for colorblind users.
Character counter Character or word counters should be used if there is a character or word limit. They display the ratio of
characters used and the total character limit.
Do these properties exist for Jetpack Compose TextField as of compose 1.0.0-alpha09?
With 1.0.x there aren't built-in properties to display an error message or the counter text.
However you can use a custom composable.
For the error message you can use something like:
var text by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
fun validate(text: String) {
isError = /* .... */
}
Column {
TextField(
value = text,
onValueChange = {
text = it
isError = false
},
trailingIcon = {
if (isError)
Icon(Icons.Filled.Error,"error", tint = MaterialTheme.colors.error)
},
singleLine = true,
isError = isError,
keyboardActions = KeyboardActions { validate(text) },
)
if (isError) {
Text(
text = "Error message",
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(start = 16.dp)
)
}
}
To display the counter text you can use something like:
val maxChar = 5
Column(){
TextField(
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
},
modifier = Modifier.fillMaxWidth()
)
Text(
text = "${text.length} / $maxChar",
textAlign = TextAlign.End,
style = MaterialTheme.typography.caption,
modifier = Modifier.fillMaxWidth().padding(end = 16.dp)
)
}
trailingIcon for the icon.
For the text at the bottom I just used
Text(
text = "Error message",
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(start = 16.dp)
)
Might be included in the future? Weirdly enough the documentation for isError says:
indicates if the text field's current value is in error. If set to
true, the label, bottom indicator and trailing icon by default will
be displayed in error color
What bottom indicator? There is no such thing. Weird.