I am trying to get Tabs working with a TabRow on Android with compose. What I'd like is the TabRow to have a white background.
The default color seems to be this purple(ish) as shown in the documentation (https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary).
When I set backgroundColor to White the Tabs are grey for some reason.
How would you achieve white Tabs in Compose?
Thanks!
EDIT: Google have now fixed this issue: https://issuetracker.google.com/issues/197254738. Hopefully it will find its way into a JC release soon!
Question's a couple of months old, but recently encountered this issue so others might find these solutions useful:
Solution 0 (quickest)
Don't have a divider. For example:
TabRow(
selectedTabIndex = ...,
modifier = Modifier.height(100.dp).fillMaxWidth(),
divider = {}
) { /* content here */ }
Solution 1
Since the TabRow is just a container, don't specify a height in its modifier. If you want it to have a custom height, instead make sure that each of the Tabs have their heights specified explicitly. For example:
var selectedTabIndex by remember { mutableStateOf(0) }
TabRow(
selectedTabIndex = selectedTabIndex,
modifier = Modifier.fillMaxWidth(), // Don't specify the TabRow's height!
backgroundColor = Colors.White
) {
listOf("Hello", "World").forEachIndexed { i, text ->
Tab(
selected = selectedTabIndex == i,
onClick = { selectedTabIndex = i },
modifier = Modifier.height(50.dp), // Specify the Tab's height instead
text = { Text(text) }
)
}
}
Solution 2
Force the divider to only draw at the bottom of the layout. This is consistent with the default indicator implementation.
TabRow(
selectedTabIndex = ...,
modifier = Modifier.height(100.dp).fillMaxWidth(),
backgroundColor = Colors.White,
divider = { TabRowDefaults.Divider(Modifier.wrapContentSize(Alignment.BottomStart)) },
) { /* content here */ }
Explanation
From the source code here and here, the default divider has height 1dp. However, the OP is seeing a grey background because the divider is drawing over the whole TabRow!
When you specify a height constraint on a TabRow, the constraint gets passed through to the divider (source code here). The divider therefore takes up the entire height of the TabRow – in OP's case, the divider is a transparent grey colour, so it makes the TabRow look grey. The above solutions have a few different ways to solve the problem:
Removing the divider removes the issue!
TabRows wrap their tabs, so an alternative solution is to give the TabRow height by specifying the tabs' heights rather than the TabRow's.
This forces the divider to ignore the TabRow's height constraint, and instead draw itself at its correct height at the BottomStart position in the TabRow.
You can set color using backgroundColor: Color = MaterialTheme.colors.primarySurface,.
(For Documentation read this)
Sample code below:
#Composable
fun TabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
indicator: (List<TabPosition>) -> Unit = #Composable { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: () -> Unit = #Composable {
TabRowDefaults.Divider()
},
tabs: () -> Unit
): #Composable Unit
Related
I am using the Card composable and I want it to be colored white.
But when I add some elevation to it, it changes color to more like the primaryContainer color.
I have seen documentation where they have somehting called as elevationOverlay. But couldn't find an example which says how to use it.
Here is my code:
Card(
modifier = Modifier.padding(top = 16.dp),
colors = CardDefaults.cardColors(containerColor = White),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
}
I do know I can use Elevated card instead of card, but there is same problem with elevated card as well.
Also, this is a special case so I am applying the color manually
To resolve the issue of changing card color when modifying the card elevation in Jetpack Compose with Material Design 3, you can use the background modifier and pass it a Color object to set the desired color. Additionally, you can use the elevationOverlay parameter to set the overlay color.
Here's an updated example of your code:
Card(
modifier = Modifier.padding(top = 16. dp)
.background(color = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 2. dp),
elevationOverlay = Color.White
) {}
After trying multiple ways found out that there is no way to override this except for to look at the Card.kt file from SDK and create something similar but disable the tonalColor(Thanks for hint #ianhanniballake that it is using tonalelevation)
Following code should do the work, until overriding is officially supported:
#Composable
fun CardWithoutTonalElevation(
modifier: Modifier = Modifier,
shape: Shape = CardDefaults.shape,
colors: Color = White,
border: BorderStroke? = null,
elevation: Dp = 0.dp,
content: #Composable ColumnScope.() -> Unit = {}
) {
Surface(
modifier = modifier,
shape = shape,
color = colors,
tonalElevation = 0.dp,
shadowElevation = elevation,
border = border,
) {
Column(content = content)
}
}
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
I get the following warning information with Code A, why?
Optional Modifier parameter should have a default value of Modifier
Code A
#Composable
fun DisplayIcon(
modifier: Modifier=Modifier.size(24.dp),
icon: ImageVector,
tint: Color = Color.Blue
) {
Icon(icon, null, modifier = modifier, tint = tint)
}
It's just a best practice. Using some other default value might lead to weird situations - imagine you want to use two of this component in a Column:
Column {
DisplayIcon()
DisplayIcon()
}
Everything's fine, but now you want to align one of them:
Column {
DisplayIcon()
DisplayIcon(modifier = Modifier.align(Alignment.End))
}
And suddenly, with no apparent reason, one has different size than the other. So you have to find out from DisplayIcon implementation what's going on, and then probably add .size(24.dp) to your aligned composable as well. But now you want to change the default size, and you have to do so on many different places...
Something like this might be better solution:
#Composable
fun DisplayIcon(
modifier: Modifier = Modifier,
icon: ImageVector,
tint: Color = Color.Blue,
size: Dp = 24.dp,
) {
Icon(icon, null, modifier = modifier.size(size), tint = tint)
}
Using Jetpack Compose and the accompanist pager, I'm trying to create a HorizontalPager where:
Edges of items to the left and right of current item are shown
There is a max width to the Pager items
As an example, I wrote the following code (for simplicities sake, I made Text items, but in reality, I'm actually making more complex Card items):
#Composable
fun MyText(modifier: Modifier) {
Text(
text = LOREM_IPSUM_TEXT,
modifier = modifier
.wrapContentHeight()
.border(BorderStroke(1.dp, Color.Red))
)
}
#ExperimentalPagerApi
#Composable
fun MyPager(pagerItem: #Composable () -> Unit = {}) {
Scaffold {
Column(
modifier = Modifier
.fillMaxSize()
// In case items in the VP are taller than the screen -> scrollable
.verticalScroll(rememberScrollState())
) {
HorizontalPager(
contentPadding = PaddingValues(32.dp),
itemSpacing = 16.dp,
count = 3,
) {
pagerItem()
}
}
}
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_200dpWidth() {
MyPager { MyText(modifier = Modifier.widthIn(max = 200.dp)) }
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_500dpWidth() {
MyPager { MyText(modifier = Modifier.widthIn(max = 500.dp)) }
}
#ExperimentalPagerApi
#Preview
#Composable
fun MyPager_FillMaxWidth() {
MyPager { MyText(modifier = Modifier.fillMaxWidth()) }
}
The issue I'm having is that when I make the item have a max width that seems to be smaller than the screen width (see MyPager_200dpWidth), I no longer see the items on the side anymore. On the other hand, using items with larger max widths (See MyPager_500dpWidth) or fillMaxWidth (See MyPager_FillMaxWidth) allows me to see the items on the side.
It seems weird to me that items with the smaller max width are actually taking up more horizontal space than the items with the larger max width. This can be shown in the Preview section...
See how the images on the left (MyPager_500dpWidth) and middle (MyPager_FillMaxWidth) show the next item, while the image on the right (MyPager_200dpWidth`) takes up the whole screen? Here's another comparison when hovering my mouse over the items to see the skeleton boxes...
Just wondering if someone could help me figure out how I can solve this use case. Is it possible that there's a bug in Compose?
The page size is controlled by the HorizontalPager.contentPadding parameter.
Applying widthIn(max = 200.dp) to your text only reduces the size of your element inside the page, while the page size remains unchanged.
Applying more padding should solve your problem, for example, contentPadding = PaddingValues(100.dp) looks like this:
You can fix this by adding your contentPadding like this
val horizontalPadding = 16.dp
val itemWidth = 340.dp
val screenWidth = LocalConfiguration.current.screenWidthDp
val contentPadding = PaddingValues(start = horizontalPadding, end = (screenWidth - itemWidth + horizontalPadding).dp)
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