expand Image as much as screen size jetpack compose - android-jetpack-compose

I have a LazyColumn and some childs in it like below
LazyColumn(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
item {
Child(
modifier = Modifier,
firstImage = fakeImage,
secondImage = fakeImage,
onImageClick = {}
)
}
item {
Child(
modifier = Modifier,
firstImage = fakeImage,
secondImage = fakeImage,
onImageClick = {}
)
}
}
here is what is inside of TwoPiecesLayout
#ExperimentalMaterialApi
#Composable
fun Child(
modifier: Modifier,
firstImage: Image,
secondImage: Image,
onImageClick: (Image) -> Unit
) {
val height = (LocalConfiguration.current.screenWidthDp / 2) - 56
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
ImageCell(
modifier = Modifier
.weight(
weight = 1F
)
.height(
height = height.dp
),
image = firstImage,
onImageClick = {
onImageClick(firstImage)
}
)
ImageCell(
modifier = Modifier
.weight(
weight = 3F
)
.height(
height = height.dp
),
image = secondImage,
onImageClick = {
onImageClick(secondImage)
}
)
}
}
when every of Images in Child clicked I want to expand their size as much as screen's size just like the material design choreography
https://storage.cloud.google.com/non-spec-apps/mio-direct-embeds/videos/FADE.mp4
how can I do this?

This is not just for image, with basically any Composable, you can apply this method
var expanded by remember { mutableStateOf (false) }
val animF by animateFloatAsState(
initialState = 0.25f,
targetState = if (expanded) 1f else 0.25f
)
MyComposable(
Modifier.fillMaxSize(animF) // pass the animated Fraction here
.clickable { expanded = !expanded }
)
This will oscillate between 0.25 of the entire screen to 1f of the same, upon clicking the Composable.
See? Super-easy, barely an inconvenience.

Related

Resizing text dynamically in LazyColumn depending on scroll - Jetpack Compose

I want to make the text to be resizing dynamically when I scroll and them to stay that size when I stop scrolling.
For example here the number 4 to be 20.sp and the numbers 3 and 5 to be 18.sp (one index away) and 2 - 6 to be 16.sp and so on. I think that could be created with
Modifier.scale(), but have not found a way for now.
#OptIn(ExperimentalSnapperApi::class)
#Composable
fun Example() {
val listStateHour = rememberLazyListState()
val hourTexts: List<String> = (1..23).map { it.toString().padStart(2, '0') }
Box(
modifier = Modifier.background(color = Color.LightGray).fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.height(32.dp)
.fillMaxWidth()
.background(color = Color.Black.copy(0.2f)),
) {}
LazyColumn(
modifier = Modifier.height(200.dp),
state = listStateHour,
horizontalAlignment = Alignment.Start,
contentPadding = PaddingValues(vertical = 116.dp),
flingBehavior = rememberSnapperFlingBehavior(lazyListState = listStateHour)
) {
itemsIndexed(hourTexts) { index, item ->
Box(
modifier = Modifier.height(32.dp),
contentAlignment = Alignment.Center
) {
Text(
text = item,
color = Color.Black,
fontSize = 20.sp
)
}
}
}
}
}
External dependencies that I am using -> implementation 'dev.chrisbanes.snapper:snapper:0.3.0'
You can observe listStateHour.firstVisibleItemIndex and calculate a scaling factor depending on the distance an item has to that value.
I didn't test it with the flingBehaviour you're using but this might help you:
val listStateHour = rememberLazyListState()
val firstVisibleItemIndex by remember { derivedStateOf { listStateHour.firstVisibleItemIndex } }
val hourTexts: List<String> = (1..23).map { it.toString().padStart(2, '0')
Box(
modifier = Modifier
.background(color = Color.LightGray)
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.height(32.dp)
.fillMaxWidth()
.background(color = Color.Black.copy(0.2f)),
) {}
LazyColumn(
modifier = Modifier.height(200.dp),
state = listStateHour,
horizontalAlignment = Alignment.Start,
contentPadding = PaddingValues(vertical = 116.dp)
) {
itemsIndexed(hourTexts) { index, item ->
Box(
modifier = Modifier.height(32.dp),
contentAlignment = Alignment.Center
) {
Text(
text = item,
color = Color.Black,
fontSize = 20.sp,
modifier = Modifier.scale(
scaleText(
firstVisibleIndex = firstVisibleItemIndex,
itemIndex = index
)
)
)
}
}
}
}
fun scaleText(firstVisibleIndex: Int, itemIndex: Int): Float {
val distance = abs( firstVisibleIndex - itemIndex)
return 1f - distance * .2f
}

