How to create grid layout for images in jetpack compose? - android-jetpack-compose

I have tried the below code it shows error at Column
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Image(...)
Image(...)
Image(...) //Like wise eight images
}
}

Like with any other compose lazy view, you need to add items, not just place composables inside.
Check out methods available in LazyGridScope: you can add items by count, enumerate collection or add single item. Check out more in the documentation.
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
items(8) { i ->
Image(...)
}
items(images) { image ->
Image(...)
}
item {
// single item
Image(...)
}
}

You require to add items in LazyVerticalGrid as below.
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
items(images.size) { // ---------> You need to add this
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Image(...)
Image(...)
Image(...) //Like wise images
}
}
}

val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
items(images) { image ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(image.toString())
}
}
}

LazyList and LazyColumn are written in DSL. You cannot use Composables directly inside them. I assume the error is the same old #Composable invocations can only happen.... bla bla bla. So just like lazy column, all the lazy composables offer the item scope, which is actually a Composable scope. Hence if you need to add an item here, you could just
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
item{
Column(horizontalAlignment = Alignment.CenterHorizontally){
Image(...)
Image(...)
Image(...) //Like wise eight images
}
}
}
which would produce a single element in the list, that is, a column with three images. If you instead wanted these images to be the elements, you just need to remove the Column call and do something like
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
items {
Image(...)
Image(...)
Image(...) //Like wise eight images
}
}
This will make them direct children of the Lazy Grid
There is also a better approach
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
items((0..8).toList()) { element ->
Image(...) // Pass in the element here
}
}
In theory, you could also,
val images = (0..8).toList()
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
repeat(8) {
Image(images[it]
}
}

Related

How can I get Scroll percentage of LazyColumn?

I am trying to get the scroll percentage of lazy columns to scroll like 25%, 50%, 100%. After the analysis, I could not find any method to get it.
You can find it based on the visible index since the items are loaded lazily:
#Composable
fun ScrollPercentageLazyColumn() {
val itemsCount = 500
val scrollState = rememberLazyListState()
val formatter = remember { DecimalFormat("0.0") }
val firstVisibleItemIndex = scrollState.firstVisibleItemIndex
val visibleItemsCount = scrollState.layoutInfo.visibleItemsInfo.size
val percent = (firstVisibleItemIndex / (itemsCount - visibleItemsCount).toFloat()) * 100f
val scrollText = "scroll percentage : ${formatter.format(percent)}%"
Box {
LazyColumn(
state = scrollState,
modifier = Modifier.fillMaxSize()
) {
items(itemsCount) {
Text(text = "Item $it", Modifier.padding(12.dp))
}
}
Text(text = scrollText, Modifier.align(Alignment.TopCenter).padding(8.dp))
}
}

Measuring string width to properly size Text() composable

I am working on a Jetpack Compose based app which shows a simple list with 3 columns.
Things are working in principle, but what I am struggling with is to automatically determine the size of the columns.
For example, the width requirement of the date column will differ significantly depending on user's locale settings and font size.
24.12.2021 - 20:00 requires a lot less screen space than
12/14/2021 - 08:00 PM.
What I was hoping I can do is to work with a sample date, measure it up based on current locale settings and font size and then set the width for all list entries accordingly.
Something similar to this:
val d = Date( 2021,12,30 ,23,59,59) // Sample date
val t = dateFormat.format(d) + " - " + timeFormat.format(d) // Build the output string
val dateColumnWidth = measureTextWidth(t, fontSize) // This is what I need
…
LazyColumn {
…
Row {
Text(text = t, Modifier.width(dateColumnWidth.dp), fontSize = fontSize.sp))
Text(text = value)
Text(text = comment)
}
}
…
I have been on this for weeks but a function like my "measureTextWidth" above doesn't seem to exist.
Is there any way to achieve this?
You can use SubcomposeLayout like this:
#Composable
fun MeasureUnconstrainedViewWidth(
viewToMeasure: #Composable () -> Unit,
content: #Composable (measuredWidth: Dp) -> Unit,
) {
SubcomposeLayout { constraints ->
val measuredWidth = subcompose("viewToMeasure", viewToMeasure)[0]
.measure(Constraints()).width.toDp()
val contentPlaceable = subcompose("content") {
content(measuredWidth)
}[0].measure(constraints)
layout(contentPlaceable.width, contentPlaceable.height) {
contentPlaceable.place(0, 0)
}
}
}
Then use it in your view:
MeasureUnconstrainedViewWidth(
viewToMeasure = {
Text("your sample text")
}
) { measuredWidth ->
// use measuredWidth to create your view
}

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.

How do I use Jepack compose to implement a drag sorted list?

I want to implement a drag sorted list, functions like drag-sort-recyclerview/gridview,but use jetpack compose.
use this library it's help-out
and this is an Example of my usage
implementation("org.burnoutcrew.composereorderable:reorderable:0.6.1")
val state = rememberReorderState()
val list=notes.toMutableList()
LazyColumn(
state = state.listState,
modifier = Modifier.reorderable(state, { a, b -> list.move(a, b) })
) {
items(list, { it.id }) { noteIndex ->
Note(
Modifier
.draggedItem(state.offsetByKey(noteIndex.id))
.detectReorderAfterLongPress(state),
note = noteIndex,
onNoteClick = onNoteClick,
onNoteCheckedChange = onNoteCheckedChange
)
}
}

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