Увеличить элемент Lazycolumn

Я пытаюсь приблизить элемент в ленивом столбце. Я пробовал разные способы, но у всех есть проблема, и я не знаю, как ее исправить. С первой попытки я попытался масштабировать и определить жест по изображению, но потерял возможность прокручивать список, и второй элемент перекрывался. Во второй попытке я поместил изображение в коробку, чтобы оно увеличивалось при увеличении, а изображение адаптировалось к рамке. Теперь второй элемент не перекрывается при масштабировании, но я не могу прокручивать.

Есть ли способ масштабировать и прокручивать список, не перекрывая элементы?

Спасибо

Попробуйте 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 = ""
                )
            }


        }
    }

Попробуйте 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 = ""
                    )
                }


            }
        }
    }

2 ответа

Это результат, который вы получите с ответом ниже

Каждый класс должен содержать значение масштабирования, чтобы не масштабировать каждый элемент в списке с фиксированным числом.

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

В приведенном ниже ответе масштаб рассчитывается только тогда, когда пользователь касается изображения двумя пальцами/указками, и, поскольку у вас не было перевода, я имею в виду движущееся изображение, я не добавлял ничего, но я могу, например, когда масштаб не равен 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 })
                            }
                        }
                    }
            )
        }
    }
}

Давайте разберем, как работают сенсорные события, здесь есть подробный ответ , а также у меня есть учебник, подробно описывающий жесты здесь.

Базовый процесс ВНИЗ, ДВИЖЕНИЕ и ВВЕРХ можно суммировать как

      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
            }
        }
}

Вы можете получить первый вниз сawaitFirstDown()и перемещайте детали события с помощьюawaitPointerEvent(). Потребление в основном говорит о других событиях pointerInput выше или в родительском или других жестах, таких как прокрутка, чтобы сказать «стоп», событие выполняется здесь.

Также порядок модификаторов имеет значение дляModifier.graphicsLayer{}иModifier.pointerInput()слишком. Если вы не поместите графический слой перед pointerInput при изменении масштаба, поворота или перевода, эти изменения не будут отражены в Modifier.pointerInput(), если только вы не используете Modifier.pointerInput(масштабирование, перевод, вращение), такие как параметры, и они будут сброшены этот модификатор при каждой рекомпозиции, поэтому, если вам явно не нужны исходные результаты 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",
    )
)

обнаруживает не только масштабирование, но и жесты перетаскивания. И чтобы правильно определить перетаскивание, он использует событие, которое предотвращает его получение при прокрутке.

Вы можете создать жест только для масштабирования, который будет использовать события указателя только во время масштабирования. я взял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 })
        }
    }
}

Чтобы решить проблему перекрытия,Modifier.zIndexможет быть использован. В моем примере я просто использую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)
        )
    }
}
Другие вопросы по тегам