BottomNavigationItems padding

Is there any way to remove this padding from the BottomNavigationItem?
Image
If I have very large text, I have to use ResponsiveText to manage this, but that's not what I intended. What I need is that it doesn't have that side padding/margin, both on the left and on the right, in order to occupy as much space as possible.
My code:
#Composable
fun BottomNavBar(
backStackEntryState: State<NavBackStackEntry?>,
navController: NavController,
bottomNavItems: List<NavigationItem>
) {
BottomNavigation(
backgroundColor = DarkGray.copy(alpha = 0.6f),
elevation = Dimen0,
modifier = Modifier
.padding(Dimen10, Dimen20, Dimen10, Dimen20)
.clip(RoundedCornerShape(Dimen13, Dimen13, Dimen13, Dimen13))
) {
bottomNavItems.forEach { item ->
val isSelected = item.route == backStackEntryState.value?.destination?.route
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = item.icon.orZero()),
contentDescription = stringResource(id = item.title)
)
},
label = {
ResponsiveText(
text = stringResource(id = item.title),
textStyle = TextStyle14,
maxLines = 1
)
},
selectedContentColor = Color.White,
unselectedContentColor = Color.White,
alwaysShowLabel = true,
selected = isSelected,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route = route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
},
modifier = if (isSelected) {
Modifier
.clip(RoundedCornerShape(Dimen13, Dimen13, Dimen13, Dimen13))
.background(color = DarkGray)
} else {
Modifier.background(color = Color.Unspecified)
}
)
}
}
}
Apparently this is currently (I am using compose version '1.2.0-rc03') not possible when using BottomNavigation, as there is padding set for each element in these lines:
.padding(horizontal = BottomNavigationItemHorizontalPadding)
Here is what is said about this value:
/**
* Padding at the start and end of a [BottomNavigationItem]
*/
private val BottomNavigationItemHorizontalPadding = 12.dp
[Solution]
Just copy BottomNavigation from androidx and remov this line:
.padding(horizontal = BottomNavigationItemHorizontalPadding)
However, it is necessary that the first and last elements still have padding, so add the innerHorizontalPaddings parameter to the your CustomBottomNavigation constructor
There are a few more changes, you can see the full code of my CustomBottomNavigation here
Example of usage:
CustomBottomNavigation(
...,
innerHorizontalPaddings = 12.dp
) {
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(...)
},
label = {
Text(
...
softWrap = false,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 2.dp)
)
},
...
)
}
}
Another solution is to wrap the label in a BoxWithConstraints and draw outside of it:
BottomNavigationItem(
label = {
/**
* Because of [BottomNavigationItemHorizontalPadding] (12.dp), we need to
* think (and draw) outside the box.
*/
BoxWithConstraints {
Text(
modifier = Modifier
.wrapContentWidth(unbounded = true)
.requiredWidth(maxWidth + 24.dp), // 24.dp = the padding * 2
text = "Centered text and clipped at the end if too long",
softWrap = false,
textAlign = TextAlign.Center
)
}
},
...
)
To get a little bit of padding, you can set requiredWidth(maxWidth + 18.dp).
With this solution, you don't need to copy the enire BottomNavigation :)

HorizontalPager detect swipe offset

