Strange scroll in the BasicTextField with IntrinsicSize.Min - android

I need to implement UI control(decrease button, resizeable text input, increase button)
I was trying both ways - compose and XML version.
Compose version.
I have a strange scroll when I click on BasicTextField and pull horizontally. It just hides TextField value.
BasicTextField(
value = text,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
keyboardActions = remember { KeyboardActions(onDone = { keyboardController?.hide() }) },
onValueChange = { onTextChanged(it) },
singleLine = true,
decorationBox = { innerTextField ->
Box(modifier = Modifier.width(IntrinsicSize.Min)) {
innerTextField()
}
}
)
Modifier.width(1.dp, Dp.Infinity), doesn't help and takes empty horizontal padding, but it doesn't have the "internal" scroll.
1.1. How to fix the "internal" scroll in the BasicTextField with applied IntrinsicSize.Min?
1.2. Also BasicTextField cuts the left-most digit when you put cursor position. It would be great to have the same behavior as we have in the XML version.
XML version.
I was trying to use the XML version with EditText instead of Compose BasicTextField but stuck with merge compose state and EditText TextWatcher listener.
2.1. To avoid twice setting the same text value to the EditText, I should compare compose state value and EditText value each time in the update {} block, is that ok? Or here can be a better solution?
AndroidView(
factory = {
_binding = L7Binding.inflate(inflater, container, false)
val view = _binding!!.root
_binding!!.editText.doOnTextChanged { text, _, _, _ ->
viewModel.onTextChanged(text.toString())
}
view
},
update = {
if (_binding!!.editText.text.toString() != inputText) {
_binding!!.editText.setText(inputText)
}
}
)

I faced the same problem.
Solved it with the help of this code, I hope it will be useful to you)
BasicTextField(,
modifier = Modifier
.disabledHorizontalPointerInputScroll()...
and also
private val HorizontalScrollConsumer = object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource) = available.copy(y = 0f)
override suspend fun onPreFling(available: Velocity) = available.copy(y = 0f)
}
fun Modifier.disabledHorizontalPointerInputScroll(disabled: Boolean = true) =
if (disabled) this.nestedScroll(HorizontalScrollConsumer) else this

Related

Scroll to bottom listener Jetpack Compose

I have a page that I want to be scrollable (mostly text only, no list). At the bottom of that page I have a disabled button, but when I reach the bottom of the page I want that button to activate. How can I do this with JetpackCompose Kotlin androidx.compose.ui version 1.3.2?
Much appreciation in advance!
I can't use ScrollableColumn, Scrollable, LazyColumn, LazyRow because of the compose library.
It's not clear why you can't use these components since they are all part of the Compose library. However, this can be done using Column composable with verticalScroll modifier.
val scrollState = rememberScrollState()
Column(Modifier.verticalScroll(scrollState)) {
//Text
}
State for enabling Button, false by default:
val isButtonEnabled by remember {mutableStateOf(false)}
ScrollState has canScrollForward Boolean property, false means we cannot scroll forward (we are the end of the list). Once we have false here, button should be enabled
LaunchedEffect(scrollState.canScrollForward) {
if (!scrollState.canScrollForward) isButtonEnabled = true
}
And button:
Button(enabled = isButtonEnabled, ...)
#Composable
override fun Build() {
val scrollState = rememberScrollState()
val isButtonEnabled by remember {mutableStateOf(false)}
LaunchedEffect(scrollState.canScrollForward) {
if (!scrollState.canScrollForward) isButtonEnabled = true
}
ScreenScaffold(
header = {
TopBar(
title = R.string.insurance_choose_insurance_travel,
onBack = { sendNavEffect.invoke(Effect.Navigation.OnBack) },
actions = {},
backgroundColor = white
)
},
page = {
Column(
Modifier
.fillMaxSize()
.padding(20.dp)
.verticalScroll(scrollState)
){
repeat(100){
Text(
"Text",
color = Color.Black
)
}
}
},
footer = {
Row(modifier = Modifier.padding(20.dp)) {
PrimaryActionButton(
text = R.string.accept_terms_and_conditions_sca.getString(),
buttonState = if(isButtonEnabled) ButtonState.Active else ButtonState.Disabled,
onClick = {}
)
}
})
}
}

TextField is overlapped by keyboard in Android Compose

I have a TextField in column with verticalScroll().
When adding a large number of characters, the textfield size goes beyond the keyboard and I stop seeing what I am typing
I tried to use this lib, but that's doesn't help
I think you can use BringIntoViewRequester in your TextField.
var state by rememberSaveable {
mutableStateOf("")
}
val coroutineScope = rememberCoroutineScope()
val bringIntoViewRequester = remember {
BringIntoViewRequester()
}
TextField(
value = state,
onValueChange = { text ->
state = text
// This will cause the TextField be repositioned on the screen
// while you're typing
coroutineScope.launch {
bringIntoViewRequester.bringIntoView()
}
},
modifier = Modifier
.bringIntoViewRequester(bringIntoViewRequester)
.onFocusChanged {
if (it.isFocused) {
coroutineScope.launch {
delay(400) // delay to way the keyboard shows up
bringIntoViewRequester.bringIntoView()
}
}
},
)
See the complete sample here.
you can add android:ellipsize="end" and android:maxLines="1" or whatever lines you want, in your text xml hope it would be helpful.

