简体   繁体   English

Zoom Lazycolumn 项目

[英]Zoom Lazycolumn item

I am trying to zoom to an item in a Lazycolumn.我正在尝试放大到 Lazycolumn 中的项目。 I have tried various ways but they all have a problem and I don't know how to fix it.我尝试了各种方法,但它们都有问题,我不知道如何解决。 On the first try, I tried to scale and detect the gesture from the image but lose the ability to scroll the list and the second item overlaps.在第一次尝试中,我尝试缩放并检测图像中的手势,但失去了滚动列表的能力,并且第二项重叠。 On the second try, I placed the image inside a Box so that it would grow when zoomed in and the image would adapt to the box.在第二次尝试中,我将图像放在 Box 中,以便在放大时它会变大,并且图像会适应框。 Now the second item doesn't overlap when zoom but I can't scroll.现在第二个项目在缩放时不会重叠,但我无法滚动。

Is there a way to zoom and scroll in a list without overlapping the items?有没有办法在不重叠项目的情况下缩放和滚动列表?

Thanks谢谢

Try 1试试 1


var scale = remember { mutableStateOf(1f) }

    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
    ) {
        items(items = list) { item ->

            if (item != null) {

                Image(
                    modifier = Modifier
                        .fillMaxSize()
                        .pointerInput(Unit) {
                            detectTransformGestures { _, _, zoom, _ ->
                                scale.value *= zoom

                            }
                        }
                        .graphicsLayer {
                            scaleX = maxOf(1f, minOf(3f, scale.value));
                            scaleY = maxOf(1f, minOf(3f, scale.value))
                        }, bitmap = item, contentDescription = ""
                )
            }


        }
    }

Try 2试试 2


var scale = remember { mutableStateOf(1f) }



    Box(modifier = Modifier
        .fillMaxSize()
        .graphicsLayer {
            scaleX = maxOf(1f, minOf(3f, scale.value));
            scaleY = maxOf(1f, minOf(3f, scale.value))
        }) {


        LazyColumn(
             modifier = Modifier
                .fillMaxSize()
        ) {
            items(items = list) { item ->

                if (item != null) {

                    Image(
                        modifier = Modifier
                            .fillMaxSize().pointerInput(Unit) {
                                detectTransformGestures { _,_, zoom, _ ->
                                    scale.value *= zoom

                                }
                            }, bitmap = item, contentDescription = ""
                    )
                }


            }
        }
    }

detectTransformGestures detects not only zoom, but also drag gestures. detectTransformGestures不仅可以检测缩放,还可以检测拖动手势。 And to detect a drag correctly, it consumes the event which prevents scroll view from receiving it.为了正确检测拖动,它会消耗阻止滚动视图接收它的事件。

You can create zoom only gesture, which will consume pointer events only during zoom.您可以创建仅缩放手势,该手势仅在缩放期间使用指针事件。 I've took detectTransformGestures source code as a base and remove all non-zoom code:我以detectTransformGestures源代码为基础并删除了所有非缩放代码:

suspend fun PointerInputScope.detectZoom(
    onGesture: (zoom: Float) -> Unit,
) {
    forEachGesture {
        awaitPointerEventScope {
            var zoom = 1f
            var pastTouchSlop = false
            val touchSlop = viewConfiguration.touchSlop

            awaitFirstDown(requireUnconsumed = false)
            do {
                val event = awaitPointerEvent()
                val canceled = event.changes.fastAny { it.isConsumed }
                if (!canceled) {
                    val zoomChange = event.calculateZoom()

                    if (!pastTouchSlop) {
                        zoom *= zoomChange

                        val centroidSize = event.calculateCentroidSize(useCurrent = false)
                        val zoomMotion = abs(1 - zoom) * centroidSize

                        if (zoomMotion > touchSlop) {
                            pastTouchSlop = true
                        }
                    }

                    if (pastTouchSlop) {
                        if (zoomChange != 1f) {
                            onGesture(zoomChange)
                            event.changes.fastForEach {
                                if (it.positionChanged()) {
                                    it.consume()
                                }
                            }
                        }
                    }
                }
            } while (!canceled && event.changes.fastAny { it.pressed })
        }
    }
}

To solve overlapping problem, Modifier.zIndex can be used.为了解决重叠问题,可以使用Modifier.zIndex In my example I just use scale for this value, I expect that in real world you're not gonna zoom more than one item at once - eg you can disable both list scrolling using LazyColumn.userScrollEnabled parameter and other cells zoom detection while one of the items being zoomed.在我的示例中,我只对这个值使用scale ,我希望在现实世界中您不会一次缩放多个项目 - 例如,您可以使用LazyColumn.userScrollEnabled参数禁用列表滚动和其他单元格缩放检测,而其中之一正在缩放的​​项目。

val list = List(10) { "https://picsum.photos/id/${237 + it}/400/400" }

LazyColumn(
    modifier = Modifier
        .fillMaxSize()
) {
    items(items = list) { item ->
        var scale by remember { mutableStateOf(1f) }
        Image(
            painter = rememberAsyncImagePainter(model = item),
            contentDescription = null,
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(1f)
                .pointerInput(Unit) {
                    detectZoom { zoom ->
                        scale *= zoom
                    }
                }
                .graphicsLayer {
                    scaleX = maxOf(1f, minOf(3f, scale));
                    scaleY = maxOf(1f, minOf(3f, scale))
                }
                .zIndex(scale)
        )
    }
}

This is the result you get with the answer below这是您通过以下答案得到的结果

在此处输入图像描述

Each class should hold zoom value to not zoom every item in the list with a fixed number每个类都应保持缩放值,以不以固定数字缩放列表中的每个项目

