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
Related
I'm using constrainAs with Jetpack Compose to constrain a list of options.
To the left of the text there is a space, I believe this is caused by the fact that I constrain the text box to the start to the parent and the end to the start of the switch, but I need the text to wrap as shown in the second option so I think I need both of those constraints. I have tried several constraints but cannot figure out how to have the text left justified and have the wrapping. The problem is depicted in red in the image.
Also, I cannot figure out how to have the same spacing between the title and the description. This is shown in blue in the picture. I have the description constrained to the bottom of the title but when it wraps the text box becomes larger and is moved up and because the text gets centered it creates different spacing.
I have attached an image and the code.
#Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
Box(modifier = Modifier
.height(66.dp)
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (titleText, descriptionText, switch) = createRefs()
Text(
text = title,
modifier = Modifier
.padding(bottom = 16.dp)
.constrainAs(titleText) {
start.linkTo(parent.start)
top.linkTo(parent.top)
},
color = MyAppTheme.colors.ice,
fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
textAlign = TextAlign.Start
)
Text(
text = description,
modifier = Modifier
.wrapContentSize()
.constrainAs(descriptionText) {
start.linkTo(parent.start)
end.linkTo(switch.start)
top.linkTo(titleText.bottom)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
},
color = MyAppTheme.colors.chalk,
fontSize = 14.sp,
fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
maxLines = 2,
textAlign = TextAlign.Start
)
val checkedState = remember { mutableStateOf(true) }
Switch(modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = MyAppTheme.colors.envy,
checkedTrackColor = MyAppTheme.colors.darkerEnvy,
uncheckedThumbColor = MyAppTheme.colors.navy,
uncheckedTrackColor = MyAppTheme.colors.darkerNavy,
),
)
}
}
}
I copy and paste the code into my editor and it gave me a lot of errors I couldn't solve. so I couldn't make you a copy and past the answer ... sorry for that.
But! look carefully!
the only thing you need to think about are Row() and Columns()
Insert this mindset into your way of thinking and it will make your life easy.
like CSS if you are familiar with web development (because I saw you wrote justify content).
look at this picture down bellow.
as you can see at the picture above there are two major properties of the rows, to Justify the content as you said you want to do. and you can list all the properties with ctrl + space
the first is horizontalArrangement = , and the second is verticalAlignment = as you can see in the code bellow.
Row(
horizontalArrangement = Arrangement.Start, // the properties you are looking for in a Row()
verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for in a Row()
)
this is very confusing because a Column has really similar properties to justify its content too!
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
)
which the first is verticalArrangement = and the second is horizontalAlignment = and they are different words so be in focus when dealing with this deign so you wont confuse your self!
I included the property of the border in every row and column so you can see my way of thinking.
pro tip! use a lazy column so that the window size can be dynamic to your use.
after you will play with this it will be easier for you to make.
the weight() property of the modifier solves this problem.
i gave the left column 85% and the Switch button 15% percent you will see in the code where to modify it if you need to do so.
I created for you the simplest example I can make to fit your need that you can modify for you if you are encountering any more problems feel free to comment and ask.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lilmokeq.ui.theme.LilMokeQTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LilMokeQTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background
) {
NotificationCenter(
)
}
}
}
}
}
#Composable
fun NotificationCenter() {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
.background(color = Color.White)
) {
item {
// create a row with a X icon on the left and a title
// right next to it with a little padding
Row(
horizontalArrangement = Arrangement.Start, // the properties you are looking for in a Row()
verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for in a Row()
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White)
.border(1.dp, Color.Black)
) {
IconButton(
onClick = { /*TODO*/ }, modifier = Modifier.clip(CircleShape)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = Color.Black
)
}
Text(
text = "Notifications Preferences",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
// fontFamily = FontFamily(Font(R.font.roboto)), // for some reason i have a problem with this line
modifier = Modifier.padding(start = 8.dp)
)
}
Spacer(modifier = Modifier.height(8.dp))
Card(
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.White)
) {
// create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.border(1.dp, Color.Black)
) {
Column(
// the left column
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start, // useful to justify content
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.85f)
) {
Text(
text = "Your Account",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 8.dp)
)
Text(
text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis ",
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
)
}
Column(
// the right column
horizontalAlignment = Alignment.End,
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.15f)
) {
Switch(
checked = true,
onCheckedChange = { /*TODO*/ },
modifier = Modifier
.padding(end = 8.dp)
.border(1.dp, Color.Black)
)
}
} // end of row
} // end of card
Spacer(modifier = Modifier.height(8.dp))
Card(
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.White)
) {
// create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.border(1.dp, Color.Black)
) {
Column(
// the left column
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start, // useful to justify content
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.85f)
) {
Text(
text = "Second notification",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 8.dp)
)
Text(
text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit,",
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
)
}
Column(
// the right column
horizontalAlignment = Alignment.End,
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.15f)
) {
Switch(
checked = true,
onCheckedChange = { /*TODO*/ },
modifier = Modifier
.padding(end = 8.dp)
.border(1.dp, Color.Black)
)
}
} // end of row
} // end of card
}
}
}
the final result ^
this playlist covers a lot of information. watch it to get more ideas about your design and how to implement them.
https://www.youtube.com/watch?v=cDabx3SjuOY&list=PLQkwcJG4YTCSpJ2NLhDTHhi6XBNfk9WiC&ab_channel=PhilippLackner
I think below changes in your code will solve your problem, I am not use font style and colour same as yours.
To left/start alignment you have to remove .wrapContentSize() method.
Box(modifier = Modifier
.height(66.dp)
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (titleText, descriptionText, switch) = createRefs()
Text(
text = title,
modifier = Modifier
.constrainAs(titleText) {
start.linkTo(parent.start)
top.linkTo(switch.top)
},
color = Color.Black,
fontSize = 18.sp,
textAlign = TextAlign.Start
)
Text(
text = description,
modifier = Modifier
.constrainAs(descriptionText) {
start.linkTo(parent.start)
end.linkTo(switch.start)
top.linkTo(titleText.bottom)
width = Dimension.fillToConstraints
},
color = Color.Red,
fontSize = 14.sp,
textAlign = TextAlign.Start
)
val checkedState = remember { mutableStateOf(true) }
Switch(modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.Green,
checkedTrackColor = Color.Gray,
uncheckedThumbColor = Color.Cyan,
uncheckedTrackColor = Color.DarkGray,
),
)
}
}
But I think you have to change you function constraint structure as below code
#Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
Box(
contentAlignment = Alignment.TopStart,
modifier = Modifier
.height(85.dp)
.padding(8.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (descriptionText, switch) = createRefs()
Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier
.fillMaxWidth()
.padding(start = 25.dp)
.constrainAs(descriptionText) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(switch.start)
},
) {
Text(
text = title,
color = Color.Black,
fontSize = 18.sp,
textAlign = TextAlign.Start
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = description,
color = Color.Red,
fontSize = 14.sp,
textAlign = TextAlign.Start,
maxLines = 2
)
}
val checkedState = remember { mutableStateOf(true) }
Switch(
modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.Green,
checkedTrackColor = Color.Gray,
uncheckedThumbColor = Color.Cyan,
uncheckedTrackColor = Color.DarkGray,
),
)
}
}
}
In both code you have to change padding and spacing as per your requirement.
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?
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)
}
}
}
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
)
}
}
}
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
)
}
}