Closed ShmuelCammebys closed 1 week ago
@ShmuelCammebys Thanks for your feedback! It appears to be expected behavior, and we need to handle it ourselves. Have you tried using Modifier.nestedscroll?
@KevinnZou Yes, I did. It did not appear to have any affect.
@ShmuelCammebys Could you show me the code?
@KevinnZou Here is a minimal reproduction (the image in the HTML is from wikipedia and is very large, so it may take a bit to load):
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@Composable
@OptIn(ExperimentalFoundationApi::class)
fun FullScreenErrorReproduction() {
val scrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
Orientation.Horizontal
)
val titleToScreen = mapOf<String, @Composable () -> Unit>(
"Page 1" to {
CardListRepro(scrollConnection)
},
"Page 2" to {
Column {
Text("a")
Text("a")
Text("a")
}
}
)
Tabs(titleToScreen, scrollConnection)
}
@Composable
private fun CardListRepro(scrollConnection: NestedScrollConnection) {
val html = """<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="Generator" content="Microsoft Word 15 (filtered medium)">
</head>
<body lang="EN-US" link="#467886" vlink="#96607D" style="word-wrap:break-word">
<div class="WordSection1"><p class="MsoNormal">Image:</p>
<p class="MsoNormal"><img width="2112" height="3802" id="Picture_x0020_1"
src="https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg"
alt="A map" style="width:22.0in; height:39.6041in" data="image001.png@01DA7B76.35A2CCB0">
</p>
</div>
</body>
</html>"""
val messages = listOf(
html,
html,
html,
)
val listState = rememberLazyListState()
Column(Modifier.fillMaxSize()) {
LazyColumn(
Modifier.fillMaxWidth().padding(horizontal = 16.dp).weight(1F),
state = listState,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(messages.size) { index ->
messages
.elementAtOrNull(index)
?.let { HtmlCardRepro(it, scrollConnection) }
}
}
}
}
@Composable
private fun HtmlCardRepro(html: String, parentScrollConnection: NestedScrollConnection? = null) {
Column(
Modifier.widthIn(max = 300.dp).padding(bottom = 4.dp),
horizontalAlignment = Alignment.Start
) {
WebView(
html,
(if (parentScrollConnection != null) Modifier.nestedScroll(parentScrollConnection)
else Modifier)
.heightIn(max = 500.dp)
)
}
}
@Composable
private fun WebView(html: String, modifier: Modifier) {
val state = rememberWebViewStateWithHTMLData(data = html)
com.multiplatform.webview.web.WebView(state = state, modifier = modifier)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Tabs(
titleToScreen: Map<String, @Composable () -> Unit>,
scrollConnection: NestedScrollConnection
) {
val state = rememberPagerState(0) { titleToScreen.size }
val scope = rememberCoroutineScope()
TabRow(state.currentPage) {
for ((index, i) in titleToScreen.keys.withIndex()) Tab(
state.currentPage == index,
onClick = {
scope.launch {
state.animateScrollToPage(index)
}
},
text = { Text(i) }
)
}
HorizontalPager(
state,
pageNestedScrollConnection = scrollConnection
) {
titleToScreen.values.elementAt(it)()
}
}
@ShmuelCammebys Thank you for the information! However, I am currently focused on resolving an issue at my company and may not have the time to look it in depth. I did take a quick look and believe that the PagerDefaults.pageNestedScrollConnection
may not be sufficient to handle the nested scroll issue. I think we will need to create a custom nestedScrollConnection that intercepts the scroll of the pager and allows the WebView to scroll first. However, we currently do not have a scroll method available for the WebView. Therefore, you may need to wait until I have the time to add this feature. Alternatively, you are welcome to submit a pull request if you cannot wait.
BTW, I recommend checking out this library link to library for guidance on implementing nested scrolling with a webview inside an outer container.
Was this fixed?
When the WebView is too small to display its contents, it displays scrollbars. However, in my use case, the WebView is embedded in a horizontally scrolling tab pager, and the width has a max value. Horizontal scrolls are stolen by the pager.