Jetpack Compose animation finished listener not called

I'm struggling to use Jetpack Compose Animation to achieve a (supposedly simple) effect:
in the case of an error, a control's background color should flash red, and after a short delay then fade back to normal (transparent).
My current approach is to model this with a boolean state shouldFlash which is set to true when an error occurs, and is set back to false when the animation completes. Unfortunately it seems the finishedListener passed to animateColorAsState is never called. I attached a debugger and also added a log statement to verify this.
What am I doing wrong?
Sample (button triggers error):
#Composable
fun FlashingBackground() {
Column(modifier = Modifier.size(200.dp)) {
var shouldFlash by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("Initial") }
FlashingText(flashing = shouldFlash, text = text) {
shouldFlash = false
text = "Animation done"
}
Button(onClick = {
shouldFlash = true
}) {
Text(text = "Flash")
}
}
}
#Composable
fun FlashingText(flashing: Boolean, text: String, flashFinished: () -> Unit) {
if (flashing) {
val flashColor by animateColorAsState(
targetValue = Color.Red,
finishedListener = { _ -> flashFinished() }
)
Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
} else {
Text(text = text)
}
}
Compose version: 1.0.5 (latest stable at time of writing), also in 1.1.0-beta02
Anyway, to understand this, you need to know how the animateColorAsState works internally.
It relies on recompositions - is the core idea.
Every time a color changes, a recomposition is triggered, which results in the updated color value being reflected on the screen. Now, what you are doing is just using conditional statements to DISPLAY DIFFERENT COMPOSABLES. Now, one Composable is actually referring to the animating value, that is, the one inside your if block (when flashing is true). On the other hand, the else block Composable is just a regular text which does not reference it. That is why you need to remove the conditional. Anyway, because after removing the conditional, what remains is only a single text, I thought it would be a waste to create a whole new Composable out of it, which is why I removed that Composable altogether and pasted the Text inside your main Composable. It helps to keep things simpler enough. Other than this, the answer by #Rafiul does work, but there is not really a need for a Composable like that, so I would still recommend using this answer instead, so that the code is easier to read.
ORIGINAL ANSWER:
Try moving the animator outside the Child Composable
#Composable
fun FlashingBackground() {
Column(modifier = Modifier.size(200.dp)) {
var shouldFlash by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("Initial") }
val flashFinished: (Color) -> Unit = {
shouldFlash = false
text = "Animation done"
}
val flashColor by animateColorAsState(
targetValue = if (shouldFlash) Color.Red else Color.White,
finishedListener = flashFinished
)
//FlashingText(flashing = shouldFlash, text = text) -> You don't need this
Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
Button(onClick = {
shouldFlash = true
}) {
Text(text = "Flash")
}
}
}
Change your code like this.
FlashingBackground
#Composable
fun FlashingBackground() {
Column(modifier = Modifier.size(200.dp)) {
var shouldFlash by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("Initial") }
FlashingText(flashing = shouldFlash, text = text) {
shouldFlash = false
text = "Animation done"
}
Button(onClick = {
shouldFlash = true
}) {
Text(text = "Flash")
}
}
}
FlashingText
#Composable
fun FlashingText(flashing: Boolean, text: String, flashFinished: () -> Unit) {
val flashColor by animateColorAsState(
targetValue = if(flashing) Color.Red else Color.White,
finishedListener = { _ -> flashFinished() }
)
Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
}
Edited:
The problem with your code is you are initializing animateColorAsState when you are clicking the Flash button and making shouldFlash = true. So for the first time, it just initializes the animateColorAsState, doesn't run the animation. So there will be no finishedListener call as well. Since finishedListener isn't executed, shouldFlash stays to true. So from the next call shouldFlash is already true there will be no state change. That's why from the subsequent button click, it doesn't recompose the FlashingText anymore. You can put some log in your method you won't see FlashingText after the first button click.
Keep in mind: targetValue = Color.Red will not do anything. target value should be either a state or like this condition if(flashing) Color.Red because you need to change the state to start the animation.
#Phillip's answer is also right. But I don't see any extra advantage in moving the animator outside the Child Composable if you use it like the above.

OutlineTextField , TextField not working in Jetpack Compose 1.0.0-alpha02

