Some of my screens do not need the drawer so I want the navigation icon not to show on the topbar. With the following code, I made the icon not shown but the space is still kept for the icon. How can I eliminate it?
TopAppBar(
title = {
Text(text = title,
textAlign = TextAlign.Start,
fontSize = 18.sp) },
actions = { TopbarActions()},
navigationIcon = {
if (needDrawer) {
IconButton(onClick = {
}) {
Icon(Icons.Filled.Menu, "")
}
} else Spacer(Modifier.width(1.dp))
},
)
You're creating an empty view, but you need to let TopAppBar know that there should be no view.
Pass null to navigationIcon like this:
TopAppBar(
navigationIcon = if (needDrawer) {
#Composable {
IconButton(onClick = {
}) {
Icon(Icons.Filled.Menu, "")
}
}
} else null
)
Related
I'm created custom dialog from common class in ini
init {
activity.setContent {
CustomDialog(viewModel)
}
}
#Composable
fun CustomDialog(viewModel: ViewModel){
Dialog(
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
}
}
But under dialog background is an empty activity, but must be a preference activity.
Not correct composable:
correct dialog via XML:
I tried, but didn't help
Surface(modifier = Modifier.background(Color.Transparent)) {
CustomDialog(viewModel)
}
```
Exampe:
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(size = 16.dp)
).clip(shape = RoundedCornerShape(size = 16.dp))
) {
//.....
}
}
Found solution:
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog?.window?.setDimAmount(0.0f)
I have been doing dictionary app for a while. when I delete dictionary snackBar shows and writes dictionary is deleted but there is a floating action button and when the snackBar appears on the screen ,the snackbar appears above the floating action button, I don't want it to appear on it. It just stays on the screen for 1-2 seconds. I want the floating action button and snackbar to appear on top of each other. I couldn't adapt this to my own code. How can I do it ? I will share my code and image
CreateYourOwnDictionaryScreen
#Composable
fun CreateYourOwnDictionaryScreen(
navController: NavController,
viewModel: CreateYourOwnDictionaryViewModel = hiltViewModel()
) {
val scaffoldState = rememberScaffoldState()
val state = viewModel.state.value
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
backgroundColor = bar,
title = {
androidx.compose.material3.Text(
text = "your dictionaries",
modifier = Modifier.fillMaxWidth(),
color = Color.White,
fontSize = 22.sp
)
},
navigationIcon = {
IconButton(onClick = {
navController.navigate(Screen.MainScreen.route)
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Go Back"
)
}
}
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
FloatingActionButton(
onClick = { navController.navigate(Screen.CreateDicScreen.route) },
backgroundColor = bar,
) {
Icon(Icons.Filled.Add, "fab")
}
}
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(state.dictionaries) { dictionary ->
CreateYourOwnDictionaryItem(
dictionary = dictionary,
modifier = Modifier
.fillMaxWidth()
.clickable {
},
onDeleteClick = {
viewModel.onEvent(
CreateYourOwnDictionaryEvents.DeleteDictionary(dictionary)
)
scope.launch {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "dictionary is deleted",
actionLabel = "Undo",
duration = SnackbarDuration.Short
)
}
},
onEditClick = {
})
}
}
}
}
}
}
Image
I'm afraid this is difficult in Compose, you can dig the ScaffoldLayout and you'll find this code block.
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
} else {
0
}
Scaffold will always offset the snackbar on top of a fab or a bottombar
And based on the answer in this post regarding material specs
This is specifically one of the "Don't" examples from the Material guidelines: https://material.io/components/snackbars#behavior
Making sure visual elements don't move out from underneath (say, when users are about to tap the FAB) is one of the key points to making a predictable UI
Also based on the Material Guidelines
Consecutive snackbars should appear above persistent bottom navigation
I'm trying to have the text field appear when the "Search Movies" button is selected, and disappear when not selected.
When I tap on the "Search Movies" button, the app seems to recognize the clicks constantly.
Here's a video of what it looks like: https://youtu.be/5LQ8k0Y05Cc
As you can see, the text field also doesn't disappear when the "Popular Movies" button is selected.
Here's the composable function:
#Composable
fun MaterialButtonToggleGroup() {
val options = listOf(
"Popular Movies",
"Search Movies"
)
var selectedOption by remember {
mutableStateOf("")
}
val onSelectionChange = { text: String ->
selectedOption = text
}
Column() {
var isExpanded by remember { mutableStateOf(false) }
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth(),
) {
options.forEach { text ->
Row(
modifier = Modifier
.padding(
vertical = 8.dp,
),
) {
Text(
text = text,
style = typography.body1.merge(),
color = Color.White,
modifier = Modifier
.clip(
shape = RoundedCornerShape(
size = 12.dp,
),
)
.clickable {
onSelectionChange(text)
}
.background(
if (text == selectedOption) {
Color.Magenta
} else {
Color.Gray
}
)
.padding(
vertical = 12.dp,
horizontal = 16.dp,
),
)
}
}
}
if (selectedOption == "Search Movies") {
isExpanded = !isExpanded
}
if (isExpanded) {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
modifier = Modifier.fillMaxWidth()
.padding(all = 3.dp),
onValueChange = { text = it },
label = { Text("Enter Movie Info") }
)
}
}
(activity as MainActivity?)?.viewSelection(selectedOption)
}
Surface {
MaterialButtonToggleGroup()
}
Turns out I should just set isExpanded to true or false for each scenario.
I changed:
if (selectedOption == "Search Movies") {
isExpanded = !isExpanded
}
if (isExpanded) {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
modifier = Modifier.fillMaxWidth()
.padding(all = 3.dp),
onValueChange = { text = it },
label = { Text("Enter Movie Info") }
)
}
To:
if (selectedOption == "Search Movies") {
isExpanded = true
} else {
isExpanded = false
}
if (isExpanded) {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
modifier = Modifier
.fillMaxWidth()
.padding(all = 3.dp),
onValueChange = { text = it },
label = { Text("Enter Movie Info") }
)
}
I have an IconButton in the trailingIcon of OutlinedTextField like:
OutlinedTextField(
modifier = Modifier.weight(1f),
label = { Text(text = "Label") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(onClick = {
println("onClick")
}, onLongClick = {
println("onLongClick shows TextToolbar")
}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
}
)
IconButton2 is just a copy of IconButton but with combinedClickable to include onLongClick instead of clickable.
The problem is that when I long click IconButton2, it shows the TextToolbar for the TextField. Doesn't matter what I do, the text field will handle long click, show the TextToolbar and provide haptic feedback.
Even if I use pointerInput with awaitPointerEvent and consumeAllChanges (like here) it still triggers it. The TextField doesn't answer to any tap or anything but if I long click it, it answers!
The workaround I'm doing for now is wrapping the text field in a Row and add the IconButton beside it instead of "inside" but I needed to have the icon button as the trailingIcon.
Is there any way to properly do it?
Compose 1.0.3 and 1.1.0-alpha05 both behaves the same.
I ended up making a small hack that seems to be working fine, so basically I add a dummy Box as the trailingIcon to get the position of it, then I add an IconButton outside of it (both wrapped in a Box) and I also get the position of it + I offset it using the position of the dummy box. Not the ideal solution but works fine.
Here's the full source if anyone needs it:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colors.background
) {
var text by remember { mutableStateOf("") }
var trailingIconOffset by remember { mutableStateOf(Offset.Zero) }
var iconButtonOffset by remember { mutableStateOf(Offset.Zero) }
val colors = TextFieldDefaults.outlinedTextFieldColors()
Column {
//With hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "With hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
Box(modifier = IconButtonSizeModifier
.onGloballyPositioned {
trailingIconOffset = it.positionInRoot()
}
)
},
colors = colors
)
val contentColor by colors.trailingIconColor(
enabled = true,
isError = false
)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalContentAlpha provides contentColor.alpha
) {
IconButton2(
modifier = Modifier
.onGloballyPositioned {
iconButtonOffset = it.positionInRoot()
}
.absoluteOffset {
IntOffset(
(trailingIconOffset.x - iconButtonOffset.x).toInt(),
(trailingIconOffset.y - iconButtonOffset.y).toInt()
)
},
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
}
}
//Without hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Without hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
},
colors = colors
)
}
}
}
}
}
}
}
private val RippleRadius = 24.dp
private val IconButtonSizeModifier = Modifier.size(48.dp)
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun IconButton2(
modifier: Modifier = Modifier,
onClick: () -> Unit,
onLongClick: (() -> Unit)? = null,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: #Composable () -> Unit
) {
Box(
modifier = modifier
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = RippleRadius)
)
.then(IconButtonSizeModifier),
contentAlignment = Alignment.Center
) {
val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled
CompositionLocalProvider(LocalContentAlpha provides contentAlpha, content = content)
}
}
I'm trying to create a dialog where you can pick a number. I use FlowRow from Accompanist. But it gives strange results when scrolling. I guess that I'm missing something here.
The code:
#Composable
fun AlertDialogErrorTest() {
var showDlg by remember { mutableStateOf(false)}
val scrollState = rememberScrollState()
if (showDlg) {
AlertDialog(
onDismissRequest = { showDlg = false },
title = { Text(text = "Pick something from the list") },
text = {
FlowRow(modifier = Modifier.verticalScroll(scrollState)) {
for (i in 1..100) {
Box (modifier = Modifier.size(48.dp).padding(8.dp).background(Color.LightGray), contentAlignment = Alignment.Center) {
Text (i.toString())
}
}
}
},
confirmButton = {
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
},
dismissButton = {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
}
)
}
Button(onClick = {showDlg = true}) {
Text("Show dialog")
}
}
The result when scrolling:
This is because AlertDialog does not currently support scrollable content. If you think this is a bug, you can report it in the Compose issue tracker.
Meanwhile, you can use a plain Dialog, which lays under the AlertDialog.
Dialog(
onDismissRequest = { showDlg = false },
content = {
Column(Modifier.background(Color.White)) {
Text(
text = "Pick something from the list",
style = MaterialTheme.typography.subtitle1,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
FlowRow(
modifier = Modifier
.verticalScroll(scrollState)
.weight(1f)
) {
for (i in 1..100) {
Box(
modifier = Modifier
.size(48.dp)
.padding(8.dp)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(i.toString())
}
}
}
FlowRow(
mainAxisSpacing = 8.dp,
crossAxisSpacing = 12.dp,
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
}
}
},
)
Result:
I have faced the same issue, and the work around is to pass null in both title , and text, and to put the whole content in the buttons, you might check the below example, and please forgive my naming :).
AlertDialog(
onDismissRequest = {
state.onDismiss(state.data)
},
title = null,
text = null,
buttons = {
Column {
val expandedState =
state.data.expandedState
expandedState?.let {
RenderFieldPerCorrespondingState(it.title)
LazyColumnViewGroupComposable(
viewGroupState = it.itemsState,
scrollTo = scrollTo
)
ViewGroupComposable(viewGroupState = it.actionsState)
}
}
}
)