class Snack(
    val imageUrl: String
) {
    var zoom = mutableStateOf(1f)
}

In the answer below zoom is calculated only when user touches an Image with 2 fingers/pointers and since you didn't have any translating, i mean moving image, i didn't add any but i can for instance when zoom is not 1 when image is touched user can translate position of image.在下面的答案中,缩放仅在用户用 2 个手指/指针触摸图像时计算,并且由于您没有任何翻译,我的意思是移动图像,我没有添加任何内容,但我可以例如当缩放不是 1 时图像被触摸用户可以翻译图像的位置。

@Composable
private fun ZoomableList(snacks: List<Snack>) {

    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
    ) {
        itemsIndexed(items = snacks) { index, item ->

            Image(
                painter = rememberAsyncImagePainter(model = item.imageUrl),
                contentDescription = null,
                contentScale = ContentScale.FillBounds,
                modifier = Modifier
                    .fillMaxWidth()
                    .aspectRatio(1f)
                    .border(2.dp, Color.Blue)
                    .clipToBounds()
                    .graphicsLayer {
                        scaleX = snacks[index].zoom.value
                        scaleY = snacks[index].zoom.value
                    }
                    .pointerInput(Unit) {
                        forEachGesture {
                            awaitPointerEventScope {
                                // Wait for at least one pointer to press down
                                awaitFirstDown()
                                do {

                                    val event = awaitPointerEvent()
                                    // Calculate gestures and consume pointerInputChange
                                    // only size of pointers down is 2
                                    if (event.changes.size == 2) {
                                        var zoom = snacks[index].zoom.value
                                        zoom *= event.calculateZoom()
                                        // Limit zoom between 100% and 300%
                                        zoom = zoom.coerceIn(1f, 3f)
                                        snacks[index].zoom.value = zoom


                                        /*
                                            Consumes position change if there is any
                                            This stops scrolling if there is one set to any parent Composable
                                         */
                                        event.changes.forEach { pointerInputChange: PointerInputChange ->
                                            pointerInputChange.consume()
                                        }
                                    }
                                } while (event.changes.any { it.pressed })
                            }
                        }
                    }
            )
        }
    }
}

Let's break down how touch events work, there is a detailed answer here , and i also have a tutorial that covers gestures in detail here .让我们分解一下触摸事件是如何工作的,这里有一个详细的答案,我也有一个详细介绍手势的教程

A basic DOWN, MOVE, and UP process can be summed as一个基本的 DOWN、MOVE 和 UP 过程可以总结为

val pointerModifier = Modifier
    .pointerInput(Unit) {
        forEachGesture {

            awaitPointerEventScope {
                
                awaitFirstDown()
               // ACTION_DOWN here
               
                do {
                    
                    //This PointerEvent contains details including
                    // event, id, position and more
                    val event: PointerEvent = awaitPointerEvent()
                    // ACTION_MOVE loop

                    // Consuming event prevents other gestures or scroll to intercept
                    event.changes.forEach { pointerInputChange: PointerInputChange ->
                        pointerInputChange.consume()
                    }
                } while (event.changes.any { it.pressed })

                // ACTION_UP is here
            }
        }
}

You can get first down with awaitFirstDown() and move event details with awaitPointerEvent() .您可以先使用awaitFirstDown()并使用awaitPointerEvent() () 移动事件详细信息。 Consuming is basically saying other pointerInput events above or in parent or other gestures such as scroll to say stop, event is done here.消费基本上是说上面或父级中的其他指针输入事件或其他手势,例如滚动说停止,事件在这里完成。

Also order of Modifiers matter for Modifier.graphicsLayer{} and Modifier.pointerInput() too. Modifier.graphicsLayer{}Modifier.pointerInput()的修饰符顺序也很重要。 If you don't place graphics layer before pointerInput when your scale, rotation or translation change these changes won't be reflected to Modifier.pointerInput() unless you use Modifier.pointerInput(zoom, translation, rotation) like params and these will reset this modifier on each recomposition so, unless you explicitly need initial results of Modifier.graphicsLayer put it first.如果您在缩放、旋转或平移更改时未将图形图层放置在指针输入之前,则这些更改将不会反映到 Modifier.pointerInput() 除非您使用 Modifier.pointerInput(zoom, translation, rotation) 之类的参数并且这些将重置这个修饰符在每次重组时都如此,除非您明确需要 Modifier.graphicsLayer 的初始结果,否则将其放在首位。

val snacks = listOf(
    Snack(
        imageUrl = "https://source.unsplash.com/pGM4sjt_BdQ",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/Yc5sL-ejk6U",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/-LojFX9NfPY",
    ),
    Snack(

        imageUrl = "https://source.unsplash.com/AHF_ZktTL6Q",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/rqFm0IgMVYY",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/qRE_OpbVPR8",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/33fWPnyN6tU",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/aX_ljOOyWJY",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/7meCnGCJ5Ms",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/m741tj4Cz7M",
    ),
    Snack(

        imageUrl = "https://source.unsplash.com/iuwMdNq0-s4",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/qgWWQU1SzqM",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/9MzCd76xLGk",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/1d9xXWMtQzQ",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/wZxpOw84QTU",
    ),
    Snack(

        imageUrl = "https://source.unsplash.com/okzeRxm_GPo",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/l7imGdupuhU",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/bkXzABDt08Q",
    ),
    Snack(

        imageUrl = "https://source.unsplash.com/y2MeW00BdBo",
    ),
    Snack(
        imageUrl = "https://source.unsplash.com/1oMGgHn-M8k",
    ),
    Snack(

        imageUrl = "https://source.unsplash.com/TIGDsyy0TK4",
    )
)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM