accompanist icon indicating copy to clipboard operation
accompanist copied to clipboard

Nested HorizontalPager add top / bottom padding for no reason

Open edharkhimich opened this issue 2 years ago • 7 comments

Hello I'm trying to use 2 HorizontalPagers - One inside another. The first one display items correctly but if I duplicate the same logic to the nested one - it starts to add an additional padding on some screens for no reasons

Really need this one asap Appreciate you a lot

`import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState import kotlinx.coroutines.launch

@OptIn(ExperimentalPagerApi::class) @Composable fun TestCard() { val mainPagerState = rememberPagerState()

val mainTitles = listOf(
    "First",
    "Second",
    "Third",
    "Forth",
    "Fifth"
)

val tabIndex = mainPagerState.currentPage
val coroutineScope = rememberCoroutineScope()

Column(Modifier.fillMaxSize()) {
    ScrollableTabRow(
        selectedTabIndex = tabIndex,
        edgePadding = 16.dp,
        indicator = { tabPositions ->
            TabRowDefaults.Indicator(
                modifier = Modifier.pagerTabIndicatorOffset(
                    mainPagerState,
                    tabPositions
                ),
                color = MaterialTheme.colors.primary
            )
        },
        backgroundColor = Color.Transparent
    ) {
        mainTitles.forEachIndexed { index, tabTitle ->
            Tab(
                modifier = Modifier
                    .height(
                        height = 30.dp
                    ),
                selected = tabIndex == index,
                onClick = {
                    coroutineScope.launch {
                        mainPagerState.animateScrollToPage(index)
                    }
                },
                text = {
                    Text(
                        modifier = Modifier
                            .alpha(
                                alpha = if (tabIndex == index) 1f else 0.85f
                            ),
                        text = tabTitle,
                        style = MaterialTheme.typography.h6.copy(
                            fontSize = 14.sp,
                            letterSpacing = -(0.25).sp,
                            color = if (tabIndex == index) Color.Black else Color.Magenta
                        )
                    )
                }
            )
        }
    }
    HorizontalPager(
        modifier = Modifier.fillMaxSize(),
        count = mainTitles.size,
        state = mainPagerState
    ) { index ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {
            when (mainTitles[index]) {
                "First" -> {
                    for (a in 0..2) {
                        Text(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(
                                    16.dp
                                )
                                .background(Color.Green),
                            text = mainTitles[index],
                            style = MaterialTheme.typography.h6
                        )
                    }

                    InnerHorizontalPager()
                }
                "Second" -> {
                    for (a in 0..3) {
                        Text(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(
                                    16.dp
                                )
                                .background(Color.Green),
                            text = mainTitles[index],
                            style = MaterialTheme.typography.h6
                        )
                    }
                }
                "Third" -> {
                    for (a in 0..7) {
                        Text(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(
                                    16.dp
                                )
                                .background(Color.Green),
                            text = mainTitles[index],
                            style = MaterialTheme.typography.h6
                        )
                    }
                }
                "Forth" -> {
                    for (a in 0..15) {
                        Text(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(
                                    16.dp
                                )
                                .background(Color.Green),
                            text = mainTitles[index],
                            style = MaterialTheme.typography.h6
                        )
                    }
                }
                "Fifth" -> {
                    for (a in 0..3) {
                        Text(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(
                                    16.dp
                                )
                                .background(Color.Green),
                            text = mainTitles[index],
                            style = MaterialTheme.typography.h6
                        )
                    }
                }
            }
        }
    }
}

}

@OptIn(ExperimentalPagerApi::class) @Composable fun InnerHorizontalPager() { val additionalTitles = listOf( "First", "Second", "Third", "Forth", "Fifth" )

val coroutineScope = rememberCoroutineScope()
val additionalPagerState = rememberPagerState()
val tabIndex = additionalPagerState.currentPage

ScrollableTabRow(
    selectedTabIndex = tabIndex,
    edgePadding = 16.dp,
    indicator = { tabPositions ->
        TabRowDefaults.Indicator(
            modifier = Modifier.pagerTabIndicatorOffset(
                additionalPagerState,
                tabPositions
            ),
            color = MaterialTheme.colors.primary
        )
    },
    backgroundColor = Color.Transparent
) {
    additionalTitles.forEachIndexed { index, tabTitle ->
        Tab(
            modifier = Modifier
                .height(
                    height = 30.dp
                ),
            selected = tabIndex == index,
            onClick = {
                coroutineScope.launch {
                    additionalPagerState.animateScrollToPage(index)
                }
            },
            text = {
                Text(
                    modifier = Modifier
                        .alpha(
                            alpha = if (tabIndex == index) 1f else 0.85f
                        ),
                    text = tabTitle,
                    style = MaterialTheme.typography.h6.copy(
                        fontSize = 14.sp,
                        letterSpacing = -(0.25).sp,
                        color = if (tabIndex == index) Color.Black else Color.Magenta
                    )
                )
            }
        )
    }
}

HorizontalPager(
    modifier = Modifier
        .background(Color.Blue),
    contentPadding = PaddingValues(0.dp),
    count = additionalTitles.size,
    state = additionalPagerState,
    userScrollEnabled = true
) { index ->
    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {
        when (additionalTitles[index]) {
            "First" -> {
                for (a in 0..2) {
                    Text(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(
                                16.dp
                            )
                            .background(Color.Red),
                        text = additionalTitles[index],
                        style = MaterialTheme.typography.h6
                    )
                }
            }
            "Second" -> {
                for (a in 0..3) {
                    Text(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(
                                16.dp
                            )
                            .background(Color.Green),
                        text = additionalTitles[index],
                        style = MaterialTheme.typography.h6
                    )
                }
            }
            "Third" -> {
                for (a in 0..7) {
                    Text(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(
                                16.dp
                            )
                            .background(Color.Green),
                        text = additionalTitles[index],
                        style = MaterialTheme.typography.h6
                    )
                }
            }
            "Forth" -> {
                for (a in 0..15) {
                    Text(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(
                                16.dp
                            )
                            .background(Color.Green),
                        text = additionalTitles[index],
                        style = MaterialTheme.typography.h6
                    )
                }
            }
            "Fifth" -> {
                for (a in 0..3) {
                    Text(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(
                                16.dp
                            )
                            .background(Color.Green),
                        text = additionalTitles[index],
                        style = MaterialTheme.typography.h6
                    )
                }
            }
        }
    }
}

}

https://user-images.githubusercontent.com/21175086/179075589-ca825df4-580d-4813-881f-f0418b48dcf4.mp4

`

