I'm trying to handle Horizontal Pager but I'm having a problem with additional padding from the top. It only happens when I add HorizontalPager() to my layout.
As you can see in the attachment I have grey indicators which I want to put below my pager but when I do that Pager goes lower and indicators become invisible. It looks like HorizontalPager() has some top padding (?)
Box(
modifier = Modifier
.fillMaxWidth()
.height(350.dp)
.clip(RoundedCornerShape(0.dp, 0.dp, 12.dp, 12.dp))
.background(MaterialTheme.colors.onBackground)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp)
) {
val (backButton, favouriteButton, photosPager) = createRefs()
BackNavigationSingleButton(
backButtonSelected = { }, modifier = Modifier
.height(42.dp)
.width(60.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.Red)
.constrainAs(backButton) {
top.linkTo(parent.top)
start.linkTo(parent.start)
}
)
IconButton(onClick = {}, modifier = Modifier
.size(42.dp)
.constrainAs(favouriteButton) {
top.linkTo(parent.top)
end.linkTo(parent.end)
}) {
Icon(
imageVector = Icons.Outlined.Favorite
)
}
Column(
modifier = Modifier
.constrainAs(photosPager) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}) {
val pagerState = rememberPagerState()
val items = 3
HorizontalPager(
count = items,
state = pagerState,
contentPadding = PaddingValues(0.dp)
) { page ->
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Green)
)
}
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
for (i in 1..items) {
Canvas(modifier = Modifier.size(20.dp)) {
drawCircle(
color = Color.Gray,
center = Offset(x = 10f, y = 10f),
radius = size.minDimension / 4,
)
}
}
}
}
}
}
Each page inside HorizontalPager is using Modifier.fillParentMaxSize, which makes it fill all available height, same what Modifier.fillMaxHeight does.
When Column has an item with Modifier.fillMaxHeight, this item will push all next items. To prevent this you can use Modifier.weight: in this case height of this view will be calculated after all other Column children.
Also most of time ConstraintLayout is redundant in Compose, here's how you can build same layout without it:
Box(
modifier = Modifier
.fillMaxWidth()
.height(350.dp)
.clip(RoundedCornerShape(0.dp, 0.dp, 12.dp, 12.dp))
.background(MaterialTheme.colors.onBackground.copy(0.5f))
.padding(10.dp)
) {
Row {
TextButton(
onClick = { },
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.background(Color.Red)
) {
Text("button")
}
Spacer(Modifier.weight(1f))
IconButton(
onClick = {},
modifier = Modifier
.size(42.dp)
) {
Icon(
imageVector = Icons.Outlined.Favorite,
contentDescription = null
)
}
}
Column {
val pagerState = rememberPagerState()
val items = 3
HorizontalPager(
count = items,
state = pagerState,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.weight(1f)
) { page ->
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Green)
)
}
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
for (i in 1..items) {
Canvas(modifier = Modifier.size(20.dp)) {
drawCircle(
color = Color.Gray,
center = Offset(x = 10f, y = 10f),
radius = size.minDimension / 4,
)
}
}
}
}
}
Result:
Related
Whenever I navigate within my screen the LazyColumn scrolls perfectly but the top app bar doesn't move at all. Is it possible to tell the enlarged top app bar to scroll or can this only be done with the 4 default top app bars provided in Material 3?
#Composable
fun MyScreen(navController: NavController) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val mConfiguration = LocalConfiguration.current
val mScreenHeight = mConfiguration.screenHeightDp.dp
val mSize = mScreenHeight / 2
Column {
Box(
modifier = Modifier
.height(mSize)
.weight(1f)
.fillMaxWidth()
) {
LargeTopAppBar(
title = {
Text(
text = "Android Studio Dolphin", overflow = TextOverflow.Visible, maxLines = 1
)
},
scrollBehavior = scrollBehavior)
}
Box(
modifier = Modifier
.height(mSize)
.weight(1f)
.fillMaxWidth()
.background(Color.Green)
) {
MyScreenContent()
// contentPadding ->
// MyScreenContent(contentPadding = contentPadding)
}
}
}
#Composable
fun MyScreenContent(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
Box(modifier = modifier.fillMaxSize()) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
item {
Text(text = "text", style = TextStyle(fontSize = 18.sp))
}
items(75) {
ListItem(it)
}
}
}
}
You should use a Scaffold applying the nestedScroll modifier.
Something like:
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = {
Text(
text = "Android Studio Dolphin", overflow = TextOverflow.Visible, maxLines = 1
)
},
scrollBehavior = scrollBehavior)
},
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding,){
//....
}
}
)
I made an expandable floating action button like
here
i want to make it expand verticly and reveal to more buttons, when its note docked to bottom navigation bar, every thing is ok but when i make it docked it's going through bottom nav bar,
When not expanded looking like this
but when expanded like this
How can i make it expand properly
and i also want to align all 3 circle buttons horizontaly.
Thank you so much!
Code:
#Composable
fun ExpandableFAB(
modifier: Modifier = Modifier,
onFabItemClicked: (fabItem: ExpandableFABMenuItem) -> Unit,
menuItems: List<ExpandableFABMenuItem> = ExpandableFABMenuItem.expandableFabMenuItems
) {
var expandedState by remember { mutableStateOf(false) }
val rotationState by animateFloatAsState(
targetValue = if (expandedState) 45f else 0f
)
Column(
modifier = modifier.wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(
visible = expandedState,
enter = fadeIn() + expandVertically(),
exit = fadeOut()
) {
LazyColumn(
modifier = Modifier.wrapContentSize(),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
items(menuItems.size) { index ->
ExpandableFABItem(
item = menuItems[index],
onFabItemClicked = onFabItemClicked
)
}
item {}
}
}
FloatingActionButton(
onClick = { expandedState = !expandedState },
backgroundColor = Blue
) {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = "FAB",
modifier = Modifier.rotate(rotationState),
)
}
}
}
#Composable
fun ExpandableFABItem(
item: ExpandableFABMenuItem,
onFabItemClicked: (item: ExpandableFABMenuItem) -> Unit
) {
Row(
modifier = Modifier
.wrapContentSize()
.padding(end = 10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = item.label,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(horizontal = 6.dp, vertical = 4.dp)
)
Box(
modifier = Modifier
.clip(CircleShape)
.size(42.dp)
.clickable { onFabItemClicked }
.background(Blue),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = item.icon,
contentDescription = "Create New Note",
tint = White
)
}
}
}
I am trying to prepare a design in jetpack compose with a partial border on one side of the box. Here is the UI I have right now,
The background is a solid color as of now but will be replaced with a image. I want to break border on bottom left of and add some text similar to the screenshot below while keeping the background as it is.
Here is my code:
Box(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()) {
Box(modifier = Modifier.fillMaxHeight().fillMaxWidth().background(Color(0xFF37C7D7).copy(alpha = 0.6f)))
Box(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(Color.Transparent)
.padding(20.dp,30.dp)
.border(width = 0.8.dp, color = Color.White.copy(alpha = 0.5f), shape=RoundedCornerShape(32.dp))
)
Box(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(Color.Transparent)
.padding(28.dp,38.dp)
.border(width = 0.8.dp, color = Color.White.copy(alpha = 0.5f), shape=RoundedCornerShape(28.dp))
)
Column(modifier = Modifier.statusBarsPadding()
.fillMaxWidth()
.fillMaxHeight().padding(20.dp,40.dp),
verticalArrangement = Arrangement.Bottom) {
Text(text = "this is Test",modifier = Modifier.padding(0.dp,30.dp))
}
}
You can prevent part of the view from being drawn with Modifier.drawWithContent and DrawScope.clipRect. Using this method, you can create the following modifier:
fun Modifier.drawWithoutRect(rect: Rect?) =
drawWithContent {
if (rect != null) {
clipRect(
left = rect.left,
top = rect.top,
right = rect.right,
bottom = rect.bottom,
clipOp = ClipOp.Difference,
) {
this#drawWithContent.drawContent()
}
} else {
drawContent()
}
}
Use it like this:
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Image(
painterResource(id = R.drawable.my_image),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier.fillMaxSize()
)
var textCoordinates by remember { mutableStateOf<Rect?>(null) }
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(Color(0xFF37C7D7).copy(alpha = 0.6f))
)
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.drawWithoutRect(textCoordinates)
.padding(20.dp, 30.dp)
.border(
width = 0.8.dp,
color = Color.White.copy(alpha = 0.5f),
shape = RoundedCornerShape(32.dp)
)
)
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.drawWithoutRect(textCoordinates)
.padding(28.dp, 38.dp)
.border(
width = 0.8.dp,
color = Color.White.copy(alpha = 0.5f),
shape = RoundedCornerShape(28.dp)
)
)
Column(
verticalArrangement = Arrangement.Bottom,
modifier = Modifier
.statusBarsPadding()
.padding(20.dp, 40.dp)
.onGloballyPositioned {
textCoordinates = it.boundsInParent()
}
.align(Alignment.BottomStart)
) {
Text(text = "this is Test", modifier = Modifier.padding(0.dp, 30.dp))
}
}
Result:
I have a Column and a LazyColumn with the following code:
Column {
LazyColumn(
modifier = Modifier.weight(1f),
contentPadding = PaddingValues(top = 8.dp)
) {
items(items = items) { it ->
TodoRow(
todo = it,
onItemClicked = { onRemoveItem(it) },
modifier = Modifier.fillParentMaxWidth()
)
}
}
}
What I would like to obtain is the following:
To have a Box() with a Text() that explains that we are at the "Top of the List";
This Box() with Text() can`t be fixed. That is, it fades away as the list turns bigger and occupies all the screen. If we scroll up the Text() appears again.
For the Box with a Text I have the following code:
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.LightGray),
contentAlignment = Alignment.Center
)
{ Text("Top of the List", modifier = Modifier.padding(vertical = 8.dp)) }
If I put this Box as a child of the above Column before the LazyColumn, this Box will be fixed, and I don`t want that.
So, where shall I put this Box? Can you help me?
Thanks
You can read scroll position from the list state. Depending on the position you can calculate progress for your "animation".
Column {
val listState = rememberLazyListState()
val topOfListProgress: Float by remember {
derivedStateOf {
listState.run {
if (firstVisibleItemIndex > 0) {
0f
} else {
layoutInfo.visibleItemsInfo
.firstOrNull()
?.let {
1f - firstVisibleItemScrollOffset.toFloat() / it.size
} ?: 1f
}
}
}
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.LightGray)
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val offset = (placeable.height * (1f - topOfListProgress)).roundToInt()
layout(placeable.width, placeable.height - offset) {
placeable.place(0, -offset)
}
}
.alpha(topOfListProgress)
) {
Text("Top of the List", modifier = Modifier.padding(vertical = 8.dp))
}
LazyColumn(
state = listState,
contentPadding = PaddingValues(top = 8.dp),
modifier = Modifier
.weight(1f)
) {
items(100) {
Text(it.toString())
}
}
}
Result:
I have a Column component (SettingsGraphicSelectComposeView.kt) which needs to have a weight of 1f (modifier.weight(weight = 1f)) so that multiple of this component in a container, would be distributed evenly. Problem is that when upgrading Compose from alpha02 to alpha06, assigning the above modifier to a Column is no longer a possibility.
Here is the components code:
#Composable
fun SettingsGraphicSelectComposeView(
modifier: Modifier = Modifier,
textStyles: TextStyles = KoinJavaComponent.inject(TextStyles::class.java).value,
viewModel: ItemViewModel.GraphicSelect
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
modifier = modifier.weight(weight = 1f) // <-- in alpha06 this gives an error
) {
Image(
asset = vectorResource(id = viewModel.imageId)
)
Text(
modifier = modifier
.padding(
start = dimensionResource(id = R.dimen.spacingL),
top = dimensionResource(id = R.dimen.spacingL),
end = dimensionResource(id = R.dimen.spacingL),
bottom = dimensionResource(id = R.dimen.spacingS)
),
text = viewModel.caption,
style = textStyles.TitleSmall.merge(TextStyle(color = colorResource(id = R.color.tint_secondary)))
)
RadioButton(
selected = viewModel.selected,
onClick = viewModel.action,
colors = RadioButtonConstants.defaultColors(
selectedColor = colorResource(R.color.brand),
unselectedColor = colorResource(R.color.tint_secondary)
)
)
}
}
This component is placed in the following compose view:
Surface(color = colorResource(id = R.color.background)) {
Column(
modifier = Modifier
.fillMaxHeight()
) {
items.value?.let { items ->
val switchers = items.filter { it.viewModel is ItemViewModel.Switcher }
val graphicSelects = items.filter { it.viewModel is ItemViewModel.GraphicSelect }
if (switchers.isNotEmpty()) {
val autoSwitcher = switchers[0]
SettingsSwitcherComposeView(viewModel = autoSwitcher.viewModel as ItemViewModel.Switcher)
}
Row(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
) {
if (graphicSelects.size > 1) {
val lightSelector = graphicSelects[0]
val darkSelector = graphicSelects[1]
Row(
modifier = Modifier
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.padding(vertical = dimensionResource(id = R.dimen.spacing2XL))
) {
Row {
SettingsGraphicSelectComposeView(
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
}
}
}
}
}
}
}
Should look like this:
But without the weight it looks like this:
It seems like the weight modifier can only attributed to Column if this column is a child of a Row, so a team member suggested replacing the Row elements containing the SettingsGraphicSelectComposeView with Column and it made the weight attribute "legal" if you will, because we're in a RowScope, and that works.
Not sure what to think about that but it works and as Compose is still at alpha07 right now (new version since I made the post), things might change again in the future.
Here is the change:
Row(
modifier = modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.fillMaxWidth()
) {
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.light.viewModel as ItemViewModel.GraphicSelect
)
}
Spacer(
modifier = Modifier
.padding(top = dimensionResource(id = R.dimen.spacing2XL))
.preferredWidth(1.dp)
.preferredHeight(160.dp) // TODO: Find a way to make this max AUTO height, not fixed
.background(color = colorResource(R.color.highlight))
)
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.dark.viewModel as ItemViewModel.GraphicSelect
)
}
}
Try using Modifier.weight(1f) in your Row itself, so it takes Modifier.weight() from RowScope:
Row {
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
)
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
)
}
Here is the solution : Modifier --> "Arrangement.SpaceEvenly"
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
)
If you wants to give bigger space to any specific Row item, you can handle it dynamically by using your own condition :
For eg :
modifier=Modifier.then(Modifier.weight(if(myItem == index) 2f else 1f))
**'OR'**
modifier=Modifier.then(Modifier.weight(if(myItem .title=="GOLF") 1.5f else 1f))