I've a HorizontalPager with 3 items.
I need to know exact offset according to user's swipe
HorizontalPager(
count = 3
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = ""
)
}
In Compose many views have some state parameter, using which you can check its state.
In case of pager, it the state has currentPage and currentPageOffset info. To calculate final offset you also need to check view size with Modifier.onSizeChanged or BoxWithConstraints.
I'm using derivedStateOf to prevent redundant recompositions.
val pagerState = rememberPagerState()
val (pagerWidth, setPagerWidth) = remember { mutableStateOf<Int?>(null) }
val scrollOffset by remember(pagerWidth) {
derivedStateOf {
if (pagerWidth == null) return#derivedStateOf 0f
(pagerState.currentPage + pagerState.currentPageOffset) * pagerWidth
}
}
HorizontalPager(
state = pagerState,
count = 3,
modifier = Modifier
.onSizeChanged {
setPagerWidth(it.width)
}
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = ""
)
}
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "",
modifier = Modifier
.fillMaxWidth()
.offset(x = with(LocalDensity.current) { -scrollOffset.toDp() })
)

How to make a lazycolumn scroll to the end when using bottomsheetscaffold?

I am going to design a layout with a bottomsheetscaffold with sheetPeekHeight to be 100 dp in order to show the sheet content. I also need to put a lazyColumn for the main content of the bottomsheetscaffold. But when the lazy column scrolls to the end, the final item will be behind the bottom sheet. How can I make the final item of the column be above the sheet?
Here is the code for the bottom sheet:
#ExperimentalMaterialApi
#Composable
fun HomeScreen() {
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
val coroutineScope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
Text("this is test", modifier = Modifier.fillMaxWidth().height(60.dp))
},
sheetPeekHeight = 100.dp,
sheetShape = RoundedCornerShape(topEnd = 52.dp, topStart = 52.dp),
backgroundColor = Color.White
) {
MainContent()
}
}
#Composable
fun MainContent() {
LazyColumn {
items(count = 5) { itemIndex ->
when (itemIndex) {
0 -> {
Image(modifier = Modifier
.fillMaxWidth()
.height(100.dp), contentDescription = "test",
painter = painterResource(id = R.drawable.image))
}
}
}
}
}
Spacer(modifier=Modifier.height(100.dp)) I think fits better than a box here.
In your case it is easier to use fixed height, but if your content is dynamic you can also calculate bottomSheet height based on screenheight - bottomSheetOffset
fun YourComposable{
...
val bottomSheetHeight =
configuration.screenHeightDp.dp - bottomSheetScaffoldState.bottomSheetState.offset.value.pxToDp
...
}
private val Float.pxToDp: Dp
get() = (this / Resources.getSystem().displayMetrics.density).dp
As a workaround, I added an empty box with the same height as peekheight to the end of the lazycloumn. But I'm still interested in a better solution.

Grid Layout with equally height rows in Jetpack Compose

