Gradient over image in Jetpack Compose - android

Here is my component:
#Composable
fun Cover(
name: String,
imageRes: Int,
modifier: Modifier = Modifier.padding(16.dp, 8.dp)
) {
Box(modifier) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Stack {
Image(
imageResource(imageRes),
modifier = Modifier
.gravity(Alignment.Center)
.aspectRatio(2f),
contentScale = ContentScale.Crop,
)
Text(
text = name,
modifier = Modifier
.gravity(Alignment.BottomStart)
.padding(8.dp),
style = MaterialTheme.typography.h6
)
}
}
}
}
This is how it looks:
I want to display a dark gradient over the Image and behind the Text so that the text is easy to read. I guess I'll have to use LinearGradient or RadialGradient but due to the lack of documentation I'm not able to do it.
Edit: This is what I'm trying to do but with Jetpack Compose.

You can use something like:
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black),
startY = sizeImage.height.toFloat()/3, // 1/3
endY = sizeImage.height.toFloat()
)
Box(){
Image(painter = painterResource(id = R.drawable.banner),
contentDescription = "",
modifier = Modifier.onGloballyPositioned {
sizeImage = it.size
})
Box(modifier = Modifier.matchParentSize().background(gradient))
}
Original:
After:
You can also apply the gradient to the Image() using the .drawWithCache modifier and the onDrawWithContent that allows the developer to draw before or after the layout's contents.
Image(painter = painterResource(id = R.drawable.conero),
contentDescription = "",
modifier = Modifier.drawWithCache {
val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black),
startY = size.height/3,
endY = size.height
)
onDrawWithContent {
drawContent()
drawRect(gradient,blendMode = BlendMode.Multiply)
}
}
)

Wow, that one took a couple of hours ;)
You can use Modifier.background with a VerticalGradient. I used a Column to hold the modifiers and made a calculation to get the images size, but your solution might differ, you could calculate or store the size differently, and put the modifiers somewhere else. I left two TODOs in the code so you can tweak the gradient.
#Composable
fun Cover(
name: String,
imageRes: Int,
modifier: Modifier = Modifier.padding(16.dp, 8.dp)
) {
val density = DensityAmbient.current.density
val width = remember { mutableStateOf(0f) }
val height = remember { mutableStateOf(0f) }
Box(modifier) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Stack {
Image(
imageResource(imageRes),
modifier = Modifier
.gravity(Alignment.Center)
.aspectRatio(2f)
.onPositioned {
width.value = it.size.width / density
height.value = it.size.height / density
},
contentScale = ContentScale.Crop,
)
Column(
Modifier.size(width.value.dp, height.value.dp)
.background(
VerticalGradient(
listOf(Color.Transparent, Color.Black),
0f, // TODO: set start
500f, // TODO: set end
)
)
) {}
Text(
text = name,
modifier = Modifier.gravity(Alignment.BottomStart)
.padding(8.dp),
style = typography.h6,
)
}
}
}
}
This is how my sample looks like:

Compose version 1.2.1 as of 2022-08-22
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
#Composable
fun Cover(
name: String,
#DrawableRes imageRes: Int,
modifier: Modifier = Modifier
) {
Box(modifier.padding(16.dp, 8.dp)) {
Card(shape = RoundedCornerShape(4.dp)) {
Box {
Image(
painter = painterResource(imageRes),
contentDescription = "image: $name",
modifier = Modifier
.align(Alignment.Center),
contentScale = ContentScale.Crop
)
Text(
text = name,
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.background(
Brush.verticalGradient(
0F to Color.Transparent,
.5F to Color.Black.copy(alpha = 0.5F),
1F to Color.Black.copy(alpha = 0.8F)
)
)
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp, top = 24.dp),
color = Color.White
)
}
}
}
}
#Preview
#Composable
fun ComicsPreview() {
Cover(
"Comics",
R.drawable.comics
)
}
The updated answer of #vitor-ramos
1.0.0-alpha09
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.dp
import tech.abd3lraouf.learn.compose.kombose.ui.theme.typography
#Composable
fun Cover(
name: String,
#DrawableRes imageRes: Int,
modifier: Modifier = Modifier
) {
val image = imageResource(imageRes)
val density = AmbientDensity.current.density
val width = remember { mutableStateOf(0f) }
val height = remember { mutableStateOf(0f) }
Box(
modifier
.padding(16.dp, 8.dp)
) {
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Box {
Image(
image,
modifier = Modifier
.align(Alignment.Center)
.aspectRatio(2f)
.onGloballyPositioned {
width.value = it.size.width / density
height.value = it.size.height / density
},
contentScale = ContentScale.Crop,
)
Column(
Modifier
.size(width.value.dp, height.value.dp)
.background(
Brush.verticalGradient(
listOf(Color.Transparent, Color.Black),
image.height * 0.6F,
image.height * 1F
)
)
) {}
Text(
text = name,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(8.dp),
style = typography.body2,
color = Color.White
)
}
}
}
}
Also, notice the control of how the gradient draws its height.
The output

