android-jetpack-composeandroid-webview

WebView carousel within a LazyColumn


I have a TrustPilot webview within a LazyColumn but scrolling the carousel horizontally is very glitchy. Any slight vertical movement will be intercepted by the LazyColumn and stop the horizontal scroll of the carousel.

Is there a way to allow the carousel to scroll horizontally without competing with the vertical scroll of the lazy list.

Here is a simple example that easily shows the issue:

Scaffold { padding ->
    LazyColumn(contentPadding = padding) {
        item {
            TrustPilotWidget()
        }
    }
}

Where TrustPilotWidget looks like this:

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun TrustPilotWidget() {
    val language = Locale.getDefault().language

    val bootstrap =
        "<!-- TrustBox script --> <script type=\"text/javascript\" src=\"https://widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js\" strategy=\"lazyOnload\"></script> <!-- End Trustbox script -->"
    val trustBox =
        "<!-- TrustBox widget - Mini Carousel --> " +
                "<div class=\"trustpilot-widget\" data-locale=\"${language}\" " +
                "data-template-id=\"54ad5defc6454f065c28af8b\" " +
                "data-businessunit-id=\"46d6a890000064000500e0c3\" " +
                "data-style-height=\"250px\" " +
                "data-style-width=\"100%\" " +
                "data-theme=\"light\" " +
                "data-review-languages=\"${language}\"" +
                "data-stars=\"5\"> " +
                "<a href=\"https://www.trustpilot.com/review/revelsystems.com\" target=\"_blank\">Trustpilot</a> </div> <!-- End TrustBox widget -->"

    AndroidView(
        factory = {
            WebView(it).apply {
                settings.javaScriptEnabled = true
                setBackgroundColor(android.graphics.Color.TRANSPARENT)
            }
        },
        update = {
            it.loadDataWithBaseURL(
                "https://widget.trustpilot.com",
                bootstrap + trustBox,
                "text/html",
                null,
                null
            )
        }
    )
}

Solution

  • You need have a gesture for checking if user scrolled horizontally and based on that result disable user gesture.

    For a gesture to work you shouldn't consume it and get it even if AnroidView consumes it with

    val down = awaitFirstDown(requireUnconsumed = false)
    

    Then after first touch and move following first touch check whether moved pointer horizontally only for once with

    .pointerInput(Unit) {
        awaitEachGesture {
            val down = awaitFirstDown(requireUnconsumed = false)
    
            val initialPosition = down.position
            var isPositionCheckDone = false
            do {
                val event: PointerEvent = awaitPointerEvent()
    
                event.changes.forEach {
                    if (isPositionCheckDone.not()) {
                        val position = it.position
    
                        val diff = initialPosition.x - position.x
                        isWebViewTouched = diff != 0f
                        isPositionCheckDone = true
                    }
                }
    
            } while (event.changes.any { it.pressed })
    
            isWebViewTouched = false
    
        }
    }
    

    And assign this isWebViewTouched to LazyColumn

    var isWebViewTouched by remember {
        mutableStateOf(false)
    }
    LazyColumn(
        userScrollEnabled = isWebViewTouched.not(),
        contentPadding = padding
    ) {
     // Rest of the code
    }
    

    Result

    enter image description here

    Full code

    @Preview
    @Composable
    fun ScrollTest() {
        Scaffold { padding ->
    
            var isWebViewTouched by remember {
                mutableStateOf(false)
            }
            LazyColumn(
                userScrollEnabled = isWebViewTouched.not(),
                contentPadding = padding
            ) {
                items(5) {
                    Box(
                        modifier = Modifier.fillMaxWidth().height(50.dp).background(Color.Red)
                    )
                }
                item {
                    Box(
                        modifier = Modifier
    .pointerInput(Unit) {
        awaitEachGesture {
            val down = awaitFirstDown(requireUnconsumed = false)
    
            val initialPosition = down.position
            var isPositionCheckDone = false
            do {
                val event: PointerEvent = awaitPointerEvent()
    
                event.changes.forEach {
                    if (isPositionCheckDone.not()) {
                        val position = it.position
    
                        val diff = initialPosition.x - position.x
                        isWebViewTouched = diff != 0f
                        isPositionCheckDone = true
                    }
                }
    
            } while (event.changes.any { it.pressed })
    
            isWebViewTouched = false
    
        }
    }
                    ) {
                        TrustPilotWidget()
                    }
                }
    
                items(10) {
                    Box(
                        modifier = Modifier.fillMaxWidth().height(50.dp).background(Color.Red)
                    )
                }
            }
        }
    }