pointerInput of Modifier takes no actions - android

I need a Card() Composable with a normal press and a long press functionality for a custom Card Composable.
The thing is Card() has its own value called onClick = {} and its working fine but has no option for a long press. So I researched if theres a way to handle it without styling my whole own Card Composable and there you go, the Modifier has an own function called Modifier.pointerInput which Ive tried but unfortunately it doesnt work.
Do I maybe use it wrong or is this functionality not available in Card()?
This is my implementation (Adapted from the Android Docs):
Card(
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = { clickable() },
onLongPress = { longClickable() }
}
)
Am I maybe supposed to deactivate the onClick functionality of Card?

Just add a size modifier. This code works for me:
Card(
modifier = Modifier
.fillMaxSize()
.background(Color.Magenta)
.pointerInput(Unit) {
detectTapGestures(
onPress = { Log.d("mlogs", "LoginScreen: onPress") },
onLongPress = { Log.d("mlogs", "LoginScreen: onLongPress") })
}
, content = {}
)

Related

TabRow/Tab Recomposition Issue in Compose Accompanist Pager

I was trying to create a sample Tab View in Jetpack compose, so the structure will be like
Inside a Parent TabRow we are iterating the tab title and create Tab composable.
More precise code will be like this.
#OptIn(ExperimentalPagerApi::class)
#Composable
private fun MainApp() {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
backgroundColor = MaterialTheme.colors.surface
)
},
modifier = Modifier.fillMaxSize()
) { padding ->
Column(Modifier.fillMaxSize().padding(padding)) {
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
val tabContents = listOf(
"Home" to Icons.Filled.Home,
"Search" to Icons.Filled.Search,
"Settings" to Icons.Filled.Settings
)
HorizontalPager(
count = tabContents.size,
state = pagerState,
contentPadding = PaddingValues(horizontal = 32.dp),
modifier = Modifier
.weight(1f)
.fillMaxWidth()
) { page ->
PagerSampleItem(
page = page
)
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier
.pagerTabIndicatorOffset(pagerState, tabPositions)
.height(4.dp)
.background(
color = Color.Green,
shape = RectangleShape
)
)
}
) {
tabContents.forEachIndexed { index, pair: Pair<String, ImageVector> ->
Tab(
selected = pagerState.currentPage == index,
selectedContentColor = Color.Green,
unselectedContentColor = Color.Gray,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(text = pair.first) },
icon = { Icon(imageVector = pair.second, contentDescription = null) }
)
}
}
}
}
}
#Composable
internal fun PagerSampleItem(
page: Int
) {
// Displays the page index
Text(
text = page.toString(),
modifier = Modifier
.padding(16.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(4.dp))
.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
.padding(8.dp)
.wrapContentSize(Alignment.Center)
)
}
And coming to my question is whenever we click on the tab item, the inner content get recompose so weirdly. Im not able to understand why it is happens.
Am attaching an image of the recomposition counts below, please take a look that too, it would be good if you guys can help me more for understand this, also for future developers.
There are two question we have to resolve in this stage
Whether it will create any performance issue, when the view getting more complex
How to resolve this recompostion issue
Thanks alot.
… whenever we click on the tab item, the
inner content get recompose so weirdly. Im not able to understand why
it is happens...
It's hard to determine what this "weirdness" is, there could be something inside the composable your'e mentioning here.
You also didn't specify what the API is, so I copied and pasted your code and integrated accompanist view pager, then I was able to run it though not on an Android Studio with a re-composition count feature.
And since your'e only concerned about the Text and the Icon parameter of the API, I think that's something out of your control. I suspect the reason why your'e getting those number of re-composition count is because your'e animating the page switching.
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
Though 'm not able to try this on another Android Studio version with the re-composition feature, I think (though I'm not sure) scrolling to another page without animation will yield less re-composition count.
coroutineScope.launch {
pagerState.scrollToPage(index)
}
If it still bothers you, the best course of action is to ask them directly, though personally I wouldn't concerned much about this as they are part of an accepted API and its just Text and Icon being re-composed many times by an animation which is also fine IMO.
Now if you have some concerns about your PagerSampleItem stability(which you have a full control), based on the provided code and screenshot, I think your'e fine.
There's actually a feature suggested from this article to check the stability of a composable, I run it and I got this report.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun PagerSampleItem(
stable page: Int
)
Everything about this report is within the article I linked.
Also, your Text and Icon are using String and ImageVector which is stable and immutable (marked by #Immutable) respectively.
So TLDR, IMO your code is fine, your PagerSampleItem is not re-composing in the screenshot.

How to use both "ModalDrawer" and "ModalBottomSheetLayout" at the same time

My app needs ModalDrawer & ModalBottomSheetLayout at the same time.
I searched but couldn't find the officially recommended way to accomplish it.
So I just put ModalDrawer inside ModalBottomSheetLayout. Please check the below code.
#Composable
fun MainScreen()
{
ModalBottomSheetLayout(
sheetState = recipeViewModel.bottomSheetState,
sheetContent = { #AnotherComposable },
sheetShape = RoundedCornerShape(topStart = 50.dp, topEnd = 50.dp)
) {
// "Rest-Content Area"
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
) {
Buttons()
}
// Drawer
ModalDrawerSample()
}
}
ModalDrawerSample() working fine without any issue.
However, the UIs in the "Rest-Content Area" is not working. For example, button clicking it not working at all.
When we press the button, the color of the button area should be changed (gray like colored) but it's not.
Is there any official document that recommend the best practice or any other solution for it?

Jetpack Compose Toggleable and MouseClickable consume the same event

I am trying to find a way for a making a row both toggleable and having a dropdown menu if right click is clicked in Jetpack Compose Desktop.
Row(
modifier = Modifier.height(IntrinsicSize.Min)
.background(if (selected) MaterialTheme.colors.secondary else Color.Transparent)
.mouseClickable(
onClick = {
if (this.buttons.isSecondaryPressed) {
onRightMouseClick.invoke(index)
}
})
.toggleable(
value = !selected,
onValueChange = {
onItemSelected.invoke(!selected, index)
}
)
)
Is there any way to consume both events at the same time?
If i change the order of the modifiers, then the second one is always consumed.
After not finding an answer for quite some while, I decided to do the implementation a bit differently and have used the logic behind selection inside mouseClickable (not the best of solutions but a workaround at least).
Row(
modifier = Modifier.height(IntrinsicSize.Min)
.background(if (selected) MaterialTheme.colors.secondary else Color.Transparent)
.mouseClickable(
onClick = {
if (this.buttons.isSecondaryPressed) {
onRightMouseClick.invoke(index)
}else{
onItemSelected.invoke(!selected, index)
}
})

Long-press event on IconToggleButton

I have a toggle button. It works fine, but I want to be able to long-press it.
IconToggleButton(
checked = isChecked,
onCheckedChange = onCheck,
modifier = modifier
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
showingHelpDialog = true
}
)
}
) {
Icon(
imageVector = if (isChecked) {
Icons.Filled.Star
} else {
Icons.Filled.StarBorder
},
contentDescription = null,
)
}
This doesn't work, because IconToggleButton itself overrides pointerInput through the toggleable modifier.
I could copy all the source code from IconToggleButton/toggleable and hack in my own pointerInput. But that would require me to copy a lot of low-level component logic which I expressly don't want to copy into my own source code, because it should be part of the Material library and I want to benefit from potential improvements in the Material library.
Is there another way I could listen for long-press events?
instead of using
modifier.pointerInput(Unit) { /*...*/ }
use
modifier.combinedClickable(
onLongClick = { /*....*/ },
onClick ={ /*....*/ })

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