Jetpack Compose:
I want to create a grid layout like UI with equally height rows, but I can't find a function for getting the current usable screen size (The application should look like that)
How the result should look like:
Have a look at the link mentioned above
What I have tried:
I tried to use Modifier.fillMaxHeight() inside of LazyVerticalGrid for generating equally sized rows, but it didn't work. Beside of that, I also had the idea of setting the height manually for every row item, but I couldn't find a function for getting the usable screen size in jetpack compose (I then would have divided the screen height by the number of rows).
val numbers = (0..34).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(7)
) {
items(numbers.count()) {
Column(horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "#"+numbers.get(it))
}
}
}
You can use the aspectRatio modifier:
LazyVerticalGrid(
columns = GridCells.Adaptive(100.dp),
) {
items(100) {
Box(
modifier = Modifier.aspectRatio(1f)
) { }
}
}
Full example:
#Composable
fun Body(modifier: Modifier) {
Column (
modifier = modifier,
) {
Text(
text = "Without aspect ratio",
modifier = Modifier.padding(12.dp),
style = MaterialTheme.typography.h5,
)
LazyVerticalGrid(
columns = GridCells.Adaptive(100.dp),
modifier = Modifier.weight(1f),
) {
items(100) {
Surface (
modifier = Modifier
.padding(12.dp),
color = Color(0xff2187bb),
) {
RandomSizeBox()
}
}
}
Divider()
Text(
"With aspect ratio",
modifier = Modifier.padding(12.dp),
style = MaterialTheme.typography.h5,
)
LazyVerticalGrid(
columns = GridCells.Adaptive(100.dp),
modifier = Modifier.weight(1f),
) {
items(100) {
Surface (
modifier = Modifier
.aspectRatio(1f)
.padding(12.dp),
color = Color(0xffbb2187),
) {
RandomSizeBox()
}
}
}
}
}
#Composable
fun RandomSizeBox() {
Box(modifier = Modifier.height((10 + Math.random() * 90).dp))
}
For what it's worth, you can measure screen height this way:
val screenHeight = LocalConfiguration.current.screenHeightDp
But it might be worth your making your grid scrollable using a scroll modifier:
https://developer.android.com/jetpack/compose/gestures#scroll-modifiers
You can use subcompose layout to precalculate the max height of the biggest view, and then use that height for all of the LazyVerticalGrid items.
For example:
How does this work?
SubcomposeLayout gives you constraints, i.e. the max width and height the view could be. You can use this to calculate how many views will fit for a given min width. I will use 128 dp:
val longestText = exampleStringList.maxBy { it.length }
val maxWidthDp = constraints.maxWidth/1.dp.toPx()
val width = maxWidthDp/floor(maxWidthDp/128)
Then you can measure the height of a test view, in my case CategoryButton passing the calculated width from above to the modifier:
val measuredHeight = subcompose("viewToMeasure") {
CategoryButton(
longestText,
modifier = Modifier.width(width.dp)
)
}[0]
.measure(Constraints()).height.toDp()
Next you compose your actual LazyVerticalGrid, ensuring that the items use the height you calculated above:
val contentPlaceable = subcompose("content") {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
items(DataStore.categories) { category ->
CategoryButton(
category,
onClick = { onCategorySelected(category) },
modifier = Modifier.height(measuredHeight)
)
}
}
}[0].measure(constraints)
And finally, layout the LazyVerticalGrid:
layout(contentPlaceable.width, contentPlaceable.height) {
contentPlaceable.place(0, 0)
}
Full code:
SubcomposeLayout { constraints ->
val longestText = DataStore.categories.maxBy { it.length }
val maxWidthDp = constraints.maxWidth/1.dp.toPx()
val width = maxWidthDp/floor(maxWidthDp/128)
val measuredHeight = subcompose("viewToMeasure") {
CategoryButton(
longestText,
modifier = Modifier.width(width.dp)
)
}[0]
.measure(Constraints()).height.toDp()
val contentPlaceable = subcompose("content") {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
items(DataStore.categories) { category ->
CategoryButton(
category,
onClick = { onCategorySelected(category) },
modifier = Modifier.height(measuredHeight)
)
}
}
}[0].measure(constraints)
layout(contentPlaceable.width, contentPlaceable.height) {
contentPlaceable.place(0, 0)
}
}
there is another perhaps simple way to do it. You could keep tab of which item is the highest at the moment and update all the other one accordingly. Here is an example:
var desiredItemMinHeight by remember {
mutableStateOf(0.dp)
}
val density = LocalDensity.current
LazyVerticalGrid(
modifier = Modifier.fillMaxSize(),
columns = GridCells.Fixed(count = 2),
) {
items(state.items) {
ItemBox(
modifier = Modifier
.onPlaced {
with(density) {
if (desiredItemMinHeight < it.size.height.toDp()) {
desiredItemMinHeight = it.size.height.toDp()
}
}
}
.defaultMinSize(minHeight = desiredItemMinHeight),
)
}
}

Resources