Увеличить элемент 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)
)
}
}