You can try this approach as well
Image(
painterResource(R.drawable.something),
null,
Modifier
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(Brush.verticalGradient(
0.5f to Color.White.copy(alpha=0F),
1F to Color.White
))
}
},
)

Straight forward:
Card(shape = RoundedCornerShape(8.dp)) {
Box {
Image(...)
Text(
text = "title",
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.background(Brush.verticalGradient(0F to Color.Transparent, .5F to Color.Red, 1F to Color.Red))
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp, top = 16.dp),
color = Color.White,
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Start
)
}
}

Related

Fix Text position in center of Jetpack Compose Button

I'm currently working on a button, which has 3 elements: An icon (with a fixed size), a title (f.e Buy Now!) and the price of the item.
The price which should be displayed is adaptive, this could be €2,00 or €2000,00. The title is supposed to be centered, based on the Button itself, rather than the area it can occupy.
The price of object has the priority within the button, and should always be fully displayed with a set style. Due to this, the size of this object is variable, and can not be determined beforehand.
When the length of the price object increases, naturally the available space of the title decreases. However, when attempting to center the text, I could only get it to center based on the available space, which resulted in the text being off-center.
How could one approach this issue, allowing for the text to be centered based on the parent (button), rather than the available text size?
I tried to prepare an understandable example for you, if it was useful, please select my answer as the correct answer
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintSet
import androidx.constraintlayout.compose.Dimension
import stackoverflow.answers.ui.theme.StackOverflowAnswersTheme
#Composable
private fun StandardToolbar(
onProfileButtonClick: () -> Unit,
onFilterButtonClick: () -> Unit,
onBackButtonClick: () -> Unit
) {
val constraintSet = ConstraintSet {
val startReference = createRefFor("startReference")
val endReference = createRefFor("endReference")
val titleReference = createRefFor("titleReference")
constrain(startReference) {
start.linkTo(parent.start, 16.dp)
top.linkTo(parent.top, 16.dp)
bottom.linkTo(parent.bottom, 16.dp)
width = Dimension.value(48.dp)
}
constrain(endReference) {
end.linkTo(parent.end, 16.dp)
top.linkTo(parent.top, 16.dp)
bottom.linkTo(parent.bottom, 16.dp)
width = Dimension.value(48.dp)
}
constrain(titleReference) {
start.linkTo(startReference.end, 8.dp)
end.linkTo(endReference.start, 8.dp)
top.linkTo(parent.top, 16.dp)
bottom.linkTo(parent.bottom, 16.dp)
width = Dimension.fillToConstraints
}
}
Surface(
elevation = AppBarDefaults.TopAppBarElevation,
shape = RoundedCornerShape(
bottomStart = 50f,
bottomEnd = 50f
),
color = Color(0XFF2F364E),
modifier = Modifier
.fillMaxWidth()
.height(72.dp)
) {
ConstraintLayout(
modifier = Modifier.fillMaxSize(),
constraintSet = constraintSet
) {
Box(
modifier = Modifier
.layoutId("startReference")
.size(48.dp)
.background(Color.Blue)
) {
}
Text(
modifier = Modifier
.layoutId("titleReference"),
text = "Title",
style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
color = Color.White,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
maxLines = 1
)
Box(
modifier = Modifier
.layoutId("endReference")
.size(48.dp)
.background(Color.Green)
) {
Text(text = "E 20,000", modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.caption)
}
}
}
}
#Composable
#Preview
fun StandardToolbarPreview() {
CompositionLocalProvider(
LocalLayoutDirection provides LayoutDirection.Ltr
) {
StackOverflowAnswersTheme {
StandardToolbar(
onProfileButtonClick = { },
onFilterButtonClick = { },
onBackButtonClick = {}
)
}
}
}
You can try this:
Button(
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 8.dp),
onClick = {}
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
) {
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.TopStart
) {
Icon(
imageVector = Icons.Default.ImageSearch,
contentDescription = null
)
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center
) {
Text(
text = "Buy Now"
)
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.TopEnd
) {
Text(
text = "€ 2.00"
// text = "€ 2000.00"
)
}
}
}
The Button has a content parameter you can use to set its content, in this case we use a Row to set contents in the horizontal axis.
Note that each of the components, Icon Text and Text are wrapped inside a Box with a weight of 1f, making each those boxes as their container that also takes equal divided space of the parent Row.
The middle Box positions its child in the center, while the first and last Box positions their children (i.e Icon and Text) in TopStart and TopEnd alignment, though you don't need to worry the "top" positioning as its neglected here because the parent Row aligns all its children center-vertically
If we put a background color on each Box,
Modifier.background(Color.LightGray/Gray/DarkGray)
we can clearly see their equal width

