Skip to content

Commit a6e9f47

Browse files
move update transform logic to ZoomState
1 parent 5ef9395 commit a6e9f47

File tree

2 files changed

+188
-16
lines changed

2 files changed

+188
-16
lines changed

image/src/main/java/com/smarttoolfactory/image/zoom/ZoomState.kt

Lines changed: 181 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,139 @@ import androidx.compose.runtime.Immutable
77
import androidx.compose.runtime.remember
88
import androidx.compose.ui.Modifier
99
import androidx.compose.ui.geometry.Offset
10+
import androidx.compose.ui.unit.IntSize
1011
import kotlinx.coroutines.coroutineScope
1112

13+
14+
15+
/**
16+
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
17+
* configuration to allow changing pan, zoom, and rotation.
18+
*
19+
* [key1] is used to reset remember block to initial calculations. This can be used
20+
* when image, contentScale or any property changes which requires values to be reset to initial
21+
* values
22+
*
23+
* @param initialZoom zoom set initially
24+
* @param initialRotation rotation set initially
25+
* @param minZoom minimum zoom value this Composable can possess
26+
* @param maxZoom maximum zoom value this Composable can possess
27+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
28+
* empty space on sides or edges of parent
29+
* @param zoomEnabled when set to true zoom is enabled
30+
* @param panEnabled when set to true pan is enabled
31+
* @param rotationEnabled when set to true rotation is enabled
32+
*/
33+
@Composable
34+
fun rememberZoomState(
35+
initialZoom: Float = 1f,
36+
initialRotation: Float = 0f,
37+
minZoom: Float = 1f,
38+
maxZoom: Float = 5f,
39+
zoomEnabled: Boolean = true,
40+
panEnabled: Boolean = true,
41+
rotationEnabled: Boolean = false,
42+
limitPan: Boolean = false,
43+
key1: Any? = Unit
44+
): ZoomState {
45+
return remember(key1) {
46+
ZoomState(
47+
initialZoom = initialZoom,
48+
initialRotation = initialRotation,
49+
minZoom = minZoom,
50+
maxZoom = maxZoom,
51+
zoomEnabled = zoomEnabled,
52+
panEnabled = panEnabled,
53+
rotationEnabled = rotationEnabled,
54+
limitPan = limitPan
55+
)
56+
}
57+
}
58+
1259
/**
1360
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
1461
* configuration to allow changing pan, zoom, and rotation.
1562
*
63+
* [key1] or [key2] are used to reset remember block to initial calculations. This can be used
64+
* when image, contentScale or any property changes which requires values to be reset to initial
65+
* values
66+
*
67+
* @param initialZoom zoom set initially
68+
* @param initialRotation rotation set initially
69+
* @param minZoom minimum zoom value this Composable can possess
70+
* @param maxZoom maximum zoom value this Composable can possess
71+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
72+
* empty space on sides or edges of parent
73+
* @param zoomEnabled when set to true zoom is enabled
74+
* @param panEnabled when set to true pan is enabled
75+
* @param rotationEnabled when set to true rotation is enabled
1676
*/
1777
@Composable
1878
fun rememberZoomState(
1979
initialZoom: Float = 1f,
2080
initialRotation: Float = 0f,
2181
minZoom: Float = 1f,
22-
maxZoom: Float = 5f
82+
maxZoom: Float = 5f,
83+
zoomEnabled: Boolean = true,
84+
panEnabled: Boolean = true,
85+
rotationEnabled: Boolean = false,
86+
limitPan: Boolean = false,
87+
key1: Any?,
88+
key2: Any?,
2389
): ZoomState {
24-
return remember {
90+
return remember(key1, key2) {
2591
ZoomState(
2692
initialZoom = initialZoom,
2793
initialRotation = initialRotation,
2894
minZoom = minZoom,
29-
maxZoom = maxZoom
95+
maxZoom = maxZoom,
96+
zoomEnabled = zoomEnabled,
97+
panEnabled = panEnabled,
98+
rotationEnabled = rotationEnabled,
99+
limitPan = limitPan
100+
)
101+
}
102+
}
103+
104+
/**
105+
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
106+
* configuration to allow changing pan, zoom, and rotation.
107+
*
108+
* @param initialZoom zoom set initially
109+
* @param initialRotation rotation set initially
110+
* @param minZoom minimum zoom value this Composable can possess
111+
* @param maxZoom maximum zoom value this Composable can possess
112+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
113+
* empty space on sides or edges of parent
114+
* @param zoomEnabled when set to true zoom is enabled
115+
* @param panEnabled when set to true pan is enabled
116+
* @param rotationEnabled when set to true rotation is enabled
117+
* @param keys are used to reset remember block to initial calculations. This can be used
118+
* when image, contentScale or any property changes which requires values to be reset to initial
119+
* values
120+
*/
121+
@Composable
122+
fun rememberZoomState(
123+
initialZoom: Float = 1f,
124+
initialRotation: Float = 0f,
125+
minZoom: Float = 1f,
126+
maxZoom: Float = 5f,
127+
zoomEnabled: Boolean = true,
128+
panEnabled: Boolean = true,
129+
rotationEnabled: Boolean = false,
130+
limitPan: Boolean = false,
131+
vararg keys: Any?
132+
): ZoomState {
133+
return remember(keys) {
134+
ZoomState(
135+
initialZoom = initialZoom,
136+
initialRotation = initialRotation,
137+
minZoom = minZoom,
138+
maxZoom = maxZoom,
139+
zoomEnabled = zoomEnabled,
140+
panEnabled = panEnabled,
141+
rotationEnabled = rotationEnabled,
142+
limitPan = limitPan
30143
)
31144
}
32145
}
@@ -35,13 +148,23 @@ fun rememberZoomState(
35148
* * State of the zoom. Allows the developer to change zoom, pan, translate,
36149
* or get current state by
37150
* calling methods on this object. To be hosted and passed to [Modifier.zoom]
151+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
152+
* empty space on sides or edges of parent.
153+
154+
* @param zoomEnabled when set to true zoom is enabled
155+
* @param panEnabled when set to true pan is enabled
156+
* @param rotationEnabled when set to true rotation is enabled
38157
*/
39158
@Immutable
40159
open class ZoomState internal constructor(
41160
initialZoom: Float = 1f,
42161
initialRotation: Float = 0f,
43162
minZoom: Float = 1f,
44-
maxZoom: Float = 5f
163+
maxZoom: Float = 5f,
164+
private val zoomEnabled: Boolean = true,
165+
private val panEnabled: Boolean = true,
166+
internal val rotationEnabled: Boolean = true,
167+
internal val limitPan: Boolean = false
45168
) {
46169

47170
internal val zoomMin = minZoom.coerceAtLeast(.5f)
@@ -74,11 +197,51 @@ open class ZoomState internal constructor(
74197
)
75198

76199

77-
fun boundPan(maxX: Float, maxY: Float) {
78-
animatablePan.updateBounds(
79-
Offset(-maxX, -maxY),
80-
Offset(maxX, maxY)
81-
)
200+
open fun boundPan(lowerBound:Offset, upperBound:Offset) {
201+
animatablePan.updateBounds(lowerBound, upperBound)
202+
}
203+
204+
internal open suspend fun updateZoomState(
205+
size: IntSize,
206+
gesturePan: Offset,
207+
gestureZoom: Float,
208+
gestureRotate: Float,
209+
) {
210+
var zoom = zoom
211+
212+
val boundPan = limitPan && !rotationEnabled
213+
214+
val rotation = if (rotationEnabled) {
215+
rotation + gestureRotate
216+
} else {
217+
0f
218+
}
219+
220+
if (panEnabled) {
221+
val offset = pan
222+
var newOffset = offset + gesturePan.times(zoom)
223+
224+
if (boundPan) {
225+
val maxX = (size.width * (zoom - 1) / 2f)
226+
.coerceAtLeast(0f)
227+
val maxY = (size.height * (zoom - 1) / 2f)
228+
.coerceAtLeast(0f)
229+
newOffset = Offset(
230+
newOffset.x.coerceIn(-maxX, maxX),
231+
newOffset.y.coerceIn(-maxY, maxY)
232+
)
233+
}
234+
snapPanTo(newOffset)
235+
}
236+
237+
if (zoomEnabled) {
238+
zoom = (zoom * gestureZoom).coerceIn(zoomMin, zoomMax)
239+
snapZoomTo(zoom)
240+
}
241+
242+
if (rotationEnabled) {
243+
snapRotationTo(rotation)
244+
}
82245
}
83246

84247
internal suspend fun animatePanTo(pan: Offset) = coroutineScope {
@@ -94,14 +257,20 @@ open class ZoomState internal constructor(
94257
}
95258

96259
internal suspend fun snapPanTo(offset: Offset) = coroutineScope {
97-
animatablePan.snapTo(offset)
260+
if (panEnabled) {
261+
animatablePan.snapTo(offset)
262+
}
98263
}
99264

100265
internal suspend fun snapZoomTo(zoom: Float) = coroutineScope {
101-
animatableZoom.snapTo(zoom)
266+
if (zoomEnabled) {
267+
animatableZoom.snapTo(zoom)
268+
}
102269
}
103270

104271
internal suspend fun snapRotationTo(rotation: Float) = coroutineScope {
105-
animatableRotation.snapTo(rotation)
272+
if (rotationEnabled) {
273+
animatableRotation.snapTo(rotation)
274+
}
106275
}
107276
}

image/src/main/java/com/smarttoolfactory/image/zoom/ZoomUtil.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.smarttoolfactory.image.zoom
22

3-
fun calculateZoom(
3+
/**
4+
* Calculate zoom level and zoom value when user double taps
5+
*/
6+
internal fun calculateZoom(
47
zoomLevel: ZoomLevel,
58
initial: Float,
69
min: Float,
@@ -13,15 +16,15 @@ fun calculateZoom(
1316
when (zoomLevel) {
1417
ZoomLevel.Initial -> {
1518
newZoomLevel = ZoomLevel.Max
16-
newZoom = max
19+
newZoom = max.coerceAtMost(3f)
1720
}
1821
ZoomLevel.Max -> {
1922
newZoomLevel = ZoomLevel.Min
20-
newZoom = if(min == initial) (min +max)/2 else min
23+
newZoom = if (min == initial) (min + max.coerceAtMost(3f)) / 2 else min
2124
}
2225
else -> {
2326
newZoomLevel = ZoomLevel.Initial
24-
newZoom = initial
27+
newZoom = initial.coerceAtMost(2f)
2528
}
2629
}
2730
return Pair(newZoomLevel, newZoom)

0 commit comments

Comments
 (0)