Is there any way to put OutlinedTextField's label on top (expanded) and still have the placeholder, when OutlinedTextField is not in focus?
In xml layouts we have TextInputLayout that has expandedHintEnabled attribute that does the expanding.
Currently the placeholder applies an alpha modifier with this condition InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f and there isn't a parameter to achieve the same behaviour of expandedHintEnabled.
A workaround can be to use a visualTransformation to display a placeholder when the text is empty, removing the placeholder parameter.
Something like:
val textColor = if (text.isEmpty())
MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
else
LocalContentColor.current.copy(LocalContentAlpha.current)
val textStyle = if (text.isEmpty())
LocalTextStyle.current.merge(MaterialTheme.typography.subtitle1)
else
LocalTextStyle.current
TextField(
value = text,
onValueChange = { text = it },
//placeholder = { Text("Placeholder") },
label = { Text("Label") },
visualTransformation = if (text.isEmpty())
PlaceholderTransformation("Placeholder")
else VisualTransformation.None,
textStyle = textStyle,
colors = TextFieldDefaults.textFieldColors(
textColor = textColor
)
)
with:
class PlaceholderTransformation(val placeholder: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return PlaceholderFilter(text, placeholder)
}
}
fun PlaceholderFilter(text: AnnotatedString, placeholder: String): TransformedText {
var out = placeholder
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return 0
}
override fun transformedToOriginal(offset: Int): Int {
return 0
}
}
return TransformedText(AnnotatedString(placeholder), numberOffsetTranslator)
}
Related
How can I get the transformed value from VisualTransformation in Jetpack compose TextField? since the only thing that is changing is the visual text not the actual input/buffered text?
class CurrencyAmountInputVisualTransformation(
private val currencySymbol: String,
private val unbufferedValueChange: (String) -> Unit
) : VisualTransformation {
private val symbols = DecimalFormat().decimalFormatSymbols
private val numberOfDecimals: Int = 2
override fun filter(text: AnnotatedString): TransformedText {
val zero = symbols.zeroDigit
val inputText = text.text
val intPart = inputText
.dropLast(numberOfDecimals)
.reversed()
.chunked(3)
.joinToString(symbols.groupingSeparator.toString())
.reversed()
.ifEmpty {
zero.toString()
}
val fractionPart = inputText.takeLast(numberOfDecimals).let {
if (it.length != numberOfDecimals) {
List(numberOfDecimals - it.length) {
zero
}.joinToString("") + it
} else {
it
}
}
val formattedNumber = intPart + symbols.decimalSeparator.toString() + fractionPart
val value = inputText.dropLast(numberOfDecimals)
unbufferedValueChange("$value.$fractionPart")
val newText = AnnotatedString(
text = "$currencySymbol $formattedNumber",
spanStyles = text.spanStyles,
paragraphStyles = text.paragraphStyles
)
...
return TransformedText(newText, ...)
}
I'm getting duplicate re-composition because of this approach since I have 2 mutable state, one for the input and one for the value that I'm getting from a lambda callback I passed to the VisualTransformation.
#Composable
internal fun CurrencyField(
) {
val pattern = remember { Regex("^\\d*\\.?\\d*\$") }
var input by remember { mutableStateOf("") }
var amountInput by remember { mutableStateOf(0.00) }
OutlinedTextInputField(
text = input,
onTextChanged = {
if (it.isEmpty() || it.matches(pattern)) {
input = it
}
},
keyboardType = KeyboardType.NumberPassword,
visualTransformation = CurrencyAmountInputVisualTransformation("PHP") {
amountInput = it.toDouble()
}
)
}
Output:
Input screenshot
Is there any way I can get
232323.23
without using a callback in the VisualTransformation class?
Thanks.
You have to apply the same filter used by the VisualTransformation
var text by remember { mutableStateOf("") }
val visualTransformation = MyVisualTransformation()
TextField(
value = text,
onValueChange = { text = it },
visualTransformation = visualTransformation
)
val transformedText = remember(text, visualTransformation) {
visualTransformation.filter(AnnotatedString(text))
}.text.text
How can I add a suffix to TextField input that flows (moves) with the user input text?
With M3 starting from the 1.1.0-alpha06 you can use the suffix attribute:
TextField(
value = text,
onValueChange = { text = it },
suffix = { Text ("€") }
)
Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation attribute.
Something like:
TextField(
value = text,
onValueChange = { text = it },
singleLine = true,
visualTransformation = SuffixTransformation(" €"),
)
class SuffixTransformation(val suffix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val result = text + AnnotatedString(suffix)
val textWithSuffixMapping = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return offset
}
override fun transformedToOriginal(offset: Int): Int {
if (text.isEmpty()) return 0
if (offset >= text.length) return text.length return offset
}
}
return TransformedText(result, textWithSuffixMapping )
}
}
If you have the placeholder you can put a condition in the visualTransformation attribute.
Something like:
TextField(
value = text,
onValueChange = { text = it },
singleLine = true,
visualTransformation = if (text.isEmpty())
VisualTransformation.None
else
SuffixTransformation(" €"),
placeholder = { Text("Placeholder") }
)
I found Gabriele Mariotti's answer buggy. Needed to change transformedToOriginal function to this:
override fun transformedToOriginal(offset: Int): Int {
if (offset > text.length) return text.length
return offset
}
This is easily done in Compose like this:
const val SUFFIX = " $"
#Composable
fun SuffixedText() {
var text by remember { mutableStateOf("") }
TextField(
text,
singleLine = true,
visualTransformation = SuffixTransformer(SUFFIX),
onValueChange = { text = it }
)
}
class SuffixTransformer(val suffix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val result = text + AnnotatedString(suffix)
return TransformedText(result, OffsetMapping.Identity)
}
}
The above component probably can be used in traditional Views too. See this post
Also, see the following:
How to add a prefix to a TextField in Compose?
GitHub issue to add flowing suffix in Views in Material components for Android
Put constant text inside EditText in View which should be non-editable
I want to make a background color change when user select a item on Surface. I am using Column in my parent, so I can't make LazyColumn. So I am using Foreach to make a list of view. By default no view will be selected, when user click on item then I want change the color. Note: Only one item will select at a time.
ScanDeviceList
#Composable
fun ColumnScope.ScanDeviceList(
scanDeviceList: List<ScanResult>,
modifier: Modifier = Modifier,
pairSelectedDevice: () -> Unit
) {
Spacer()
AnimatedVisibility() {
Column {
Text()
Spacer()
scanDeviceList.forEachIndexed { index, scanResult ->
ClickableItemContainer(
rippleColor = AquaLightOpacity10,
content = {
ScanDeviceItem(index, scanResult, scanDeviceList)
}
){}
}
AvailableWarningText()
PairSelectedDevice(pairSelectedDevice)
}
}
}
ScanDeviceItem
#Composable
fun ScanDeviceItem(
index: Int,
scanResult: ScanResult,
scanDeviceList: List<ScanResult>
) {
Column {
if (index == 0) {
Divider(color = Cloudy, thickness = 1.dp)
}
Text(
text = scanResult.device.name,
modifier = Modifier.padding(vertical = 10.dp)
)
if (index <= scanDeviceList.lastIndex) {
Divider(color = Cloudy, thickness = 1.dp)
}
}
}
ClickableItemContainer
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ClickableItemContainer(
rippleColor: Color = TealLight,
content: #Composable (MutableInteractionSource) -> Unit,
clickAction: () -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
CompositionLocalProvider(
LocalRippleTheme provides AbcRippleTheme(rippleColor),
content = {
Surface(
onClick = { clickAction() },
interactionSource = interactionSource,
color = White
) {
content(interactionSource)
}
}
)
}
I want to something like this
Now above solution is only working on ripple effect, Now I want to extend my function to select a single item at a time. Many Thanks
You can store the selected index and use the clickAction to update its value when the user clicks on each item.
Something like:
#Composable
fun MyList(
var selectedIndex by remember { mutableStateOf(-1) }
Column() {
itemsList.forEachIndexed() { index, item ->
MyItem(
selected = selectedIndex == index,
clickAction = { selectedIndex = index }
)
}
}
#Composable
fun MyItem(
selected : Boolean = false,
clickAction: () -> Unit
){
Surface(
onClick = { clickAction() },
color = if (selected) Color.Red else Color.Yellow
) {
Text("Item...")
}
}
To have only item selected at a time, you can follow a behavior that is similar. You save your item selected in a mutableState variable, like:
var itemSelected by remember { mutableState(ScanDeviceResult()) }
After that, when you click the item, you update the itemSelected, something like:
ClickableItemContainer(
rippleColor = AquaLightOpacity10,
content = {
ScanDeviceItem(index, scanResult, scanDeviceList)
}
){ newItemSelected ->
itemSelected = newItemSelected
}
Now, each ClickableItemContainer needs to have an associated ScanResult.
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ClickableItemContainer(
itemSelected: ScanResult,
scanResult: ScanResult,
rippleColor: Color = TealLight,
content: #Composable (MutableInteractionSource) -> Unit,
clickAction: (ScanResult) -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
CompositionLocalProvider(
LocalRippleTheme provides AbcRippleTheme(rippleColor),
content = {
Surface(
onClick = { clickAction.invoke(scanResult) },
interactionSource = interactionSource,
color = if (itemSelected == scanResult) YOUR_BACKGROUND-SELECTED_COLOR else White
) {
content(interactionSource)
}
}
)
}
Now, on your ScanDeviceList method:
#Composable
fun ColumnScope.ScanDeviceList(
itemSelected: MutableState<ScanResult>,
scanDeviceList: List<ScanResult>,
modifier: Modifier = Modifier,
pairSelectedDevice: () -> Unit
) {
Spacer()
AnimatedVisibility() {
Column {
Text()
Spacer()
scanDeviceList.forEachIndexed { index, scanResult ->
ClickableItemContainer(
itemSelected = itemSelected,
scanResult = scanResult,
rippleColor = AquaLightOpacity10,
content = {
ScanDeviceItem(index, scanResult, scanDeviceList)
}
){ scanResultToSelect ->
itemSelected = scanResultToSelect
}
}
AvailableWarningText()
PairSelectedDevice(pairSelectedDevice)
}
}
}
I have a Composable with a Boxand some text and it also returns a value
How to use that value
#Composable
fun dummyAndUselessUI(String:String) : String{
val text = remember { mutableStateOf("") }
Box(modifier = Modifier.size(100.dp)){ Text(String) }
return text.value
}
You don't need a function that return a value, in Compose you handle State
#Composable
fun dummyScreen() {
var text by rememberSaveable { mutableStateOf("") }
dummyAndUselessUI(text = text, onNameChange = { text = it })
}
#Composable
fun dummyAndUselessUI(text: String, onTextChange: (String) -> Unit) {
Box(modifier = Modifier.size(100.dp)){
OutlinedTextField(
value = text,
onValueChange = onTextChange,
label = { Text("Name") }
)
}
}
I want my Textfield to be prefixed with a country code (+91), which can't be changed by the user.
How do I achieve this?
With M3 TextField,starting from 1.1.0-alpha06 you can use the prefix attribute:
//androidx.compose.material3
TextField(
value = text,
onValueChange = { text = it },
prefix = { Text ("+91") }
)
Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation property:
Something like:
TextField(
value = text,
onValueChange = { text = it},
visualTransformation = PrefixTransformation("(+91)")
)
with:
class PrefixTransformation(val prefix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return PrefixFilter(text, prefix)
}
}
fun PrefixFilter(number: AnnotatedString, prefix: String): TransformedText {
var out = prefix + number.text
val prefixOffset = prefix.length
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return offset + prefixOffset
}
override fun transformedToOriginal(offset: Int): Int {
if (offset < prefixOffset) return 0
return offset - prefixOffset
}
}
return TransformedText(AnnotatedString(out), numberOffsetTranslator)
}
You can simply add Text() inside leadingIcon parameter inside textField
OutlinedTextField(
value = text,
onValueChange = onTextChange,
maxLines = 1,
leadingIcon =
{
Text(
text = prefixText,
style = textStyle,
color = Color.Black,
modifier = Modifier.padding(start = 24.dp, end = 8.dp)
)
}
)
Another option you could use as well as Gabriele's answer is using the leadingIcon property of TextField
TextField(
value = text,
onValueChange = { text = it},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_pound_symbol),
contentDescription = null,
tint = colorResourceFromAttr(id = R.attr.colorOnSurface)
)
}
)
Which gives you this: