How to create BottomNavigation with one of the item is larger than the parent, but without using floatingActionButton. For example like this:
I tried to do that by wrapping the icon with Box but it get cut like this:
Then i try to separate that one button and use constraintLayout to position it, but the constraintLayout cover the screen like this. Even when i color it using Color.Transparent, it always feels like Color.White (i dont know why Color.Transparent never work for me). In this picture i give it Red color for clarity reason.
So how to do this kind of bottomNavBar without having to create heavy-custom-composable?
Update: so i try to make the code based on MARSK and Dharman comment (thanks btw). This is what i
BoxWithConstraints(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(Color.Transparent)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.White)
.align(Alignment.BottomCenter)
)
Row(
modifier = Modifier
.zIndex(56.dp.value)
.fillMaxWidth()
.selectableGroup(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
items.forEach { item ->
val selected = item == currentSection
BottomNavigationItem(
modifier = Modifier
.align(Alignment.Bottom)
.then(
Modifier.height(
if (item == HomeSection.SCAN) 84.dp else 56.dp
)
),
selected = selected,
icon = {
if (item == HomeSection.SCAN) {
ScanButton(navController = navController, visible = true)
} else {
ImageBottomBar(
icon = if (selected) item.iconOnSelected else item.icon,
description = stringResource(id = item.title)
)
}
},
label = {
Text(
text = stringResource(item.title),
color = if (selected) Color(0xFF361DC0) else LocalContentColor.current.copy(
alpha = LocalContentAlpha.current
),
style = TextStyle(
fontFamily = RavierFont,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 18.sp,
),
maxLines = 1,
)
},
onClick = {
if (item.route != currentRoute && item != HomeSection.SCAN) {
navController.navigate(item.route) {
launchSingleTop = true
restoreState = true
popUpTo(findStartDestination(navController.graph).id) {
saveState = true
}
}
}
}
)
}
}
}
It works in preview, but doesn't work when i try in app.
This one in the preview, the transparent working as expected:
And this is when i try to launch it, the transparent doesnt work:
Note: I assign that to bottomBar of Scaffold so i could access the navigation component. Is it the cause that Transparent Color doesnt work?
Update 2: so the inner paddingValues that makes the transparent doesnt work. I fixed it by set the padding bottom manually:
PaddingValues(
start = paddingValues.calculateStartPadding(
layoutDirection = LayoutDirection.Ltr
),
end = paddingValues.calculateEndPadding(
layoutDirection = LayoutDirection.Ltr
),
top = paddingValues.calculateTopPadding(),
bottom = SPACE_X7,
)
Custom Composable are not heavy, really.
Anyway, try this:-
Create a Container of MaxWidth (maybe a BoxWithConstraints or something), keep its background transparent, set the height to wrap content. Create the tabs as usual, but keeping the bigger tab's icon size bigger explicitly using Modifier.size(Bigger Size).
After you have this setup, add another container inside this container with white background, covering a specific height of the original container. Let's say 60%
Now set the z-index of all the icons and tabs to higher than the z-index of this lastly added container. Use Modifier.zIndex for this. And viola, you have your Composable ready.
In order to set a specific percentage height of the inner container, you will need access to the height of the original container. Use BoxWithConstraints for that, or just implement a simple custom Layout Composable
Related
I'm currently trying to recreate a behavior, upon adding a new element to a LazyColumn the items start shifting to the right, in order to represent a Tree and make the elements easier to read.
The mockup in question:
Documentation
Reading through the documentation of Jetpack Compose in Lists and grids I found the following.
Keep in mind that cases where you’re nesting different direction layouts, for example, a scrollable parent Row and a child LazyColumn, are allowed:
Row(
modifier = Modifier.horizontalScroll(scrollState)
) {
LazyColumn {
// ...
}
}
My implementation
Box(Modifier.padding(start = 10.dp)) {
Row(
modifier = Modifier
.horizontalScroll(scrollState)
.border(border = BorderStroke(1.dp, Color.Black))
) {
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
for (i in 0..25) {
item {
OptionItem(Modifier.padding(start = (i*20).dp))
}
item {
TaskItem(Modifier.padding(start = (i*10).dp))
}
}
}
}
.
.
.
}
OptionItem represents the element with the dot at the beginning, and TaskItem the other one.
When testing the LazyColumn, it appears as if instead of having a fixed size, the size of the column starts growing just after the elements have gone outside the screen, this causes a strange effect.
As you can see in the GIF, the width of the column starts increasing after the elements no longer fit in the screen.
The Question
I want to prevent this effect from happening, so is there any way I could maintain the width of the column to the maximum all the time?
The reason that applying a simple fillMaxWidth will not work because you are telling a composable to stretch to max, but that is impossible because the view itself can stretch indefinitely since it can be horizontally scrollable. I'm not sure why do you want to prevent this behavior but perhaps maybe you want your views to have some initial width then apply the padding, while maintaining the same width. what you can do in such case is simply give your composables a specific width, or what you can do is to get the width of the box and apply them to your composables by width (i used a text in this case)
val localDensity = LocalDensity.current
var lazyRowWidthDp by remember { mutableStateOf(0.dp) }
Box(
Modifier
.padding(start = 10.dp)
.onGloballyPositioned { layoutCoordinates -> // This function will get called once the layout has been positioned
lazyRowWidthDp =
with(localDensity) { layoutCoordinates.size.width.toDp() } // with Density is required to convert to correct Dp
}
) {
val scrollState = rememberScrollState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(scrollState)
) {
items(25) { i ->
Text(
text = "Hello",
modifier = Modifier
.padding(start = (i * 20).dp)
.width(lazyRowWidthDp)
.border(1.dp, Color.Green)
)
}
items(25) { i ->
Text(
text = "World",
modifier = Modifier
.padding(start = (i * 10).dp)
.width(lazyRowWidthDp)
.border(1.dp, Color.Green)
)
}
}
}
Edit:
you can apply horizontal scroll to the lazy column itself and it will scroll in both directions
The library I'm using: "com.google.accompanist:accompanist-placeholder-material:0.23.1"
I want to display a placeholder in the place of (or over) a component when it's in the loading state.
I do the following for a Text:
MaterialTheme() {
var placeholderVisible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
placeholderVisible = !placeholderVisible
}
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.border(1.dp, Color.Red)
.padding(16.dp)
) {
Text(
modifier = Modifier
.then(
if (placeholderVisible) {
Modifier.height(28.dp).width(62.dp)
} else {
Modifier
}
)
.placeholder(
visible = placeholderVisible,
highlight = PlaceholderHighlight.shimmer()
),
text = if (placeholderVisible) "" else "Hello"
)
}
}
}
And I get this:
I want instead that no matter how big I set the placeholder's height or width, it will not participate in any way in the measuring process and, if I want to, to be able to draw itself even over other components (in this case let's say the red border).
As an effect of what I want, the box with red border will always have the dimension as if that Modifier.height(28.dp).width(62.dp) is not there.
I know I can draw outside a component's borders using drawWithContent, specifying the size of a rectangle or a circle (or whatever) to be component's size + x.dp.toPx() (or something like that). But how do I do this with Modifier.placeholder?
Ideally, I would need something like Modifier.placeholder(height = 28.dp, width = 62.dp)
So, with or without this ideal Modifier, the UI should never change (except, of course, the shimmer box that may be present or not).
I think I can pull this off by modifying the source code of this Modifier, but I hope I won't need to turn to that.
Just replace your Text() with below code, maybe conditional Modifier is the issue in above code!
Text(
modifier = Modifier
.size(width = 62.dp, height = 28.dp)
.placeholder(
visible = placeholderVisible,
highlight = PlaceholderHighlight.shimmer()
),
text = if (placeholderVisible) "" else "Hello",
textAlign = TextAlign.Center
)
I am struggling with the jetpack compose LazyColumn and the stickyHeader functionality. Basically the static view works well, but once I start scrolling, the items would go over the sticky headers, the scrolling starts a weird behaviour and the last item would never be visible as the scrolling always bounces back.
Here's how it looks like:
Here's the composable:
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun CollectionsScreen(
collectionsLive: LiveData<List<CollectionsView>>,
onCollectionChanged: (ICalCollection) -> Unit
/* some more hoisted functions left out for simplicity */
) {
val list by collectionsLive.observeAsState(emptyList())
val grouped = list.groupBy { it.accountName ?: it.accountType ?: "Account" }
LazyColumn(
modifier = Modifier.padding(8.dp)
) {
item {
Text(
stringResource(id = R.string.collections_info),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
}
grouped.forEach { (account, collectionsInAccount) ->
stickyHeader {
Text(
account,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
)
}
items(
items = collectionsInAccount,
key = { collection -> collection.collectionId }
) { collection ->
CollectionCard(
collection = collection,
allCollections = list,
onCollectionChanged = onCollectionChanged,
/* some more hoisted functions left out for simplicity */
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.animateItemPlacement()
.combinedClickable(
//onClick = { onCollectionClicked(collection) }
)
)
}
}
}
}
I am really not sure what is causing this issue as the code itself is pretty straightforward from the example provided in the documentation. Only the CollectionCard itself is a more complex structure.
I have also tried removing the header text (the first item) and removed the Modifier.animateItemPlacement() for the card, but with no difference, the problem stays the same...
The composable itself is used in a Compose View within a Fragment, but there is no nested scrolling.
Do you have any idea what could cause this strange behaviour? Or might this be a bug when using cards within the LazyColumn with sticky headers?
UPDATE:
It seems like the problem nothing to do with the stickyHeader, but somehow with the LazyColumn. If I replace the "stickyHeader" just with "item", the problem still persists... Only when I replace the lazyColumn with a column it would work. But I assume that there must be a solution for this problem...
Setting the stickyHeader background color will help.
stickyHeader {
Text(
"text",
modifier = Modifier.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
.background(colorResource(id = R.color.white))
)
}
I don't know if you solved it yet, but try to fillMaxWidth and set the background. This code worked for me.
Text(
account,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
.fillMaxWidth()
.background(MaterialTheme.colors.background)
)
In general, if you are using Material or Material3 theming, you can wrap your stickyHeader content in a Surface to automatically make it non-transparent with your theme's standard (or customized) coloring scheme. Surface lets you raise the stickyHeader above your table's other contents.
stickyHeader {
Surface(Modifier.fillParentMaxWidth()) {
Text("Header")
}
}
You can customize the Surface at your heart's desire.
I'd create another issue for the bounciness problem, it looks like a separate concern.
Just provide a background for the sticky header.
I have a problem with RadioButton component in my Jetpack Compose application. I have some RadioButtons with text and this have a lot of padding by default. Can I remove this padding or to set a custom padding to avoid a lot of space between each?
Currently I have this:
My code is:
Column {
MyEnum.values().filter { rb -> rb.visible }.forEach { rb ->
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 0.dp, vertical = 0.dp)
.clickable(
interactionSource = interactionSource,
indication = null
) {
TODO()
},
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = (rb.position == selectedOption),
onClick = {
TODO()
},
colors = RadioButtonDefaults.colors(
selectedColor = DialogOutlinedTextFocus,
unselectedColor = DialogOutlinedTextUnfocus
)
)
Text(
text = stringResource(id = rb.idText),
color = Color.Black,
fontSize = 14.sp,
modifier = Modifier
.padding(horizontal = 3.dp, vertical = 2.dp)
)
}
}
}
I tried with contentPadding, but this property does not exist in RadioButton component.
The source code for RadioButton.kt sets the padding modifier at line 108. With modifiers, order matters. Even if you provide your own padding modifier it will be overridden by line 108.
The sizing values are hardcoded at the bottom of the file.
If you really want to "reduce the padding", apply a size modifier. I use 20.dp because it's equal to radioButtonSize in RadioButton.kt so the button doesn't get clipped. This should work for your purposes since the entire row is clickable.
RadioButton(
modifier = Modifier.size(20.dp),
selected = selected,
onClick = { TODO() },
)
Although, you're probably better off in the long term making custom components you can control. Luckily, the source code is ready available. Just copy, paste and adjust to your needs.
You could specify the Row height in the Row.Modifier
like this:
Row(
Modifier
.fillMaxWidth()
//HERE YOU GO
.height(30.dp)
.padding(horizontal = 0.dp, vertical = 0.dp)
I have a question regarding ScrollableTabRow and edge padding
is there a possibility to set padding only for the items inside, and not the left and right edge?
simplified code: https://gist.github.com/a/4cd4994c91b4de2c59d7f6a1f5f1da12
What i need is:
https://imgur.com/a/TbIbOVP
but i get something like this,
https://imgur.com/a/FspxhVU
maybe i’m setting something wrong, or there is some internal padding that i cannot change? (Basically i need to set the padding between items to be 16.dp)
i managed to get something like i want using lazy row:
https://gist.github.com/a/4fdb1b124e067e5ec8d50b933477bea7
https://imgur.com/a/DzM6IKy
but that would mean implementing logic for the indicator, and if possible i’d like to avoid that
You can use edgePadding = 0.dp for remove this padding like this:
crollableTabRow(
selectedTabIndex = 0, indicator = { tabPositions ->
Box(
modifier = Modifier
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
.height(4.dp)
.padding(horizontal = 8.dp)
.background(color = Color.Blue, shape = RoundedCornerShape(8.dp))
) {})
}, modifier = Modifier.fillMaxWidth(), edgePadding = 0.dp, backgroundColor = colorResource(
id = R.color.white
)
)
Couldnt find a solution, so i went with lazy row and custom indicator
simply use
edgepadding = 0
to remove starting and ending padding of scrollable row tab in compose
I can't read your code.
I solve your problem:
Basically i need to set the padding between items to be 16.dp
by adding a Tab warpper and set padding for it.
#Composable
fun Sample(){
ScrollableTabRow(
backgroundColor = Color.Transparent,
selectedTabIndex = 0,
edgePadding = 24.dp,
modifier = Modifier.height(80.dp)
) {
(1..20).forEach { index ->
Tab(
selected = false,
onClick = { },
modifier = Modifier.padding(10.dp)
){
Text("DemoBox_$index")
}
}
}
}