Android Jetpack - Fit Image as Custom Style in Card

I have a screen that includes only a LazyRow horizontal list includes 2 Cards, so you can slide cards. You can see my code below:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.jetpackdeneme.R
#Composable
fun TestScreen1() {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
LazyRow(
Modifier.height(160.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
item {
PromotionItem1(
imagePainter = painterResource(id = R.drawable.statue_of_liberty),
title = "AMERICA",
header = "USA",
backgroundColor = Color.White
)
}
item {
PromotionItem1(
imagePainter = painterResource(id = R.drawable.statue_of_liberty),
title = "AMERICA",
header = "USA",
backgroundColor = Color.White
)
}
}
}
}
}
}
#Composable
fun PromotionItem1(
title: String = "",
header: String = "",
backgroundColor: Color = Color.Transparent,
imagePainter: Painter
) {
Card(
Modifier.width(300.dp),
shape = RoundedCornerShape(8.dp),
backgroundColor = backgroundColor,
elevation = 0.dp
) {
Row {
Image(
painter = imagePainter, contentDescription = "",
modifier = Modifier
.fillMaxHeight()
.weight(1f),
alignment = Alignment.CenterEnd,
contentScale = ContentScale.Crop
)
Column(
Modifier
.padding(horizontal = 16.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center
) {
Text(text = title, fontSize = 14.sp, color = Color.Black, fontWeight = FontWeight.Bold)
Text(text = header, fontSize = 14.sp, color = Color.Black)
}
}
}
}
The output is like this (it is not fitting, and I want a custom style):
I wanted to set the image's bottom edge to bottom of card, and overflow the top a little bit like that output:
You can get the image here
How can I do that? Can you help me please?
First, replace Card with Box which doesn't clip layout that prevents overflow.
Second, set a graphicsLayer to increase scale of Image but by default tranform origin is center since we want to push image upwards we need to set bottom left as transform origin which is TransformOrigin(0f,1f).
Third, increase scale as you wish and have expected output.
#Composable
fun PromotionItem1(
title: String = "",
header: String = "",
backgroundColor: Color = Color.Transparent,
imagePainter: Painter
) {
Box(
Modifier
.width(300.dp)
.background(backgroundColor, RoundedCornerShape(8.dp)),
) {
Row {
Image(
painter = imagePainter,
contentDescription = "",
modifier = Modifier
.graphicsLayer {
scaleY = 1.2f
scaleX = 1.2f
this.transformOrigin = TransformOrigin(0f, 1f)
}
.fillMaxHeight(),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.weight(1f))
Column(
Modifier
.padding(horizontal = 16.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center
) {
Text(
text = title,
fontSize = 14.sp,
color = Color.Black,
fontWeight = FontWeight.Bold
)
Text(text = header, fontSize = 14.sp, color = Color.Black)
}
}
}
}
Also i increased LazyRow height to Modifier.height(200.dp)
Result

Why are all my composables being recomposed?

I am creating a screen with a color picker. Here's the code for the whole screen, including the color picker code. The reason why I'm including the whole screen's code instead of just the color picker's is because I want to give more context.
package com.gitlab.djsushi123.kingenscoreboard.presentation.screen.newplayer
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.gitlab.djsushi123.kingenscoreboard.domain.model.Player
import com.google.accompanist.systemuicontroller.SystemUiController
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
#Composable
fun NewPlayerScreen(
viewModel: NewPlayerScreenViewModel = getViewModel(),
appNavController: NavHostController,
systemUiController: SystemUiController,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
val state = viewModel.state
val animatedSelectedColor by animateColorAsState(
targetValue = state.playerColor,
animationSpec = tween(durationMillis = 600)
)
val primaryVariantColor = MaterialTheme.colors.primaryVariant
val darkIconsDisposable = MaterialTheme.colors.isLight
val useDarkIcons = with(animatedSelectedColor) {
(red + green + blue) / 3f > 0.5f
}
DisposableEffect(key1 = animatedSelectedColor) {
systemUiController.setStatusBarColor(
color = animatedSelectedColor,
darkIcons = useDarkIcons
)
onDispose {
systemUiController.setStatusBarColor(
color = primaryVariantColor,
darkIcons = darkIconsDisposable
)
}
}
Scaffold(
topBar = {
TopBar(
color = animatedSelectedColor,
onColor = if (useDarkIcons) Color.Black else Color.White,
saveButtonEnabled = state.saveButtonEnabled,
onBackButtonClicked = { appNavController.popBackStack() },
onSave = {
val player = Player(
name = state.playerName,
abbreviation = state.playerAbbreviation,
color = state.playerColor
)
coroutineScope.launch {
viewModel.savePlayer(player)
appNavController.popBackStack()
}
}
)
},
modifier = modifier
) { paddingValues ->
Surface(color = MaterialTheme.colors.surface, modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()) // so that we can scroll with the keyboard open
.padding(paddingValues)
.padding(start = 8.dp, top = 8.dp, end = 8.dp)
) {
// Player name field with error
OutlinedTextField(
value = state.playerName,
onValueChange = {
viewModel.onEvent(
NewPlayerFormEvent.PlayerNameChanged(name = it)
)
},
isError = state.playerNameError != null,
label = { Text(text = "Name") },
placeholder = { Text(text = "John Cena") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words
),
modifier = Modifier.fillMaxWidth()
)
Text(
text = state.playerNameError ?: "",
color = MaterialTheme.colors.error,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
// Player abbreviation field
OutlinedTextField(
value = state.playerAbbreviation,
onValueChange = {
viewModel.onEvent(
NewPlayerFormEvent.PlayerAbbreviationChanged(abbreviation = it)
)
},
isError = state.playerAbbreviationError != null,
label = { Text(text = "Abbreviation") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
autoCorrect = false
),
modifier = Modifier.fillMaxWidth()
)
Text(
text = state.playerAbbreviationError ?: "",
color = MaterialTheme.colors.error,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
ColorPicker(
selected = state.playerColor,
onSelect = { color ->
viewModel.onEvent(
NewPlayerFormEvent.PlayerColorChanged(color = color)
)
},
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
#Composable
private fun TopBar(
color: Color,
onColor: Color,
saveButtonEnabled: Boolean,
onBackButtonClicked: () -> Unit,
onSave: () -> Unit
) {
val saveButtonAlpha by animateFloatAsState(
if (saveButtonEnabled) ContentAlpha.high else ContentAlpha.disabled
)
TopAppBar(
title = { Text(text = "New player", color = onColor) },
navigationIcon = {
IconButton(onClick = onBackButtonClicked) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
tint = onColor
)
}
},
actions = {
IconButton(onClick = onSave, enabled = saveButtonEnabled) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Save",
tint = onColor.copy(alpha = saveButtonAlpha)
)
}
},
backgroundColor = color
)
}
#Composable
private fun ColorPicker(
selected: Color,
onSelect: (color: Color) -> Unit,
modifier: Modifier = Modifier
) {
val colors: List<Color> = remember {
(0..340 step 8).map { hue ->
Color.hsl(hue = hue.toFloat(), saturation = 0.5f, lightness = 0.5f)
}.dropLast(3)
}
Column(modifier = modifier) {
for (row in 0..(colors.size - 1) / 4) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
for (tile in 0 until 4) {
val color = colors[row * 4 + tile]
ColorPickerTile(
color = color,
selected = color == selected,
onClick = { onSelect(color) },
modifier = Modifier
.width(70.dp)
.height(70.dp)
)
}
}
}
}
}
#Composable
private fun ColorPickerTile(
color: Color,
selected: Boolean,
onClick: () -> Unit,
shape: Shape = MaterialTheme.shapes.small,
modifier: Modifier = Modifier
) {
val scale = remember { Animatable(initialValue = 1f) }
val borderWidth by animateDpAsState(
targetValue = if (selected) 6.dp else 0.dp,
animationSpec = tween(durationMillis = 350)
)
LaunchedEffect(key1 = selected) {
if (selected) {
launch {
scale.animateTo(
targetValue = 0.90f,
animationSpec = tween(
durationMillis = 50
)
)
scale.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
}
} else {
launch { scale.animateTo(1f) }
}
}
Box(
modifier = modifier
.scale(scale.value)
.border(
width = borderWidth,
color = if (borderWidth.value == 0f) Color.Transparent else MaterialTheme.colors.onSurface,
shape = shape
)
.clip(shape)
.background(color = color)
.clickable(onClick = onClick)
)
}
My color picker is composed of ColorPickerTiles, which are stacked in a grid that is 4 tiles wide. This grid is a made-up grid from Rows inside a big Column. My problem is related to performance. When we check the "layout inspector" tab in Android Studio, we can see that when the user selects a different color tile in the app, every single ColorPickerTile gets recomposed MULTIPLE TIMES because of an animation I've added. I don't understand why this is necessary and why is this even possible, since the ColorPickerTile composable is restartable and also skippable and none of its parameters changed AFAIK. Here's a screenshot of what I mean:
The fact that these recompositions happen for every single ColorPickerTile many times during an animation means that the whole animation lags, especially on low-end devices. And the lag increases exponentially, with more ColorPickerTiles. My question is, what is causing these useless recompositions? And how would I go about solving the issue?

How can I use lazy column and lazyverticalgrid in jetpack compose?

I wanted a layout similar to
`<Scrollview>
<Relativelayout>
<Recyclerview/>(Horizontal)
<Recyclerview/>(Vertical)
</Relativelayout>
</Scrollview>`
this is my compose code related to the issue
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.composeappdemo.R
import com.example.composeappdemo.model.Feature
import com.example.composeappdemo.ui.theme.*
#ExperimentalFoundationApi
#Composable
fun HomeScreen(list: List<String>) {
var features = listOf(
Feature(
title = "Summer Times",
R.drawable.ic__search,
BlueViolet1,
BlueViolet1,
BlueViolet1
),
Feature(
title = "Winter Vibes",
R.drawable.ic__search,
LightGreen1,
BlueViolet1,
BlueViolet1
),
Feature(
title = "Spring Times",
R.drawable.ic__search,
LightRed,
BlueViolet1,
BlueViolet1
)
)
Box(
modifier = Modifier
.fillMaxWidth()
.background(DeepBlue)
) {
LazyColumn() {
item {
GreetingScreen("Android")
}
item {
ChipSection(list)
}
item {
CurrentMeditation()
}
item {
Text(
text = "Features",
color = Color.White,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(15.dp)
)
}
items(features.windowed(2, 2, true)) { list ->
Row(Modifier.fillMaxWidth()) {
list.forEach {
FeatureItem(it)
}
}
}
}
}
}
#Composable
fun GreetingScreen(name: String = "Android") {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Column(
verticalArrangement = Arrangement.Center
) {
Text(
text = "Good Morning , $name",
style = MaterialTheme.typography.h6,
color = Color.White
)
Text(
text = "We wish you a good day today",
style = MaterialTheme.typography.body1,
color = Color.White
)
}
Icon(
painter = painterResource(id = R.drawable.ic__search),
contentDescription = "Search",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
}
#Composable
fun ChipSection(
chips: List<String>
) {
var selectedChipIndex by remember {
mutableStateOf(0)
}
LazyRow {
items(chips.size) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(start = 15.dp, top = 15.dp, bottom = 15.dp)
.clickable {
selectedChipIndex = it
}
.clip(RoundedCornerShape(10.dp))
.background(
if (selectedChipIndex == it) ButtonBlue
else DarkerButtonBlue
)
.padding(15.dp)
) {
Text(text = chips[it], color = Color.White)
}
}
}
}
#Composable
fun CurrentMeditation(
color: Color = LightRed
) {
Box(
modifier = Modifier
.padding(15.dp)
.clip(RoundedCornerShape(15.dp))
.background(color)
.fillMaxWidth()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Column(verticalArrangement = Arrangement.Center) {
Text(
text = "Daily Thought",
color = TextWhite,
fontSize = 18.sp
)
Text(
text = "Meditation 3 - 10 mins",
color = TextWhite,
fontSize = 13.sp
)
}
Icon(
painter = painterResource(id = R.drawable.ic__play),
contentDescription = "Play",
tint = Color.White
)
}
}
}
#ExperimentalFoundationApi
#Composable
fun FeatureSection(features: List<Feature>) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Text(
text = "Features",
color = Color.White,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(15.dp)
)
}
items(features.windowed(2, 2, true)) { list ->
Row(Modifier.fillMaxWidth()) {
list.forEach {
FeatureItem(feature = it)
}
}
}
/*LazyVerticalGrid(
cells = GridCells.Fixed(2),
contentPadding = PaddingValues(start = 5.dp, top = 5.dp, bottom = 5.dp),
modifier = Modifier.fillMaxHeight()
) {
items(features.size) {
}
}*/
}
}
#Composable
fun FeatureItem(feature: Feature) {
Box(
modifier = Modifier
.padding(15.dp)
.fillMaxWidth(.3f)
.clip(RoundedCornerShape(15.dp))
.aspectRatio(1f)
.background(color = feature.lightColor)
) {
Text(
text = feature.title,
color = Color.White,
style = MaterialTheme.typography.h6,
lineHeight = 24.sp,
modifier = Modifier.align(Alignment.TopStart)
)
Icon(
painter = painterResource(id = feature.iconId),
contentDescription = feature.title,
tint = Color.White,
modifier = Modifier.align(Alignment.BottomStart)
)
Text(
text = "Start",
color = TextWhite,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.clickable {
}
.align(
Alignment.BottomEnd
)
.clip(RoundedCornerShape(10.dp))
.background(ButtonBlue)
.padding(vertical = 6.dp, horizontal = 15.dp)
)
}
}
When I try to achieve it in jetpack compose. The list scrolls under it's own layout but doesn't expand making the whole scrolling. Horizontal one is fine but the vertical one tends to have a small height not making it the sameheight as the list itself.
Needed a view something like this so that the view can expand as well as scroll with the layout.
#Composable
fun NewsList() {
LazyColumn {
items(rows) { item ->
Text(
modifier = Modifier
.height(80.dp),
text = item
)
}
}
something like this you can create fr lazycolum
for gride you can try this(Not sure for this) is it right or not
LazyVerticalGrid(
cells = GridCells.Fixed(cellState),
content = {
}
}
)
Don't forget to import this library explicitly
import androidx.compose.foundation.lazy.items
#Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}

