I'm trying to implement zooming an image using PointerEvent
(2 fingers) and it's working perfectly (Zooming out of column bounders which is 0.4% of the screen height) when wrapped in a Column
but it's not working as expected if it wrapped in a HorizontalPager.
Code with screenshot for the Column
implementation:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProductImageDialog(
properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest: () -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val coroutineScope = rememberCoroutineScope()
var userScrollEnabled by remember { mutableStateOf(true) }
val pagerState = rememberPagerState { 2 }
Dialog(
properties = properties,
onDismissRequest = onDismissRequest
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Green),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
IconButton(onClick = onDismissRequest) {
Icon(
imageVector = Icons.Default.Close,
tint = Color.White,
contentDescription = null
)
}
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier
.background(color = Color.White)
.fillMaxWidth()
.fillMaxHeight(0.4f),
) {
// items(2) { page ->
Image(
painter = painterResource(id = R.drawable.image_1),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
coroutineScope
.launch {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
when (event.changes.size) {
2 -> {
userScrollEnabled = false
val change1 = event.changes[0]
val change2 = event.changes[1]
val distanceCurrent = calculateDistance(
change1.position,
change2.position
)
val distancePrevious = calculateDistance(
change1.previousPosition,
change2.previousPosition
)
scale *= distanceCurrent / distancePrevious
offset += change1.positionChange()
Log.i(
"TAGTTTTTT",
"distanceCurrent: $distanceCurrent\ndistancePrevious: $distancePrevious\nscale: $scale\noffset: $offset"
)
}
else -> {
userScrollEnabled = true
scale = 1f
offset = Offset.Zero
}
}
}
}
}
}
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
),
contentScale = ContentScale.Inside
)
// }
}
}
}
}
private fun calculateDistance(point1: Offset, point2: Offset): Float {
val dx = point1.x - point2.x
val dy = point1.y - point2.y
return sqrt(dx * dx + dy * dy)
}
Here is the code using HorizontalPager:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProductImageDialog(
properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest: () -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val coroutineScope = rememberCoroutineScope()
var userScrollEnabled by remember { mutableStateOf(true) }
val pagerState = rememberPagerState { 2 }
Dialog(
properties = properties,
onDismissRequest = onDismissRequest
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Green),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
IconButton(onClick = onDismissRequest) {
Icon(
imageVector = Icons.Default.Close,
tint = Color.White,
contentDescription = null
)
}
Spacer(modifier = Modifier.height(8.dp))
HorizontalPager(
modifier = Modifier
.background(color = Color.White)
.fillMaxWidth()
.fillMaxHeight(0.4f),
state = pagerState,
userScrollEnabled = userScrollEnabled,
) { page ->
Image(
painter = if (page == 0) painterResource(id = R.drawable.image_1) else painterResource(id = R.drawable.image_2),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
coroutineScope
.launch {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
when (event.changes.size) {
2 -> {
userScrollEnabled = false
val change1 = event.changes[0]
val change2 = event.changes[1]
val distanceCurrent = calculateDistance(
change1.position,
change2.position
)
val distancePrevious = calculateDistance(
change1.previousPosition,
change2.previousPosition
)
scale *= distanceCurrent / distancePrevious
offset += change1.positionChange()
Log.i(
"TAGTTTTTT",
"distanceCurrent: $distanceCurrent\ndistancePrevious: $distancePrevious\nscale: $scale\noffset: $offset"
)
}
else -> {
userScrollEnabled = true
scale = 1f
offset = Offset.Zero
}
}
}
}
}
}
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
),
contentScale = ContentScale.Inside
)
}
}
}
}
I found the solution by adding a zooming modifier to the HorizontalPager
not the Image
.
Code:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProductImageDialog(
properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest: () -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val coroutineScope = rememberCoroutineScope()
var userScrollEnabled by remember { mutableStateOf(true) }
val pagerState = rememberPagerState { 2 }
Dialog(
properties = properties,
onDismissRequest = onDismissRequest
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Green),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
IconButton(onClick = onDismissRequest) {
Icon(
imageVector = Icons.Default.Close,
tint = Color.White,
contentDescription = null
)
}
Spacer(modifier = Modifier.height(8.dp))
HorizontalPager(
modifier = Modifier
.background(color = Color.White)
.fillMaxWidth()
.fillMaxHeight(0.4f)
.pointerInput(Unit) {
coroutineScope
.launch {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
when (event.changes.size) {
2 -> {
userScrollEnabled = false
val change1 = event.changes[0]
val change2 = event.changes[1]
val distanceCurrent = calculateDistance(
change1.position,
change2.position
)
val distancePrevious = calculateDistance(
change1.previousPosition,
change2.previousPosition
)
scale *= distanceCurrent / distancePrevious
offset += change1.positionChange()
Log.i(
"TAGTTTTTT",
"distanceCurrent: $distanceCurrent\ndistancePrevious: $distancePrevious\nscale: $scale\noffset: $offset"
)
}
else -> {
userScrollEnabled = true
scale = 1f
offset = Offset.Zero
}
}
}
}
}
}
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
),
state = pagerState,
userScrollEnabled = userScrollEnabled,
) { page ->
Image(
painter = if (page == 0) painterResource(id = R.drawable.image_1) else painterResource(
id = R.drawable.image_2
),
contentDescription = null,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.Inside
)
}
}
}
}
private fun calculateDistance(point1: Offset, point2: Offset): Float {
val dx = point1.x - point2.x
val dy = point1.y - point2.y
return sqrt(dx * dx + dy * dy)
}