edharkhimich avatar Jul 14 '22 20:07 edharkhimich

Update:

I decided to create my own CustomLayout with Column behaviour

@Composable
fun CustomColumn(
   modifier: Modifier = Modifier,
   content: @Composable () -> Unit
) {
   Layout(
       modifier = modifier,
       content = content,
       measurePolicy = { measurables, constraints ->
           val placeables = measurables.map { measurable ->
               // Measure each children
               measurable.measure(constraints)
           }

           var yPosition = 0

           layout(constraints.maxWidth, constraints.maxHeight) {
               placeables.forEach { placeable ->
                   placeable.placeRelative(x = 0, y = yPosition)

                   yPosition += placeable.height
               }
           }
       }
   )
} ```

The elements locates on the screen as needed but when on the first tab I swipe left - the app crashes with this error:

java_vm_ext.cc:579] JNI DETECTED ERROR IN APPLICATION: JNI NewStringUTF called with pending exception java.lang.IllegalStateException: Unable to create layer for Compose, size 1280x-2147483520 max size 16384 color type 4 has context 1
    java_vm_ext.cc:579] (Throwable with empty stack trace)
    java_vm_ext.cc:579] 
    java_vm_ext.cc:579]     in call to NewStringUTF
2022-07-14 18:42:06.195 22821-22887/com.packagename A/roid.myappname.bet: runtime.cc:669] Runtime aborting...
    runtime.cc:669] Dumping all threads without mutator lock held
    runtime.cc:669] All threads:
    runtime.cc:669] DALVIK THREADS (50):
    runtime.cc:669] "RenderThread" prio=10 tid=50 Runnable
    runtime.cc:669]   | group="" sCount=0 ucsCount=0 flags=0 obj=0x14683b90 self=0x7bfeb17c70
    runtime.cc:669]   | sysTid=22887 nice=-10 cgrp=top-app sched=0/0 handle=0x79efca0cb0
    runtime.cc:669]   | state=R schedstat=( 34993437 3521875 81 ) utm=0 stm=2 core=7 HZ=100
    runtime.cc:669]   | stack=0x79efba9000-0x79efbab000 stackSize=991KB
    runtime.cc:669]   | held mutexes= "abort lock" "mutator lock"(shared held)
    runtime.cc:669]   native: #00 pc 0000000000458f1c  /apex/com.android.art/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+120)
    runtime.cc:669]   native: #01 pc 00000000006f98a8  /apex/com.android.art/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, bool, BacktraceMap*, bool) const+252)
    runtime.cc:669]   native: #02 pc 0000000000701380  /apex/com.android.art/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+304) ```

