Hello :wave: Is there a way to use HorizontalPage...
# compose
k
Hello 👋 Is there a way to use HorizontalPager without the lazy loading? Basicly i have a use case where all my items have to have the height of the tallest item in pager (height of items is dynamic, depends on data i provide to the item), but compose prohibits the use of
Copy code
IntrinsicSize.Max()
in lazy components. (Understandably) is there an easy way to work around this or do i have to do some magic with SubcomposeLayout / Create quasi non lazy pager with Row composable?
s
I’d give it a shot with a normal row, and SnapFlingBehavior https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior#SnapFlingBehavior[…]mpose.ui.unit.Dp) to get the feeling of a pager as you swipe through, I think that is what it’s for. I do wonder however if the new Swipable APIs are also a good fit for this, I remember this discussion https://kotlinlang.slack.com/archives/CJLTWPH7S/p1680170260342599 in the past. It seems like it’s part of 1.6.0-alphas here https://developer.android.com/jetpack/androidx/releases/compose-foundation#version_16_2, it’s called
AnchoredDraggable
it seems.
k
thanks for the fast response 😄 i guess ill create my own pager then. SnapFlingBehavior should suit my needs, i dont need anything fancy just a feel of pager as you stated. thanks again 🙂
ill look into AnchoredDraggable but since its still alpha i wouldnt really rely on it yet
s
Yeah, that sounds like a good idea. If you make it work, I’d really be interested to hear what worked for you in the end 😊
k
Sure thing! :)
a
Here's my non-lazy horizontal pager implementation. Set
offscreenLimit
to cover all the pages and you should be able to achieve what you want.
😲 1
🤯 2
thank you color 1
k
Thank you! Ill try to make some basic implementation with snap behaviour but ill give your implementation a try as well. Much appreciated. You sure did put alot of work into it i see
👍 1
s
That’s one super helpful snippet right there 😅 Thanks a lot for sharing Albert!
👍 1
c
FWIW, Accompanist HorizontalPager used to have it's implementation built in a non-lazy way. Whenever I need it I've copied that into my app. /shrug
Stylianos' suggestion is what I'd probably try first tho
s
Well Albert literally has a full blown solution here, even supporting accessibility properly and everything, so if it's a serious project I'd definitely go with that first.
😄 1
a
there is a
beyondBoundsPageCount
param on
HorizontalPager
from compose:foundation. but keep in mind that loading a lot of pages at once will affect your performance. sometimes a more reasonable approach is to react on size changes nicely, like with having animateContentSize modifier on Pager, or something like this
l
I'm dropping my simple solution using
SubcomposeLayout
and the regular, lazy
HorizontalPager
here, in case someone finds this thread:
Copy code
fun OnboardingPager(onboardingCards: List<OnboardingCardState>) {
    val pagerState = rememberPagerState(pageCount = { onboardingCards.size })
    var maxCardHeight by remember { mutableStateOf(0.dp) }
    val pagerContentPadding = 16.dp

    // Measure the maximum height of all cards
    SubcomposeLayout { constraints ->
        val measuredHeights = onboardingCards.map { card ->
            val placeables = subcompose(card) {
                OnboardingCard(
                    onboardingCard = card,
                    modifier = Modifier.padding(horizontal = pagerContentPadding)
                )
            }.map { it.measure(constraints) }

            placeables.maxOf { it.height.toDp() }
        }

        maxCardHeight = measuredHeights.maxOrNull() ?: 0.dp
        layout(0, 0) {}
    }

    HorizontalPager(
        state = pagerState,
        pageSpacing = 16.dp,
        verticalAlignment = Alignment.Top,
        contentPadding = PaddingValues(horizontal = pagerContentPadding),
    )
    { page ->
        OnboardingCard(
            onboardingCard = onboardingCards[page],
            modifier = Modifier.height(maxCardHeight)
        )
    }
        // ...
SubcomposeLayout
just measures all the `OnboardingCard`s here, which are displayed in
HorizontalPager
. It is important to pass the same constraints to the `OnboardingCard`s inside the
SubcomposeLayout
, that's why I pass the
pagerContentPadding
both to the card in the
SubcomposeLayout
and to the
HorizontalPager
itself. Otherwise the card will assume it has more horizontal space and will cut off the content of the highest pages in the
HorizontalPager
.
SubcomposeLayout
is executed during layout phase, after measuring and before placing items. It doesn't draw anything on the screen. It's sole purpose is if you need to measure some Composables (e.g. here the
OnboardingCard
) to pass the result to some other Composable (here it is passed to the
OnboardingCard
inside the
HorizontalPager
in the
Modifier.height(maxCardHeight)
).
a
it is very inefficient as we compose and measure all the pages multiple times. you can achieve this behavior more efficiently by just passing an itemsCount as beyondBoundsPageCount for HorizontalPager
l
How do I set the page height then to be the same for all pages?
Just setting the
itemsCount
to
beyondViewportPageCount
didn't fix the problem
I want to set the page height to be all equal
Not that the
HorizontalPager
height doesn't change
This is easy, but doesn't solve the problem
Or can I then set
Modifier.height(Intrinsics.Max)
to the pages, because they all will be composed eagerly then?
So like this:
Copy code
HorizontalPager(
    // ...
    beyondViewportPageCount = itemsCount
    // ...
) { page ->
    Page(Modifier.height(IntrinsicsSize.Max))
}
a
ok, I agree. beyondBoundsPageCount is helpful to set the size of the parent to the max height. but to also size each page for this max we need to use intrinsics, and there is no api for that for now. we track this request in https://issuetracker.google.com/issues/259686541. it mentions grids, but the same is true for all the lazy layouts. feel free to mention your case there as a comment
l
Thank you!
534 Views