Image I have a normal Button in Android and a Star icon. I would like to compose them into a new Button icon, where the star is in one of the upper corners like here:
When I use Row both are seperated. As you can see, the star shall overlap the Button in one of its corner. How can I do that?
EDIT: Thanks to Gabriele Mariotti I used
Box {
Button(
id = "btnButton",
modifier = Modifier
.padding(end = 48)
onClick = {
//..
}
)
IconWithStar(
modifier = Modifier
.scale(0.65f)
)
}
Star icon is bound to upper left corner, how would I modify that?
You can wrap the composables with a Box and use the align/offset modifier to adjust the positions of them.
Box(Modifier.padding(top=40.dp)){
Button(
onClick = {})
{
Text("Hello World")
}
Icon(
Icons.Filled.Star, "",
modifier =Modifier
.align(TopEnd)
.offset(12.dp,-12.dp),
tint = Yellow600
)
}
To have more control you can build a custom Layout.
Something like:
Layout( content = {
Button(
modifier = Modifier.layoutId("button"),
onClick = { /* ... */ })
{
Text("Hello World")
}
Icon(Icons.Filled.Star, "",
Modifier.layoutId("icon"),
tint = Yellow600)
}){ measurables, incomingConstraints ->
val constraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
val buttonPlaceable =
measurables.find { it.layoutId == "button" }?.measure(constraints)
val iconPlaceable =
measurables.find { it.layoutId == "icon" }?.measure(constraints)
//align the icon on the top/end edge
layout(width = widthOrZero(buttonPlaceable) + widthOrZero(iconPlaceable)/2,
height = heightOrZero(buttonPlaceable)+ heightOrZero(iconPlaceable)/2){
buttonPlaceable?.placeRelative(0, heightOrZero(iconPlaceable)/2)
iconPlaceable?.placeRelative(widthOrZero(buttonPlaceable)- widthOrZero(iconPlaceable)/2,
0)
}
}
internal fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
internal fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
Related
I'm trying to make a swipe to reveal component using compose, but I want the width of the card that will appear after the swipe to grow to the size of the wrap content without using it, but I don't understand how to calculate the wrap content size.
var width by remember {
mutableStateOf(0.dp)
}
val lowerTransition = updateTransition(transitionState, "lowerCardTransition")
val lowerOffsetTransition by lowerTransition.animateFloat(
label = "lowerCardOffsetTransition",
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
targetValueByState = { if (isRevealed) width.value else 0f },
)
How do I equate the width value used here to the wrap content value?
I'm trying to make the resulting delete button appear all without using a constant value
Try using AnimatedVisibility. For demo purpose I used OnClick, replace it with OnSwipe.
#Preview
#Composable
fun AnimateVisibility2() {
var visible by remember {
mutableStateOf(false)
}
Row(
modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.Center
) {
AnimatedVisibility(
visible = visible, enter = expandHorizontally(), exit = shrinkHorizontally()
) {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Default.Phone, contentDescription = null)
}
}
Button(onClick = { visible = !visible }, Modifier.weight(1f)) {
Text("Click Me")
}
}
}
The Code A displays a dialog box based AlertDialog, and I get Image A when I run Code A.
I find the space between title = { Text(text = dialogTitle) } and text = {...} is too closer in Image A.
So I set Modifier.padding(top = 100.dp) to wish to increase the space between the two controls, but I only get Image B, it seems that Modifier.padding(top = 100.dp) doesn't work as expected, how can I fix it?
Code A
#Composable
fun EditTextDialog(
isShow: Boolean,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit,
saveTitle: String = stringResource(R.string.dialog_save_title),
cancelTitle:String = stringResource(R.string.dialog_cancel_title),
dialogTitle:String ="Edit",
editFieldContent:String ="",
) {
var mText by remember(editFieldContent){ mutableStateOf(editFieldContent) }
val cleanAndDismiss = {
mText = editFieldContent
onDismiss()
}
if (isShow) {
AlertDialog(
title = { Text(text = dialogTitle) },
text = {
Column(
Modifier.padding(top = 20.dp)
//Modifier.padding(top = 100.dp)
//Modifier.height(100.dp), //The same result as Image A
//verticalArrangement = Arrangement.Center
) {
TextField(
value = mText,
onValueChange = { mText = it }
)
}
},
confirmButton = {
TextButton(onClick = { onConfirm(mText) }) {
Text(text = saveTitle)
}
},
dismissButton = {
TextButton(onClick = cleanAndDismiss) {
Text(text = cancelTitle)
}
},
onDismissRequest = cleanAndDismiss
)
}
}
Image A
Image B
With M3 AlertDialog (androidx.compose.material3.AlertDialog) it works.
With M2 AlertDialog, one solution is to remove the title attribute and use the text attribute for the whole layout.
AlertDialog(
onDismissRequest = {},
text = {
Column(){
Text(text = "Title")
Spacer(Modifier.height(30.dp))
TextField(
value = "mText",
onValueChange = { },
)
}
},
//buttons..
)
I don't understand what you're trying to do. If you want more space between the TextField and the dialog buttons, then you don't want a top padding. You want padding below the TextField, so it would be bottom padding on the column.
Also, there's a chance that it won't work properly inside a Column, and you might have to switch it out for Box. And if that doesn't work for some reason, just add a spacer below the TextField:
Spacer(Modifier.height(20.dp).fillMaxWidth())
I assume you are using Material AlertDialog? If yes try using the Material3 variant. It should work then.
Just implement following library:
implementation "androidx.compose.material3:material3:1.0.0-beta02"
And make sure to use the Material3 AlertDialog Composable which is imported with the library.
I'm using the new BackdropScaffold composable to make a similar looking screen like Google Map with a Map on the back and a list on the front. (See the image)
As you can see there is a problem with the corner around the front layer. Currently is displayed the surface under (pale blue). What I would like to achieve is having the Google Map shown in those corners. I tried to play with the size and padding of GoogleMap composable or the front panel but no luck.
UPDATE
The following example code shows the issue I'm facing. As you can see the BackdropScaffold background is correctly applied (RED). The corners of the front layer are transparent. The issue comes out when you have a different color in your background layer (BLUE). If the background layer contains a map you have the same issue.
BackdropScaffold is dividing the space but not overlaying any layer. The front layer should overlay a bit the back layer to fix this problem.
#OptIn(ExperimentalMaterialApi::class)
#Composable
internal fun test() {
val scope = rememberCoroutineScope()
val selection = remember { mutableStateOf(1) }
val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed)
val frontLayerHeightDp = LocalConfiguration.current.screenHeightDp / 3
LaunchedEffect(scaffoldState) {
scaffoldState.conceal()
}
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop scaffold") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(onClick = { scope.launch { scaffoldState.reveal() } }) {
Icon(Icons.Default.Menu, contentDescription = "Localized description")
}
} else {
IconButton(onClick = { scope.launch { scaffoldState.conceal() } }) {
Icon(Icons.Default.Close, contentDescription = "Localized description")
}
}
},
actions = {
var clickCount by remember { mutableStateOf(0) }
IconButton(
onClick = {
// show snackbar as a suspend function
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar #${++clickCount}")
}
}
) {
Icon(Icons.Default.Favorite, contentDescription = "Localized description")
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
LazyColumn(modifier = Modifier.background(Color.Blue)) {
items(if (selection.value >= 3) 3 else 5) {
ListItem(
Modifier.clickable {
selection.value = it
scope.launch { scaffoldState.conceal() }
},
text = { Text("Select $it", color = Color.White) }
)
}
}
},
backLayerBackgroundColor = Color.Red,
frontLayerShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
headerHeight = frontLayerHeightDp.dp,
frontLayerBackgroundColor = Color.White,
frontLayerContent = {
LazyColumn {
items(50) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
}
)
}
BackdropScaffold is creating a Surface for from layer under the hood, when you create your own inside frontLayerContent it's displayed on top of built-in one.
Instead use frontLayerShape and frontLayerBackgroundColor parameters:
frontLayerShape = BottomSheetShape,
frontLayerBackgroundColor = Color.White,
frontLayerContent = {
LazyColumn(
modifier = Modifier.padding(16.dp)
) {
items(
items = moorings,
itemContent = { mooring ->
...
}
)
}
}
p.s. some comments about your code:
When you have modifier parameter, you should only apply it for the topmost container in your view - here you've applied it for content of frontLayerContent, which may cause unexpected behaviour.
You don't need to wrap LazyColumn in a Column - it has no effect when Column has only one child, and if you need to apply a modifier you can do it directly for LazyColumn
I am creating a dropdown menu where the items contain a text element and an icon (a spacer in between); but only the first text is shown completely. The icon is only visible when there is another item taking more space.
#Preview(showSystemUi = true)
#Composable
fun MyScreen() {
Box(Modifier.fillMaxSize(), Alignment.Center) {
Box() {
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = { expanded = !expanded }) {
Icon(imageVector = Icons.Default.MoreVert, contentDescription = null)
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
MyMenuItem("item 1") // Icon visible
MyMenuItem("item 2") // Icon visible
MyMenuItem("item 3 long text") // Icon width shrunk to 0
MyMenuItem("item 4 long te") // Icon visible but shrunk
}
}
}
}
#Composable
fun MyMenuItem(text: String) {
DropdownMenuItem(onClick = { }) {
Text(text)
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = Icons.Default.Check, contentDescription = null) // <-- Icon
}
}
Note :
I have also tried using Row() and Surface() in place of DropdownMenuItem() but the result is similar.
Giving width to the MyMenuItem() works; but I want it to size itself automatically based on content.
Generally speaking, to create such a layout you just need to apply Modifier.weight(1f) to your Text.
You also need Modifier.width(IntrinsicSize.Max) for your Column to make the width equal to the widest item, but in your case it's already built into DropdownMenu.
But then this bug pops up, which doesn't allow you to properly size your MyMenuItem with Icon inside. Please put a star to draw more attention to it.
As a temporary solution until this bug is fixed, you can specify the size of the icon manually, like this:
// copied from the source code as it's internal
const val MaterialIconDimension = 24f
#Composable
fun MyMenuItem(text: String) {
DropdownMenuItem(
onClick = { }
) {
Text(text, Modifier.weight(1f))
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(MaterialIconDimension.dp)
)
}
}
Result:
When using ConstraintLayout in a LazyColumn a simple Text does not appear when we simply scroll up and down.
Changing the Item from a ConstraintLayout to a Row fixes the issue thus I conclude either my code is bugged or ConstraintLayout alpha has a bug.
You can see in the Layout inspector picture the Text is supposed to display a -6.40 euro
edit: I also posted it on the android bug tracker as I wasn't sure if it was my problem or a bug https://issuetracker.google.com/issues/188855913 - Will close this soon most probably
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(smallMargin),
) {
item {
header()
}
transactionsGroupedPer.forEach { (section, transactions) ->
item {
Text(
modifier = modifier.padding(start = largeMargin, end = largeMargin, top = normalMargin),
text = section.uppercase(),
style = MyTheme.typography.label
)
}
items(items) {
MyItem(
item = it,
modifier = Modifier.fillParentMaxWidth(),
)
}
}
}
#Composable
fun MyItem(
name: String,
amount: Money,
modifier: Modifier = Modifier,
textColor: Color = Color.Unspecified,
) {
val constraints = ConstraintSet {
val titleSubTitle = createRefFor(ModifierId.TITLE_SUBTITLE)
val logo = createRefFor(ModifierId.LOGO)
val amount = createRefFor(ModifierId.AMOUNT)
constrain(amount) {
top.linkTo(parent.top, margin = xsmallMargin)
bottom.linkTo(parent.bottom, margin = xsmallMargin)
end.linkTo(parent.end, margin = smallMargin)
}
constrain(logo) {
start.linkTo(parent.start, margin = smallMargin)
bottom.linkTo(parent.bottom)
}
constrain(titleSubTitle) {
top.linkTo(logo.top)
bottom.linkTo(logo.bottom)
start.linkTo(logo.end, margin = smallMargin)
end.linkTo(amount.start, margin = smallMargin)
width = Dimension.fillToConstraints
}
}
ConstraintLayout(constraints,
modifier = modifier.fillMaxWidth().wrapContentHeight()
) {
Text(
color = textColor,
modifier = Modifier.layoutId(ModifierId.AMOUNT),
text = amount.toMoneyAnnotatedString(),
style = amountStyle,
)
ImageContainer(Modifier.layoutId(ModifierId.LOGO), image = image, size = logoSize)
Column(modifier = Modifier.layoutId(ModifierId.TITLE_SUBTITLE), verticalArrangement = Arrangement.Center) {
Text(
color = textColor,
maxLines = maxLines,
text = name,
style = MyTheme.typography.bodyBold
)
if (showSubTitle) {
Text(
color = textColor,
text = date.formatDateTime(DateFormat.LONG),
style = MyTheme.typography.meta
)
}
}
}
}
Bug fixed in beta08
References
https://issuetracker.google.com/issues/188855913
https://issuetracker.google.com/issues/188566058