I've recently migrated from XML to Jetpack Compose and decided to create a note app with Jetpack Compose to learn more about it. an item of my notes in this program includes the title, description, and the level of priority, which is displayed in the direction of its start and in different colors.
According to what I wanted, the priority section should extend to the size of the item's content, but
When I set the height of the priority section to fillMaxHeight(), the note item extends to the size of the screen.
I also tried wrapContentHeight(), but this time the priority section disappeared entirely.
My code:
#Composable
fun SingleItem(modifier: Modifier = Modifier, title: String = "", description: String = "") {
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp).wrapContentHeight()
) {
Row() {
Box(modifier = Modifier.fillMaxHeight().width(15.dp).background(Color.Red))
Column() {
Text(text = title, modifier = Modifier.padding(start = 16.dp, top = 16.dp))
Spacer(modifier = Modifier.height(16.dp))
Text(text = description, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp))
}
}
}
}
what happened when using fillMaxHeight() :
what I wanted to do :
something like this
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(modifier = Modifier.background(Color.Red)) {
Box(modifier = Modifier.width(15.dp)) {
}
Column(modifier = Modifier.background(Color.Black)) {
Text(
text = "title",
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
)
Spacer(modifier = Modifier
.height(16.dp)
)
Text(
text = "null",
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
)
}
}
}
You have to use Intrinsic Measurements in your Row. Also, remember to pass your incoming Modifier to the Card.
#Composable
fun SingleItem(modifier: Modifier = Modifier, title: String = "", description: String = "") {
Card(
shape = MaterialTheme.shapes.medium,
modifier = modifier.padding(horizontal = 24.dp, vertical = 16.dp).wrapContentHeight()
) {
Row(
modifier = Modifier.height(IntrinsicSize.Min)
) {
Box(modifier = Modifier.fillMaxHeight().width(15.dp).background(Color.Red))
Column() {
Text(text = title, modifier = Modifier.padding(start = 16.dp, top = 16.dp))
Spacer(modifier = Modifier.height(16.dp))
Text(text = description, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp))
}
}
}
}
Related
I want to align my contact elements as in the picture but I don't want to use padding and spacer, because it will look different on different devices(mb Im wrong). So how can I get such an output of elements?
[This is what I want it to look like] (https://i.stack.imgur.com/L8GLZ.png)
Tryed using horizontalAlignment = Alignment.CenterHorizontall on the columb block and different horizontalAlignments on the rows but couldn't manage to get what I wanted(
#Composable
fun Greeting() {
loadLogo()
loadContactInfo()
}
#Composable
fun loadLogo(
fio: String = stringResource(R.string.FIO),
logo: Int = R.drawable.android_logo,
title: String = stringResource(R.string.title)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(bottom = 100.dp)
) {
Image(
painter = painterResource(id = logo),
contentDescription = stringResource(R.string.logo_desc),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.height(150.dp)
.width(150.dp)
)
Text(text = fio, fontSize = 32.sp, modifier = Modifier.padding(bottom = 4.dp))
Text(text = title, fontSize = 16.sp, color = Color.Green)
}
}
#Composable
fun loadContactInfo(
phoneNum: String = stringResource(R.string.my_phone_num),
TGLink: String = stringResource(R.string.telegram_link),
emailAddress: String = stringResource(R.string.email_adress)
) {
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Row() {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = "Phone icon",
)
Text(text = phoneNum)
}
Spacer(modifier = Modifier.height(35.dp))
Row() {
Icon(
imageVector = Icons.Default.Share,
contentDescription = "Phone icon",
)
Text(text = TGLink)
}
Spacer(modifier = Modifier.height(35.dp))
Row(
modifier = Modifier
.padding(bottom = 45.dp)
) {
Icon(
imageVector = Icons.Default.Email,
contentDescription = "Phone icon",
)
Text(text = emailAddress)
}
}
}
Looks like this
Use as parent container a Column and just apply the weight(1f) modifier to the 1st nested Column (logo and title).
Column(Modifier.fillMaxSize()){
loadLogo(modifier = Modifier.fillMaxWidth().weight(1f))
loadContactInfo()
}
#Composable
fun loadLogo(
modifier : Modifier = Modifier,
fio: String = "Fio",
logo: Int = R.drawable.ic_launcher_foreground,
title: String = "Title"
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
//
}
}
#Composable
fun loadContactInfo(
modifier : Modifier = Modifier,
phoneNum: String = "+XX YYYY.YYYY.YYYY",
TGLink: String = "Android Dev",
emailAddress: String = "#androidDev"
) {
Column(
modifier = modifier,
) {
//...
}
}
I don't want to use padding and spacer, because it will look different on different devices
I don't think this is correct. If you use padding for your Composables and test it on different devices with different screen sizes, the padding you set for them is the same.
But if you don't use Modifier.fillMaxWidth() or Modifier.fillMaxHeight() or Modifier.fillMaxSize(), You may get some empty space around your Composable because you didn't tell it to fill the screen.
So for your rows this is what I suggest you to do:
First create a Composable representing a row. I'm calling it TextWithIcon:
#Composable
fun TextWithIcon(
modifier: Modifier = Modifier,
title: String,
icon: ImageVector
) {
Row(
modifier = modifier.padding(vertical = 16.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = icon, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = title)
}
}
Next, use it in your screen for each row:
#Composable
fun loadContactInfo(
phoneNum: String = stringResource(R.string.my_phone_num),
TGLink: String = stringResource(R.string.telegram_link),
emailAddress: String = stringResource(R.string.email_adress)
) {
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
TextWithIcon(
modifier = Modifier.fillMaxWidth(),
title = phoneNum,
icon = Icons.Default.Phone
)
TextWithIcon(
modifier = Modifier.fillMaxWidth(),
title = TGLink,
icon = Icons.Default.Share
)
TextWithIcon(
modifier = Modifier.fillMaxWidth(),
title = emailAddress,
icon = Icons.Default.Email
)
}
I'm new to Android Jetpack Compose and have the following layout problem:
It's a simple example to clarify my issue. I want that Button header to stay on top and this two text areas at the bottom.
The image should take the rest of the available space, depending on the screen size. I don't want a scrollbar or white areas at the top/bottom.
At the moment the image has a fixed size. How do I have to adjust the layout so that the image automatically adapts to the available space?
Here's the code:
#Composable
fun Greeting() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Spacer(
modifier = Modifier
.height(32.dp)
.fillMaxSize()
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
Button(onClick = { /*TODO*/ }) {
Text(text = "Back")
}
Button(onClick = { /*TODO*/ }) {
Text(text = "Next")
}
}
Spacer(modifier = Modifier.height(24.dp))
Image(painter = painterResource(id = R.drawable.waterfall), contentDescription = "Instant post demo image", Modifier.height(200.dp))
Spacer(modifier = Modifier.height(24.dp))
Text(text = text, fontSize = 14.sp, modifier = Modifier.padding(start = 16.dp, end = 16.dp))
Spacer(modifier = Modifier.height(16.dp))
Text(text = text, fontSize = 14.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 16.dp, end = 16.dp)
)
}
}
Thx in advance
I wanna have this layout in Jetpack Compose. How can I place the image at the bottom of the card and have it a bit outside the card borders? Also how to make sure that the content above won't collide with the image, because the content above can be longer. Thanks very much.
A simple way is to use a Box and apply an Offset to the Image
Box() {
val overlayBoxHeight = 40.dp
Card(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
) {
//...
}
Image(
painterResource(id = R.drawable.xxxx),
contentDescription = "",
modifier = Modifier
.align(BottomCenter)
.offset(x = 0.dp, y = overlayBoxHeight )
)
}
If you want to calculate the offset you can use a Layout composable.
Something like:
Layout( content = {
Card(
elevation = 10.dp,
modifier = Modifier
.layoutId("card")
.fillMaxWidth()
.height(300.dp)
) {
//...
}
Image(
painterResource(id = R.drawable.conero),
contentDescription = "",
modifier = Modifier
.layoutId("image")
)
}){ measurables, incomingConstraints ->
val constraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
val cardPlaceable =
measurables.find { it.layoutId == "card" }?.measure(constraints)
val imagePlaceable =
measurables.find { it.layoutId == "image" }?.measure(constraints)
//align the icon on the top/end edge
layout(width = widthOrZero(cardPlaceable),
height = heightOrZero(cardPlaceable)+ heightOrZero(imagePlaceable)/2){
cardPlaceable?.placeRelative(0, 0)
imagePlaceable?.placeRelative(widthOrZero(cardPlaceable)/2 - widthOrZero(imagePlaceable)/2,
heightOrZero(cardPlaceable) - heightOrZero(imagePlaceable)/2)
}
}
You can use Card for main view layout.
To have an icon placed like this, you need to place it with Card into a box, apply Alignment.BottomCenter alignment to image and add paddings.
To make image be under your text, I've added Spacer with size calculated on image size.
#Composable
fun TestView() {
CompositionLocalProvider(
LocalContentColor provides Color.White
) {
Column {
LazyRow {
items(10) {
Item()
}
}
Box(Modifier.fillMaxWidth().background(Color.DarkGray)) {
Text("next view")
}
}
}
}
#Composable
fun Item() {
Box(
Modifier
.padding(10.dp)
) {
val imagePainter = painterResource(id = R.drawable.test2)
val imageOffset = 20.dp
Card(
shape = RoundedCornerShape(0.dp),
backgroundColor = Color.DarkGray,
modifier = Modifier
.padding(bottom = imageOffset)
.width(200.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(20.dp)
) {
Text(
"Title",
modifier = Modifier.align(Alignment.Start)
)
Spacer(modifier = Modifier.size(15.dp))
Text("PM2.5")
Text(
"10",
fontSize = 35.sp,
)
Text("m/s")
Spacer(modifier = Modifier.size(15.dp))
Text(
"Very Good",
fontSize = 25.sp,
)
Spacer(
modifier = Modifier
.size(
with(LocalDensity.current) { imagePainter.intrinsicSize.height.toDp() - imageOffset }
)
)
}
}
Image(
painter = imagePainter,
contentDescription = "",
Modifier
.align(Alignment.BottomCenter)
)
}
}
Result:
How do I create a rounded checkbox in Jetpackcompose like this. I tried using a Shape composable on it but it doesn't work.
I was looking on how to do the same thing you were asking, your question helped me on my journey so it is only fair I share. Add some animations and you are set my friend.
Make a round looking icon by using a box and an icon
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
2.Place the newly made rounded icon and some text next to each other by using a Row
Row(
verticalAlignment = Alignment.CenterVertically,
){
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
3.Replace whatever you want with variables so you can customize
it
#Composable
fun RoundedCheckView(
) {
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
{
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
4.Finally add Modifier.toggleable to the row, basically making it a clickable item that toggles (between true and false) a variable in this case isChecked. Then just customize the variables according to what you need
#Composable
fun RoundedCheckView()
{
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.toggleable(value = isChecked.value,role = Role.Checkbox) {
isChecked.value = it
if (isChecked.value) {
checkedText.value = "Checked"
circleSize.value = 40.dp
circleThickness.value = 3.dp
color.value = Color.Black
} else {
checkedText.value = "unChecked"
circleSize.value = 20.dp
circleThickness.value = 2.dp
color.value = Color.Gray
}
}) {
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
if(isChecked.value){
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
This is how we can make a custom check box in jetpack compose
val isCheck = remember { mutableStateOf(false) }
Row {
Card(
modifier = Modifier.background(Color.White),
elevation = 0.dp,
shape = RoundedCornerShape(6.dp),
border = BorderStroke(1.5.dp, color = titleColor)
) {
Box(
modifier = Modifier
.size(25.dp)
.background(if (isCheck.value) titleColor else Color.White)
.clickable {
isCheck.value = !isCheck.value
},
contentAlignment = Center
) {
if(isCheck.value)
Icon(Icons.Default.Check, contentDescription = "", tint = Color.White)
}
}
Text(
modifier = Modifier
.align(CenterVertically)
.padding(start = 10.dp),
text = "I agree with the terms & condition",
)
}
You can try to make it use Box with modifier content alignment center. and put an icon on there.
#Preview
#Composable
fun Check() {
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.background(Color.Red)
.padding(5.dp)
.clip(CircleShape)
.background(Color.Blue),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
We can use animations to make it behave similar to the default Checkbox. Using Modifier.toggleable on the top-level Row, the entire thing is clickable, including the label. This also creates the proper semantics for screen reader users. You can change the shape of the card to get a circular checkbox.
#Composable
fun PrimaryCheckbox(
label: String,
modifier: Modifier = Modifier,
size: Float = 24f,
checkedColor: Color = DarkGray,
uncheckedColor: Color = White,
checkmarkColor: Color = White,
onValueChange: () -> Unit
) {
var isChecked by remember { mutableStateOf(false) }
val checkboxColor: Color by animateColorAsState(if (isChecked) checkedColor else uncheckedColor)
val density = LocalDensity.current
val duration = 200
Row(
modifier = modifier
.toggleable(
value = isChecked,
role = Role.Checkbox,
onValueChange = {
isChecked = !isChecked
onValueChange.invoke()
}
)
) {
Card(
elevation = 0.dp,
shape = RoundedCornerShape(4.dp),
border = BorderStroke(1.5.dp, color = checkedColor),
) {
Box(
modifier = Modifier
.size(size.dp)
.background(checkboxColor),
contentAlignment = Alignment.Center
) {
androidx.compose.animation.AnimatedVisibility(
visible = isChecked,
enter = slideInHorizontally(
animationSpec = tween(duration)
) {
with(density) { (size * -0.5).dp.roundToPx() }
} + expandHorizontally(
expandFrom = Alignment.Start,
animationSpec = tween(duration)
),
exit = fadeOut()
) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = checkmarkColor
)
}
}
}
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp),
text = label,
)
}
}
I want to make an Image with Text on top, just like Google CLassroom. But first I want to test Image and then Text. Instead, I got the image overlapping the text. Image Overlapping text
Then I move the Image code after the text. How to get simple G classroom format
Text then Image
#Composable
fun ClassImage(
// icon: VectorAsset,
// label: String,
// modifier: Modifier = Modifier
) {
val imageAlpha = 1f
Surface(
modifier = Modifier
.padding(start = 8.dp, top = 8.dp, end = 8.dp)
.fillMaxWidth(),
color = colors.primary.copy(alpha = 0.12f)
) {
TextButton(
onClick = {},
modifier = Modifier.fillMaxWidth()
) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth())
{
Image(
imageResource(id = R.drawable.class1),
alpha = imageAlpha
)
Column {
Text("Alfred Sisley", fontWeight = FontWeight.Bold)
ProvideEmphasis(emphasis = EmphasisAmbient.current.medium) {
Text("3 minutes ago", style = MaterialTheme.typography.body2)
}
}
}
}
}
}
To put Text on top of the Image you can use Box, which is similar to old FrameLayout.
I'm not sure want you wanted to achieve, but if smth like this:
Then you can do it this way:
Surface(
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.preferredHeight(128.dp)
.clickable(onClick = {})
) {
Box {
Image(
vectorResource(id = R.drawable.ic_launcher_background),
alpha = imageAlpha,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Column(modifier = Modifier.padding(16.dp)) {
Text(
"Alfred Sisley",
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.h6)
ProvideEmphasis(emphasis = EmphasisAmbient.current.medium) {
Text("3 minutes ago", style = MaterialTheme.typography.body2)
}
Spacer(modifier = Modifier.weight(1f))
Text(text = "Footer", style = MaterialTheme.typography.body1)
}
}
}
With 1.0.0-beta02 you can use a Box as parent container.
Something like:
Box(modifier = Modifier.height(IntrinsicSize.Max))
{
Image(
painterResource(id = R.drawable.xx),
"contentDescription",
alpha = 0.8f,
modifier = Modifier.requiredHeight(100.dp)
)
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Bottom) {
Text("Alfred Sisley",
fontWeight = FontWeight.Bold)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text("3 minutes ago", style = MaterialTheme.typography.body2)
}
}
}