Someone knows where to dig ?

edharkhimich avatar Jul 15 '22 01:07 edharkhimich

Update:

Most likely the issue is related to .verticalScroll(rememberScrollState()) modifier. When I remove it the issue with extra padding is not happening but the list is not scrolling now.

edharkhimich avatar Jul 18 '22 03:07 edharkhimich

I'm having the exactly same issue, but using a LazyColumn as a parent composable and HorizontalPage is an item which contains 2 tabs with lists each, displaying list items using foreach

shidobecker avatar Jul 18 '22 14:07 shidobecker

I think the issue could be that you have Modifier.verticalScroll(), but then your child Pager has a Column with Modifier.fillMaxSize(). you can't really have both vertical scroll and filling vertical size. So most likely the page with inner pager shouldn't be vertically scrollable, or this modifier should be set on the inner Pager's child, not the outer ones

andkulikov avatar Jul 20 '22 17:07 andkulikov

Could you please provide an example with the workaround solution ? Because I used to try different approaches and it didn't work. If you check this example everything is working fine. https://www.geeksforgeeks.org/nested-scrolling-in-android-using-jetpack-compose/

So it means that I can set vertical scroll in inner and outer widgets.

edharkhimich avatar Jul 21 '22 13:07 edharkhimich

There is no vertical nesting scrolling in your case. You only have Modifier.verticalScroll() set once. And what I am saying is that a child of a component with Modifier.verticalScroll() can't have Modifier.fillMaxHeight() as it makes no sense, verticalScroll is automatically measuring its children with an infinity max height, nothing to fill, so it will fallback to wrapping content

andkulikov avatar Jul 21 '22 13:07 andkulikov

When I'm removing verticalScroll and fillMaxSize modifiers, I have the same padding issue but now in both viewpagers. Seems like it's something else :(

edharkhimich avatar Jul 21 '22 15:07 edharkhimich

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Aug 21 '22 03:08 github-actions[bot]

It's still need to be fixed

edharkhimich avatar Sep 13 '22 19:09 edharkhimich

Can you change the status of this issue to open ?

edharkhimich avatar Oct 11 '22 05:10 edharkhimich

Hey. I still believe it is an issue in how you structured your code. Can you please explain the issue if you fix the one I already mentioned: when you set Modifier.verticalScroll() on a parent (Column in the first Pager) all the subhierarchy can't have Modifier.fillMaxHeight() anymore (Modifier.fillMaxSize is setting both width and height) as it can't fill the infinity

andkulikov avatar Oct 11 '22 11:10 andkulikov