For some reason, CoreTextField works but TextField and OutlineTextField wont
Works
#Composable
fun TextFieldDemo(){
val text = remember { mutableStateOf(TextFieldValue("Text")) }
CoreTextField(modifier = Modifier.fillMaxWidth(),
value = text.value,
onValueChange = {text.value = it})
}
Not working
#Composable
fun TextFieldDemo(){
val text = remember { mutableStateOf(TextFieldValue("Text")) }
OutlinedTextField(value = text.value,
onValueChange = {text.value = it}, label = {Text("Test")})
}
Error: Non of the following Functions can be called for OutlinedTextField
From the docs change
Bug Fixes
androidx.ui.foundation.TextFieldValue and androidx.ui.input.EditorValue are deprecated. TextField, FilledTextField and CoreTextField composables that uses that type is also deprecated. Please use androidx.ui.input.TextFieldValue instead (I4066d, b/155211005)
But I'm using what it says (I think)
Edit
You are missing the "label" param in the original question, as we can see in the image. Following Gabriele's comment made it work, so you shouldn't change the question with the answer, it's a lot confusing.
Just for the record, here it`s the code that works in 1.0.0-alpha02:
#Composable
fun TextFieldDemo() {
val text = remember { mutableStateOf(TextFieldValue("Text")) }
OutlinedTextField(value = text.value,
onValueChange = { text.value = it },
label = { Text("Test") })
}

Prevent the keyboard from appearing in a Jetpack Compose app

I'm making a calculator to learn Compose, so I placed my own number buttons on screen and I wanted to prevent the soft keyboard from appearing.
Here is my repo: https://github.com/vitor-ramos/CalculadorCompose
I noticed in TextFieldImpl.kt there is a modifier to show the keyboard, so I tried to clone the code and remove the line: keyboardController.value?.showSoftwareKeyboard() I know it's not a good idea to duplicate code like that, but I wanted to give it a try, and it didn't work. As you can see in the original code below there's a TODO saying it should be handled by BaseTextField, but I looked in it's code and didn't find where it shows or hides the keyboard.
val textFieldModifier = modifier
.focusRequester(focusRequester)
.focusObserver { isFocused = it.isFocused }
.clickable(indication = null) {
focusRequester.requestFocus()
// TODO(b/163109449): Showing and hiding keyboard should be handled by BaseTextField.
// The requestFocus() call here should be enough to trigger the software keyboard.
// Investiate why this is needed here. If it is really needed, instead of doing
// this in the onClick callback, we should move this logic to the focusObserver
// so that it can show or hide the keyboard based on the focus state.
keyboardController.value?.showSoftwareKeyboard()
}
I found in this question that with views I can extend EditText and change the functionality, but I haven't found a equivalent for Compose: Android: Disable soft keyboard at all EditTexts
public class NoImeEditText extends EditText {
public NoImeEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onCheckIsTextEditor() {
return false;
}
}
I have tested Arun Pdiyan solution and works like a charm with null LocalTextInputService (in my case I read data from device attached Barcode reader)
CompositionLocalProvider(
LocalTextInputService provides null
) {
TextField(
value = barcodeReaderService.readedText.value,
onValueChange = { textState.value=it },
label = { Text("The Label") }
)
}
Explanation
I created a Composable ReadonlyTextField, that places a invisible box in front of the text field. The box has the same size as the text field.
With that workaround you can't focus the text field anymore, so no keyboard appears. In order to apply custom click handling, i added a onClick to the Box-Modifier.
This is not really a clean solution, but a good workaround.
Implementation of ReadonlyTextField
#Composable
fun ReadonlyTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
onClick: () -> Unit,
label: #Composable () -> Unit
) {
Box {
TextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
label = label
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(onClick = onClick),
)
}
}
Usage of ReadonlyTextField
#Composable
fun App() {
val textState = remember { mutableStateOf(TextFieldValue()) }
Column {
ReadonlyTextField(
value = textState.value,
onValueChange = { textState.value = it },
onClick = {
// custom click handling (e.g. open dialog)
},
label = {
Text(text = "Keyboardless Input")
}
)
}
}
A complete integrated example can be found in my medium post:
https://caelis.medium.com/jetpack-compose-datepicker-textfield-39808e42646a
Credits also go to this stackoverflow answer:
Jetpack Compose: Disable Interaction with TextField
You can hide keyboard on compose by providing TextInputService to TextField. You can implement your TextInputService or just pass it null for disabling input service.
CompositionLocalProvider(
// You can also provides null to completely disable the default input service.
LocalTextInputService provides myTextInputService
) {
BaseTextField(...)
}
You may see google employee answer here about this subject.
With ReadonlyTextField it is not possible to position the cursor. So added wrapped EditText inside a compose AndroidView
#Composable
fun NoKeyboardTextField(
modifier: Modifier,
text: String,
textColor: Int
) {
AndroidView(
modifier = modifier,
factory = { context ->
AppCompatEditText(context).apply {
isFocusable = true
isFocusableInTouchMode = true
showSoftInputOnFocus = false
}
},
update = { view ->
view.setText(text)
view.setTextColor(textColor)
view.setSelection(text.length)
}
)
}

Categories

Resources