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
)
}
How can we achieve this in jetpack compose
I'm doing something like this
Button(
elevation = ButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 8.dp,
disabledElevation = 0.dp
),
onClick = { onClick },
shape = RoundedCornerShape(28.dp),
modifier = modifier
.fillMaxWidth()
.shadow(0.dp),
contentPadding = PaddingValues(15.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.White),
border = BorderStroke(1.dp, Color.Grey)
) {
Box(modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center) {
Icon(
imageVector = imageVector,
modifier = Modifier
.size(18.dp),
contentDescription = "drawable icons",
tint = Color.Unspecified
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = buttonText,
color = Color.Black,
textAlign = TextAlign.Center
)
}
}
So as you can see the Google logo is just left of the text I need it at the start of the box so how can I do this.
You can use align(Alignment.CenterStart) on the Icon's Modifier parameter to center the icon around the start of the Box Composable. This alignment will have priority over the Box's alignment parameter.
You can also delete the Spacer composable because the Box layout children are stacked one on top of the other in the composition order. So the Spacer composable is basically laying below the Text composable in the center.
If you want some space between the Icon and the Text, you could use some padding around the Icon instead.
Try this (It worked for me) :
Box(modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center) {
Icon(
imageVector = imageVector,
modifier = Modifier
.size(18.dp)
.align(Alignment.CenterStart),
contentDescription = "drawable icons",
tint = Color.Unspecified
)
Text(
text = buttonText,
color = Color.Black,
textAlign = TextAlign.Center
)
}
#Composable
fun GoogleButton(
modifier: Modifier = Modifier,
imageVector: ImageVector,
buttonText: String,
onClick: (isEnabled: Boolean) -> Unit = {},
enable: Boolean = true,
backgroundColor: Color,
fontColor: Color,
) {
Button(
onClick = { onClick(enable) },
modifier = modifier
.fillMaxWidth()
.shadow(0.dp)
.noInteractionClickable(enabled = false) { onClick(enable) },
elevation = ButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
hoveredElevation = 0.dp,
focusedElevation = 0.dp
),
shape = RoundedCornerShape(28.dp),
contentPadding = PaddingValues(15.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = backgroundColor,
contentColor = fontColor
),
border = BorderStroke(1.dp, MaterialTheme.colors.getButtonBorderStroke)
) {
Box(
modifier = Modifier
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterStart)
) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
imageVector = imageVector,
modifier = Modifier
.size(18.dp),
contentDescription = "drawable_icons",
tint = Color.Unspecified
)
}
Text(
modifier = Modifier.align(Alignment.Center),
text = buttonText,
color = MaterialTheme.colors.loginButtonTextColor,
textAlign = TextAlign.Center,
fontSize = 16.sp,
fontFamily = FontFamily(
Font(
R.font.roboto_medium
)
)
)
}
}
}
As suggested in other answers you can wrap the content with a Box.
As alternative you can simply use the RowScope of the Button without any container.
Just apply a weight(1f) modifier to the Text and an offset(x=- iconWidth/2).
Something like:
Button(
//....
) {
Icon(
imageVector = imageVector,
modifier = Modifier.size(iconWidth),
contentDescription = "drawable icons",
tint = Color.Unspecified
)
Text(
text = "Button",
color = Color.Black,
textAlign = TextAlign.Center,
modifier = Modifier
.weight(1f)
.offset(x= -iconWidth/2) //default icon width = 24.dp
)
}
If you want to use a Box, remove the contentAlignment = Alignment.Center in the Box and use:
Box(modifier = Modifier.fillMaxWidth()) {
Icon( /* ..... */ )
Text(
modifier = Modifier.fillMaxWidth(),
text = "buttonText",
textAlign = TextAlign.Center
)
}
Box doesn't provide bounds, so for longer texts, it causes overlapping. Row works better for me. Also, you can use Spacer here which is not possible for Box. In my case, I have used spacedBy as a replacement for Spacer:
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter,
contentDescription = null
)
Box(
modifier = Modifier.weight(1F),
contentAlignment = Alignment.Center
) {
Text(buttonText)
}
}
Box(contentAlignment = Center){
Icon(Modifier.align(CenterStart))
Text()
}
I have the following page on which I'm trying to display a list of saved cards with the ability to add a new one. The last item in the column is an expandable one so that when clicked, the user can see a form for filling out card info in order to add a new card. This was working just fine yesterday, but I can't figure out why it's not working today.
The actual CardItem elements receive clicks just fine but the custom expandable one does not and neither does the ShadowWrapper parent nor the RaisedCard one.
Cards screen:
private data class CreditCard(val type: CreditCardTypes, val lastDigits: String)
#Composable
fun CardSelectionScreen(onCardSelected: () -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(vertical = 24.dp, horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
var selectedCardIndex by remember { mutableStateOf(0) }
val cardList = listOf(
CreditCard(CreditCardTypes.MASTERCARD, "3429"),
CreditCard(CreditCardTypes.VISA, "3429"),
CreditCard(CreditCardTypes.MASTERCARD, "3429")
)
TopBarPadding(true)
Spacer(modifier = Modifier.height(10.dp))
RaisedCard() {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringResource(id = R.string.please_select_a_card),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Start
)
Spacer(modifier = Modifier.height(9.dp))
for (i in cardList.indices) {
CreditCardItem(cardList[i],
isSelected = selectedCardIndex == i, onItemSelected = { ->
selectedCardIndex = i
})
}
ShadowWrapper( // This is the item's layout
cardElevation = 1.dp,
shadowElevation = 3.dp
) {
Column(
modifier = Modifier
.animateContentSize()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(6.dp))
.background(
if (selectedCardIndex == cardList.size) colorResource(
id = R.color.bottom_modal_drawer_background
) else Color.White
)
.padding(horizontal = 16.dp, vertical = 16.dp)
.clickable(
indication = null,
interactionSource = MutableInteractionSource()
) { // this does not register at all, tried with Log.d
selectedCardIndex = cardList.size
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_add_credit_card),
contentDescription = "Add credit card icon"
)
Spacer(modifier = Modifier.width(13.dp))
Text(
stringResource(id = R.string.new_card_addition),
textAlign = TextAlign.Start,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = colorResource(id = R.color.Orange_100)
)
}
if (selectedCardIndex == cardList.size) {
Column(
modifier = Modifier.padding(
horizontal = 16.dp
)
) {
Spacer(modifier = Modifier.padding(22.fixedDp()))
Text(
text = LocalContext.current.getString(R.string.add_credit_card_top_msg),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = colorResource(id = R.color.Black_100)
)
Spacer(modifier = Modifier.padding(10.dp))
InputField(label = LocalContext.current.getString(R.string.owner_name))
Spacer(modifier = Modifier.padding(18.fixedDp()))
InputField(label = LocalContext.current.getString(R.string.credit_card_number))
Spacer(modifier = Modifier.padding(18.fixedDp()))
Row() {
Box(
modifier = Modifier
.weight(1.5f)
) {
InputField(label = LocalContext.current.getString(R.string.expiration_date))
}
Spacer(modifier = Modifier.padding(6.fixedDp()))
Box(
modifier = Modifier
.weight(1f)
) {
InputField(
label = LocalContext.current.getString(R.string.cvv),
isPassword = true,
placeholder = ""
)
}
}
Spacer(modifier = Modifier.height(34.fixedDp()))
Row() {
MyCheckbox(
modifier = Modifier.padding(top = 3.dp),
isCheckedInitially = true
)
Spacer(modifier = Modifier.width(13.dp))
Text(
stringResource(id = R.string.save_card_for_future_transactions),
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
color = colorResource(id = R.color.Black_100)
)
}
Spacer(modifier = Modifier.padding(22.fixedDp()))
}
}
}
}
Spacer(modifier = Modifier.height(2.dp))
}
}
Spacer(modifier = Modifier.height(32.dp))
MyButton(
text = stringResource(id = R.string.continue_text),
MyButtonType.PRIMARY,
onClick = { onCardSelected() }
)
Spacer(modifier = Modifier.height(20.dp))
AcceptedCardsFooter()
BottomBarPadding(true)
}
}
#Composable
private fun CreditCardItem(
cardDetails: CreditCard,
isSelected: Boolean,
onItemSelected: () -> Unit
) {
ShadowWrapper(cardElevation = 1.dp, shadowElevation = 3.dp) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(6.dp))
.background(if (isSelected) colorResource(id = R.color.bottom_modal_drawer_background) else Color.White)
.padding(horizontal = 16.dp, vertical = 15.dp)
.clickable(indication = null, interactionSource = MutableInteractionSource()) {
onItemSelected()
},
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(verticalAlignment = Alignment.CenterVertically) {
MyRadioButton(label = "", selected = isSelected)
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier
.width(43.dp)
.height(33.dp)
.clip(RoundedCornerShape(4.dp))
.background(colorResource(id = R.color.Grey_10))
.padding(horizontal = 6.dp, vertical = 7.dp)
) {
Image(
painter = painterResource(id = cardDetails.type.icon),
contentDescription = "",
modifier = Modifier.align(Alignment.Center)
)
}
Spacer(modifier = Modifier.padding(8.fixedDp()))
Text(
text = "${cardDetails.type.prefix}****${cardDetails.lastDigits}",
fontSize = 16.sp,
color = colorResource(id = R.color.Black_100)
)
}
}
}
}
RaisedCard.kt:
#Composable
fun RaisedCard(
modifier: Modifier = Modifier,
mainBody: #Composable () -> Unit
) {
Card(
shape = RoundedCornerShape(13.dp),
elevation = 10.dp,
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
) {
Column(
modifier = Modifier
.background(Color.White)
.padding(horizontal = 16.dp)
) {
mainBody()
}
}
}
ShadowWrapper.kt:
#Composable
fun ShadowWrapper(
modifier: Modifier = Modifier,
border: BorderStroke = BorderStroke(0.dp, Color.Transparent),
cardElevation: Dp = 2.dp,
shadowElevation: Dp = 1.dp,
shadowShapeRadius: Dp = 6.dp,
content: #Composable () -> Unit,
) {
Card(
elevation = cardElevation,
border = border,
shape = RoundedCornerShape(shadowShapeRadius),
modifier = modifier.shadow(shadowElevation, RoundedCornerShape(shadowShapeRadius)).wrapContentHeight()
) {
content()
}
}
I wasn't able to reproduce your issue, probably because other parts of your application are triggering additional recompositions. However the cause is most likely that you forget to remember your MutableInteractionSources.
Like this:
otherModifiers.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { // this does not register at all, tried with Log.d
selectedCardIndex = cardList.size
}
If you do not wrap MutableInteractionSource in a remember, a new instance is created on every recomposition, so state like a previous touch events is lost
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)
}
}
}
I want to create Cardview using jetpack compose but i am not able to find any
example.
You can use something like:
Card(
shape = RoundedCornerShape(8.dp),
backgroundColor = MaterialTheme.colors.surface,
) {
Column(
modifier = Modifier.height(200.dp).padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Text(text = "This is a card view",
style = MaterialTheme.typography.h4)
}
}
With Material3 you can use:
Card(
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = /* ... */)
)
in v0.1.0-dev09 version, can be done like this, where the padding of Card sets the margin of the card, the padding of Box sets the padding inside the card.
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier.padding(16.dp).fillMaxWidth()
) {
Box(modifier = Modifier.height(200.dp).padding(16.dp)) {
Text("This is a card view")
}
}
As the friends recommended, Card is a way of creating CardView but you can do that by surface too :
val shape = RoundedCornerShape(10.dp)
Surface(modifier = Modifier.size(250.dp, 70.dp), elevation = 8.dp, shape = shape) {
Text(text = "Sample text")
}
Note that Surface and Card cannot be used for positioning items so that if you wanna position that Text(text = "Sample text") , you should use one layout inside that Surface like this :
val shape = RoundedCornerShape(10.dp)
Surface(modifier = Modifier.size(250.dp, 70.dp), elevation = 8.dp, shape = shape) {
Box(modifier = Modifier.fillMaxSize()) {
Text(modifier = Modifier.align(Alignment.Center), text = "Sample text")
}
}
The appropriate way for creating CardView is this but if you wanna just create shadow for a view, you can use Modifier.shadow (Note that Modifier.shadow and Surface/Card are not the same):
Box(modifier = Modifier.size(250.dp, 70.dp).shadow(8.dp, RoundedCornerShape(10.dp)), contentAlignment = Alignment.Center) {
Text(text = "Sample Text")
}
Code:
class ImageCardActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val painter = painterResource(id = R.drawable.grogu)
val contentDescription = "Grogu says hi"
val title = "Force is strong with Grogu"
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier.fillMaxWidth(0.5f)
) {
ImageCard(
title = title,
contentDescription = contentDescription,
painter = painter
)
}
}
}
}
}
#Composable
fun ImageCard(
title: String,
contentDescription:String,
painter: Painter,
modifier:Modifier=Modifier
){
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
elevation = 5.dp
) {
Box(
modifier = Modifier.height(200.dp)
) {
// Image
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = ContentScale.Crop
)
// Gradient
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent, Color.Black
),
startY = 300f
)
)
)
// Title
Box(
modifier = Modifier
.fillMaxSize()
.padding(12.dp),
contentAlignment = Alignment.BottomStart
) {
Text(
text = title,
style = TextStyle(color = Color.White, fontSize = 12.sp)
)
}
}
}
}