I want to disable a whole view from any interaction (e.g. button presses) when a Boolean in my view model is true. How can I do this in Jetpack Compose without having to disable each of the elements within the view?
See example below as to what I'm trying to do.
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
Column(
/*
Disable all elements in the column so I don't need to disable each element individually for example:
modifier = Modifier
.disabled(
if (alertViewModel.showAlert == true) {
true
} else {
false
}
*/
) {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
}
Proceed like this :
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
if (alertViewModel.showAlert == true) {
Text(text = "Nothing to show")
} else {
Column(modifier = Modifier) {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
}
}
Example:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
Box() {
Column() {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
Box(
modifier = Modifier
.then(
if(alertViewModel.showAlert == true){
Modifier.fillMaxSize().disabled(true)
}
)
)
}
}
Seems as though it isn't possible to disable a whole view in Jetpack Compose. All interact-able elements such as Button and TextField and have to be set to enabled = false individually.
Buttons: Jetpack Compose: How to disable FloatingAction Button?
TextFields: Jetpack Compose: Disable Interaction with TextField
Related
I have a scrollable container (LazyColumn) and I want to navigate to the next FocusRequester in the list. But if the FocusRequester exists outside the rendered elements the FocusRequester can not be reached by FocusManger.
Code to reproduce problem:
#ExperimentalComposeUiApi
#Composable
fun exampleDesktop() { // Also works on phone but you need an external keyboard
val focusManager = LocalFocusManager.current
fun handleKeyPreview(evt: KeyEvent) : Boolean {
return if (evt.type == KeyEventType.KeyDown && evt.key == Key.Tab) {
if (evt.isShiftPressed) {
focusManager.moveFocus(FocusDirection.Up)
} else {
focusManager.moveFocus(FocusDirection.Down)
}
true
} else false
}
LazyColumn {
repeat(100) {
item {
FocusableItem(it, ::handleKeyPreview)
}
}
}
}
#ExperimentalComposeUiApi
#Composable
fun examplePhone() {
val focusManager = LocalFocusManager.current
Column {
Row {
Button(onClick = { focusManager.moveFocus(FocusDirection.Up)}) { Text("Up")}
Button(onClick = { focusManager.moveFocus(FocusDirection.Down)}) { Text("Down")}
}
LazyColumn {
repeat(100) {
item {
FocusableItem(it, handler = {false})
}
}
}
}
}
#Composable
fun FocusableItem(i: Int, handler: (KeyEvent) -> Boolean) {
val requester = remember { FocusRequester() }
TextField(
modifier = Modifier
.focusRequester(requester)
.onPreviewKeyEvent(onPreviewKeyEvent = handler),
value = "$i", onValueChange = {})
}
Is there a way to know of the existence of other focus requesters and put them into the view?
when I open DropdownMenu, my keyboard auto-close. This behavior is not my expect.
I expect my keyboard to remain open when I open the DropdownMenu.
current behavior:
want behavior:
Thanks to my friends in the compose community for helping me with this on slack.
Just set the properties.focusable property to false
var content by remember { mutableStateOf("") }
var deadLineMenuExpanded by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
TextField(value = content, onValueChange = {content = it})
Box() {
TextButton(onClick = { deadLineMenuExpanded = true }) {
Text("open Menu")
}
DropdownMenu(
expanded = deadLineMenuExpanded,
onDismissRequest = { deadLineMenuExpanded = false },
properties = PopupProperties(focusable = false)
) {
DropdownMenuItem(onClick = {
deadLineMenuExpanded = false
}) {
androidx.compose.material.Text("today")
}
}
}
}
Perhaps this is the normal behaviour, but i wish it was different. I had tried to google the solution, but did not find anything suitable (or merely missed it).
Sample code (for simplicity i hold mutable states right here, not using ViewModel):
#Composable
fun Greeting() {
Scaffold(topBar = {
TopAppBar(title = { Text(text = "Some title") })
}) {
val focusManager = LocalFocusManager.current
LazyColumn(
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(count = 20) { index ->
val (value, onValueChange) = rememberSaveable { mutableStateOf("Some value $index") }
TextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Some label $index") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {
if (!focusManager.moveFocus(FocusDirection.Down))
focusManager.clearFocus()
}),
singleLine = true
)
}
}
}
}
Compose version 1.0.5
You could try just hiding the keyboard whenever scrolling occurs. This is okay as long as you don't have a large set of items. But since you're using TextFields, it isn't likely that you'll have such a large number. This sample illustrates hiding the keyboard when scrolling occurs:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
Greeting(this)
}
}
}
#Composable
fun Greeting(activity: Activity) {
Scaffold(topBar = {
TopAppBar(title = { Text(text = "Some title") })
}) {
val lazyListState = rememberLazyListState()
val ctx = LocalContext.current
LaunchedEffect(lazyListState.firstVisibleItemIndex) {
val inputMethodManager = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(activity.window?.decorView?.windowToken, 0)
}
LazyColumn(
state = lazyListState,
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(
count = 20,
key = { index ->
// Return a stable + unique key for the item
index
}
) { index ->
val (value, onValueChange) = rememberSaveable { mutableStateOf("Some value $index") }
TextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "Some label $index") },
singleLine = true
)
}
}
}
}
I need the whole column as one scroll component. I can't use item directly since I need to use aclassen/ComposeReorderable library for drag and drop scrolling. Any ideas?
val scrollState = rememberScrollState()
Column(
modifier = Modifier.verticalScroll(scrollState)
) {
LazyColumn(){
repeat(20) {
item {
Text(text = "Item: " + it)
}
}
}
Text(text = "Header")
LazyColumn(){
repeat(20) {
item {
Text(text = "Item: " + it)
}
}
}
}
I tried this but the scrolling feels unnatural and I don't know how to add the nestedScroll on non list Composables like the header
val offset = remember { mutableStateOf(0f) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
offset.value = (offset.value + available.y).coerceIn(-200f, 0f)
return Offset.Zero
}
}
}
Column(
modifier = Modifier.offset(y = offset.value.dp)
) {
LazyColumn(
modifier = Modifier.nestedScroll(nestedScrollConnection)
){
repeat(20) {
item { Text(text = "Item: $it") }
}
}
Text(text = "Header")
LazyColumn(
modifier = Modifier.nestedScroll(nestedScrollConnection)
){
repeat(20) {
item { Text(text = "Item: $it") }
}
}
}
Dialog moves up when keyboard appears. I want a Composable do the same , but i cant find how to do this.
Ok, I've found the answer.
First, in manifest:
<activity
android:windowSoftInputMode="adjustResize">
After that use "Insets", that are explained in this page:
https://google.github.io/accompanist/insets/
in the following example I use a fab that makes a textfield appear that is positioned above the keyboard
PD: Notice in FloatingActionButton I use Modifier.systemBarsPadding(), it is made so that it is not hidden
class MainActivity : ComponentActivity() {
#ExperimentalAnimatedInsets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MyApplicationTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
val focusRequester = FocusRequester()
Greeting(focusRequester)
}
}
}
}
}
#ExperimentalAnimatedInsets
#Composable
fun Greeting(focusRequester: FocusRequester) {
var creatorVisibility by remember{ mutableStateOf(false)}
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Scaffold(floatingActionButton = {
Fab(onClick = { creatorVisibility = true }, creatorVisibility = creatorVisibility)}
) {
ConstraintLayout(constraintSet = constraints, modifier = Modifier.fillMaxSize(1f)) {
TextField(
value = TextFieldValue(),
onValueChange = { /*TODO*/ },
modifier = Modifier.layoutId("mainTf")
)
if (creatorVisibility){
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
creatorVisibility = false
}
.background(color = Color(0f, 0f, 0f, 0.5f))
,contentAlignment = Alignment.BottomCenter
) {
SideEffect(effect = { focusRequester.requestFocus() })
TextField(colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White),
value = TextFieldValue(),
onValueChange = { /*TODO*/ },
modifier = Modifier
.layoutId("textField")
.imePadding()
.focusRequester(focusRequester = focusRequester)
)
}
}
}
}
}
}
#Composable
fun Fab(onClick : () -> Unit, creatorVisibility:Boolean){
if (!creatorVisibility){
FloatingActionButton(
onClick = { onClick() },
modifier = Modifier.layoutId("fab").systemBarsPadding()
) {
Text(text = "Crear tarea", modifier = Modifier.padding(start = 10.dp, end = 10.dp))
}
}
}
val constraints = ConstraintSet {
val textField = createRefFor("textField")
val mainTf = createRefFor("mainTf")
val fab = createRefFor("fab")
constrain(textField){
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
constrain(mainTf){
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
constrain(fab){
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
}