Jetpack Compose Fullscreen Dialog

I tried to make a fullscreen dialog using Jetpack Compose using this code:
Dialog(onDismissRequest = { /*TODO*/ }) {
NewPostDialog()
}
It ended up looking something like this. How can I remove the margin at the side (marked red)?
UPDATE: As #Nestor Perez mentioned, since compose 1.0.0-rc01 you can set usePlatformDefaultWidthin DialogProperties to make a dialog fill the whole screenwidth:
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest...
){
Surface(modifier = Modifier.fillMaxSize()) {
DialogContent()
}
}
Compose Dialog uses ContextThemeWrapper so you should be able to theme your dialog with a custom style.
themes.xml:
<style name="Theme.YourApp" parent="Theme.MaterialComponents.Light.NoActionBar">
//theme content...
<item name="android:dialogTheme">#style/Theme.DialogFullScreen</item>
</style>
<style name="Theme.DialogFullScreen" parent="#style/ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="android:windowMinWidthMajor">100%</item>
<item name="android:windowMinWidthMinor">100%</item>
</style>
And in code:
#Composable
fun FullScreenDialog(showDialog:Boolean, onClose:()->Unit) {
if (showDialog) {
Dialog(onDismissRequest = onClose ) {
Surface(
modifier = Modifier.fillMaxSize(),
shape = RoundedCornerShape(16.dp),
color = Color.LightGray
) {
Box(
contentAlignment = Alignment.Center
) {
Text(modifier = Modifier.align(Alignment.TopCenter),
text = "top")
Text("center")
Text(
modifier = Modifier.align(Alignment.BottomCenter),
text = "bottom")
}
}
}
}
}
Solution from jns didnt work too well form me, I leave another solution here if anyone is still looking:
Implement the theme as jns answer:
<style name="Theme.Outlay" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
<!-- Customize your theme here. -->
<item name="android:dialogTheme">#style/Theme.DialogFullScreen</item >
</style>
<style name="Theme.DialogFullScreen" parent="#style/ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="android:windowMinWidthMajor">100%</item>
<item name="android:windowMinWidthMinor">100%</item>
</style>
For the dialog create an scaffold and add the experimental property "usePlatformDefaultWidth = false" on dialog properties:
Dialog(
onDismissRequest = onBackPressed,
properties = DialogProperties(
usePlatformDefaultWidth = false
)
) {
Scaffold(topBar = { TopBar(onBackPressed = onBackPressed) }) {
Content()
}
}
If you want to avoid having an xml theme entirely and also avoid doing this for all dialogs, you can set a requiredWidth modifier to be equal to LocalConfiguration.current.screenWidthDp.dp (multiplied by some fraction as you please).
An example that takes up 0.96f of the screen width:
#Composable
fun LargerDialog(
dialogOpen: MutableState<Boolean>
) {
Dialog(onDismissRequest = { dialogOpen.value = false }) {
Card( // or Surface
elevation = 8.dp,
modifier = Modifier
.requiredWidth(LocalConfiguration.current.screenWidthDp.dp * 0.96f)
.padding(4.dp)
) {
// content
}
}
}
To make a full-screen dialog using Jetpack Compose using this code:
EG1:
Full screen dialog screenshot
package compose.material.theme
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import compose.material.theme.ui.theme.Material3ComposeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Material3ComposeTheme {
val openFullDialogCustom = remember { mutableStateOf(false) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.padding(20.dp)
.verticalScroll(rememberScrollState())
) {
//...................................................................
// * full screen custom dialog
Button(
onClick = {
openFullDialogCustom.value = true
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "No internet",style = MaterialTheme.typography.labelLarge)
}
}
}
//...............................................................................
//Full screen Custom Dialog Sample
NoInternetScreen(openFullDialogCustom)
}
}
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
private fun NoInternetScreen(openFullDialogCustom: MutableState<Boolean>) {
if (openFullDialogCustom.value) {
Dialog(
onDismissRequest = {
openFullDialogCustom.value = false
},
properties = DialogProperties(
usePlatformDefaultWidth = false // experimental
)
) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.no_intrenet),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(20.dp))
//.........................Text: title
Text(
text = "Whoops!!",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 20.dp)
.fillMaxWidth(),
letterSpacing = 2.sp,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.height(8.dp))
//.........................Text : description
Text(
text = "No Internet connection was found. Check your connection or try again.",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, start = 25.dp, end = 25.dp)
.fillMaxWidth(),
letterSpacing = 1.sp,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
)
//.........................Spacer
Spacer(modifier = Modifier.height(24.dp))
val cornerRadius = 16.dp
val gradientColor = listOf(Color(0xFFff669f), Color(0xFFff8961))
GradientButton(
gradientColors = gradientColor,
cornerRadius = cornerRadius,
nameButton = "Try again",
roundedCornerShape = RoundedCornerShape(topStart = 30.dp,bottomEnd = 30.dp)
)
}
}
}
}
}
}
//...........................................................................
#Composable
fun GradientButton(
gradientColors: List<Color>,
cornerRadius: Dp,
nameButton: String,
roundedCornerShape: RoundedCornerShape
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 32.dp, end = 32.dp),
onClick = {
//your code
},
contentPadding = PaddingValues(),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
shape = RoundedCornerShape(cornerRadius)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
brush = Brush.horizontalGradient(colors = gradientColors),
shape = roundedCornerShape
)
.clip(roundedCornerShape)
/*.background(
brush = Brush.linearGradient(colors = gradientColors),
shape = RoundedCornerShape(cornerRadius)
)*/
.padding(horizontal = 16.dp, vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Text(
text = nameButton,
fontSize = 20.sp,
color = Color.White
)
}
}
}

Categories

Resources