I am trying to overlap two different compose elements. I want to show a toast kind of message at the top whenever there is an error message. I don't want to use a third party lib for such an easy use case. I plan to use the toast in every other composable screen for displaying error message. Below is the layout which i want to achieve
So I want to achieve the toast message saying "Invalid PIN, please try again".
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
and my screen composable is as follows
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box(){
MyToast(
title = "Invalid pin, please try again"
)
Column() {
//my other screen components
}
}
}
I will add the AnimatedVisibility modifier later to MyToast composable. First I need to overlap MyToast over all the other elements and somehow MyToast is just not visible
If you want the child of a Box to overlay/overlap its siblings behind, you should put it at the last part in the code
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
}
So if I put the green box before the bigger red box like this
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
// your Toast
Box(
modifier = Modifier.background(Color.Green).size(80.dp)
)
Box(
modifier = Modifier.background(Color.Red).size(150.dp)
)
}
the green box will hide behind the red one
You have to solutions, you can either put the Toast in the bottom of your code because order matters in compose:
#Composable
fun Registration(navController: NavController, registrationViewModel: RegistrationViewModel) {
Scaffold() {
Box() {
Column() {
//my other screen components
}
MyToast(
title = "Invalid pin, please try again"
)
}
}
}
Or you can keep it as it is, but add zIndex to the Toast:
#Composable
fun MyToast(title: String) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 40.dp)
.zIndex(10f) // add z index here
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
Note: elevation in Card composable is not the same as elevation in XML so it's not going to make the composable in the top, it will just add a shadow but if you want to give the composable a higher z order use Modifier.zIndex(10f)
I am trying to have a splash screen in our application and I got stuck Abit with the jetpack Compose design. So I want to have a background gradient, I am now using the image which does not fit well in the phone and want to center my logo and text in the middle of the screen how can I achieve that. Mostly centering since I have tried the gradient part and I have no success. Here is my code
Here is my background component
#Composable
fun BackgroundComponents(
#DrawableRes backgroundDrawableRes: Int,
contentDescription: String?,
modifier: Modifier = Modifier,
painter: Painter,
alignment: Alignment = Alignment.Center,
) {
Box(
modifier = modifier
) {
Image(
painter = painterResource(id = backgroundDrawableRes),
contentDescription = contentDescription,
modifier = modifier.matchParentSize()
)
Box(
contentAlignment = Alignment.Center
) {
Image(
painter = painter,
contentDescription = contentDescription,
alignment = alignment
)
}
Text(
modifier =
modifier.padding(top = 36.dp),
text = "Hello and welcome to our app",
color = (colorResource(id = R.color.white)),
fontSize = 16.sp,
)
}
}
This is how I am calling it on the Screen
#Composable
fun Splash(modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxSize()) {
BackgroundComponents(
backgroundDrawableRes = R.drawable.ic_launcher_foreground,
contentDescription = "",
modifier = modifier.fillMaxSize(),
painter = painterResource(id = coil.base.R.drawable.notification_bg)
)
}
}
I would like to push the icon and text in the middle and centered, also instead of using an image can I draw that gradient?
You can apply the gradient to the parent Box as background modifier and then just apply the expected alignment to the composable inside the Box.
Something like:
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.horizontalGradient(
colors = listOf(
Color.Blue,
Teal200
)
)
),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Image(
painter = painterResource(id = R.drawable.xx),
contentDescription = "contentDescription",
)
Spacer(Modifier.height(36.dp))
Text(
text = "Hello and welcome to our app",
color = White,
fontSize = 16.sp,
)
}
}
I'm trying to build a custom Drop down menu and I encountered some issues in animating its state. The animation is both laggy and sketchy, even on a real device and even on a release build (APK). The Compose version I'm using is 1.1.1.
Observe the flicker (and the lag?).
The code:
Column(modifier = Modifier.fillMaxWidth()) {
var visible by remember { mutableStateOf(false) }
//header
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { visible = !visible }
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Click me",
style = MaterialTheme.typography.h6
)
Icon(
modifier = Modifier.rotate(animateFloatAsState(if (visible) 180f else 0f).value),
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null
)
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = Color.Black.copy(ContentAlpha.disabled)
)
}
//the 4 items
Column {
(1..4).forEach {
AnimatedVisibility(
visible = visible,
enter = expandVertically(
spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = IntSize.VisibilityThreshold
),
),
exit = shrinkVertically(),
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Hello",
style = MaterialTheme.typography.h6
)
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = null
)
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = Color.Black
)
}
}
}
}
}
If I add some bottom padding to the bigger Column or if I make it occupy the whole screen's height, there's no more flicker, but I feel like that is a workaround and also I'm not sure whether the animation is lagging or not, so this wouldn't be a solution to all my problems. The parent Column wraps around its content and as the content size increases, it tries to "keep up" with the new size, but it doesn't do a perfect job. Am I using AnimatedVisibility improperly? How else could I create a custom Drop down menu?
I want to make an IconButton with text below the icon. I have tried applying those width-related methods in Modifier to all the IconButton, Column, Icon and Text. The code below the is closest I got. The result looks like this. And this is what I want to achieve.
#Composable
fun IconButtonWithTextBelow(
title: String,
#DrawableRes imageId: Int,
onClick: () -> Unit
) {
IconButton(
onClick = onClick,
modifier = Modifier.requiredWidth(IntrinsicSize.Max)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.requiredWidth(IntrinsicSize.Max)
) {
Icon(
painter = painterResource(id = imageId),
contentDescription = title,
)
Text(
text = title,
)
}
}
}
As the name IconButton implies, it is only for displaying Icon. Even if you increase the size of the container with a static size modifier, you will find that the ripple when you click it is still too small.
You can use TextButton, I will work fine with Icon + Text, you just need to specify contentColor: IconButton uses LocalContentColor.current by default, so you can use that too.
TextButton(
onClick = { /*TODO*/ },
colors = ButtonDefaults.textButtonColors(contentColor = LocalContentColor.current)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
) {
Icon(
painter = painterResource(id = imageId),
contentDescription = title,
)
Text(
text = title,
)
}
}
But the implementation of TextButton may change in the future, the name says only about Text after all.
So a better option would be to just use the clickable modifier on your Column, and maybe padding. The result will be pretty much the same.
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(10.dp)
.clickable(
onClick = onClick,
role = Role.Button,
)
) {
Icon(
painter = painterResource(id = imageId),
contentDescription = title,
)
Text(
text = title,
)
}
IconButton's size restricted with 48.dp so you need to implement your own IconButton. You can check source code of IconButton.
TopAppBar(
backgroundColor = Color.Transparent,
elevation = 0.dp,
modifier= Modifier.fillMaxWidth(),
navigationIcon = {
IconButton(
onClick = { TODO },
enabled = true,
) {
Icon(
painter = painterResource(id = R.drawable.icon_back_arrow),
contentDescription = "Back",
)
}
}
},
title = {
Text(
modifier = if (action == null) Modifier.fillMaxWidth() else Modifier,
textAlign = if (action == null) TextAlign.Center else TextAlign.Start,
maxLines = 1,
text = "Hello"
)
},
actions = {
action?.run {
Text(
modifier = Modifier
.padding(horizontal = 16.dp)
.clickable(onClick = TODO),
color = Color.Green,
text ="Cancel",
)
}
}
I'm new in jetpack and want to align title of TopAppBar at center if action is null. Title is not align center of layout. when there is no navigationIcon it work but adding navigationIcon it show slightly right. How can I do it to make title text at center of layout.
With Material2 you have to use the other constructor of TopAppBar that has no pre-defined slots for content, allowing you to customize the layout of content inside.
You can do something like:
val appBarHorizontalPadding = 4.dp
val titleIconModifier = Modifier.fillMaxHeight()
.width(72.dp - appBarHorizontalPadding)
TopAppBar(
backgroundColor = Color.Transparent,
elevation = 0.dp,
modifier= Modifier.fillMaxWidth()) {
//TopAppBar Content
Box(Modifier.height(32.dp)) {
//Navigation Icon
Row(titleIconModifier, verticalAlignment = Alignment.CenterVertically) {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
) {
IconButton(
onClick = { },
enabled = true,
) {
Icon(
painter = painterResource(id = R.drawable.ic_add_24px),
contentDescription = "Back",
)
}
}
}
//Title
Row(Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically) {
ProvideTextStyle(value = MaterialTheme.typography.h6) {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
){
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
maxLines = 1,
text = "Hello"
)
}
}
}
//actions
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Row(
Modifier.fillMaxHeight(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
content = actions
)
}
}
}
With Material3 you can simply use the CenterAlignedTopAppBar:
CenterAlignedTopAppBar(
title = { Text("Centered TopAppBar") },
navigationIcon = {
IconButton(onClick = { /* doSomething() */ }) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Localized description"
)
}
}
)
If you're using Material3, you can use the CenterAlignedTopAppBar as well.
fun CenterAlignedTopAppBar(
title: #Composable () -> Unit,
modifier: Modifier = Modifier,
navigationIcon: #Composable () -> Unit = {},
actions: #Composable RowScope.() -> Unit = {},
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
scrollBehavior: TopAppBarScrollBehavior? = null
) {
SingleRowTopAppBar(
modifier = modifier,
title = title,
titleTextStyle =
MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
centeredTitle = true,
navigationIcon = navigationIcon,
actions = actions,
colors = colors,
scrollBehavior = scrollBehavior
)
}
EDIT: the old answer is out of date, please use CenterAlignedTopAppBar
CenterAlignedTopAppBar(
title = { Text(text = stringResource(id = titleRes)) },
actions = {
IconButton(onClick = onActionClick) {
Icon(
imageVector = actionIcon,
contentDescription = actionIconContentDescription,
tint = MaterialTheme.colorScheme.onSurface
)
}
},
colors = colors,
modifier = modifier,
)
OLD ANSWER:
I redid the native implementation a bit.
Need to do just two things:
1.Add this file to your project. This is a slightly modified implementation of the TopAppBar class.
https://gist.github.com/evansgelist/aadcd633e9b160f9f634c16e99ffe163
2.Replace in your code TopAppBar to CenterTopAppBar. And it's all!
Scaffold(
topBar = {
CenterTopAppBar(
title = {
Text(
textAlign = TextAlign.Center,
text = text,
)
},
EDIT
extension's code
val Number.toPx
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
)
Result
The core of the title centering is that the space of the left and right occupying slots is the same. You only need to adjust the default size of the slots. We give the left and right slots the default space occupying slots, which can solve this problem well, and the code is simple.
#Composable
fun TopBar(title: Int, actions: #Composable (() -> Unit)? = null, popOnClick: () -> Unit) {
val modifier = Modifier.size(width = 70.dp, height = 50.dp).background(Color.Red)
TopAppBar(
title = {
Text(text = stringResource(id = title), fontSize = 16.sp,
textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()) },
navigationIcon = {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
IconButton(onClick = { popOnClick() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "", tint = MaterialTheme.colors.primary)
}
}
},
actions = {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
if (actions != null) {
actions()
}
}
},
backgroundColor = MaterialTheme.colors.surface,
elevation = 1.dp
)
}
It depends what's in the appbar.
If you only have the title, then you could do the following:
topBar = {
TopAppBar(content = {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Title Text",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6,
)
})
},
If you have an icon either side you should be able to do the same, may have to adjust things if have two icons one side and one the other, then may want to use add a same sized icon to even things out and put enabled to false to it's not clickable and color to transparent so it's not seen, otherwise you could try and figure out the size and add padding end to the text, it seems you also need to add 16.dp padding to the transparent icon, as the navigation icon is given extra padding before the title but not between title and actions
Here's what I did for the arrowIcon and title
topBar = {
TopAppBar(
navigationIcon = {
IconButton(
onClick = {
navController.popBackStack()
}
) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "back arrow icon"
)
}
},
title = {
Text(
modifier = Modifier
.fillMaxWidth(),
text = "Basic Navigation",
textAlign = TextAlign.Center,
)
},
actions = {
IconButton(
modifier = Modifier
.padding(start = 16.dp),
enabled = false,
onClick = {}
) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "back arrow icon",
tint = Color.Transparent
)
}
}
)
}
I'm just a begginer with Jetpack Compose and before I searched for a solution of that problem I tried to figure my own, maybe it will be enough for someone. I needed centered title for TopAppBar with navigation icon on the left side only or with two icons on the left and right side, this solution was OK for me. Later I configurated that for including passed icon on the right side or no.
TopAppBar(
backgroundColor = Color.Green,
elevation = 5.dp,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// size of an icon and placeholder box on the right side
val iconSize = 32.dp
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Arrow Back",
modifier = Modifier
.clickable {
navController.popBackStack()
}
.size(iconSize)
)
Text(text = "Centered Title", fontSize = 32.sp)
// placeholder on the right side in order to have centered title - might be replaced with icon
Box(
modifier = Modifier
.size(iconSize)
) { }
}
I can't attach screenshot, preview is here: https://i.stack.imgur.com/UNQTF.png
I found a good workaround if you're using Material 2.
Create a Row, that contains:
Column with weight 1, containing an Icon
Column with weight 1, containing Header
Spacer with weight 1
The device's screen will automatically divide into 3 equal sections.
Here is a code sample:
Scaffold(
topBar = {
TopAppBar(
backgroundColor = Transparent,
elevation = 0.dp,
modifier = Modifier.fillMaxWidth()
) {
/** TopAppBar Content */
Row(
modifier = Modifier.height(51.dp).fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
/** Icon*/
Column(modifier = Modifier.weight(1f).padding(start = 9.dp)) {
IconButton(
onClick = { /*TODO*/ },
modifier = Modifier
.width(22.dp)
.height(22.dp),
enabled = true,
) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_arrow_back),
contentDescription = "Back",
tint = ThemeBlue
)
}
}
/** Header */
Column(modifier = Modifier.weight(1f)) {
Text(
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.h1,
textAlign = TextAlign.Center,
maxLines = 1,
text = "Header",
)
}
/*TODO
* When the stable version of Material 3 comes out
* we should replace the whole TopAppBar with CenterAlignedTopAppBar.
* For now, this Spacer keeps the Header component in the center of the topBar
*/
Spacer(modifier = Modifier.weight(1f))
}
}
},
content = {
Divider(color = ThemeBlue, thickness = 0.5.dp)
}
)
Here is a screenshot of the result this code produces:
Use fillMaxWidth() to let the Text View occupy AppBar width
Use wrapContentWidth(align = Alignment.CenterHorizontally) to align the Text Title.
TopAppBar(
title = {
Text(
text = screenname,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(align= Alignment.CenterHorizontally)
)
},
modifier = modifier
)
title = {
Row(
modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Text(
stringResource(R.string.app_title).uppercase(),
style = MaterialTheme.typography.h1
)
Spacer(modifier.width(1.dp))
}
},
#Composable
fun TopAppBarCompose(){
TopAppBar(
title = {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Hello",
fontSize = 30.sp,
modifier = Modifier.align(Alignment.Center)
)
}
},
)
}
The previous solutions are too complex. It is actually quite simple:
title = {
Text(
text = "title",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}