Skip to content

Commit 3d24434

Browse files
update Modifier.zoom with ZoomState
1 parent eb5d025 commit 3d24434

File tree

1 file changed

+63
-103
lines changed

1 file changed

+63
-103
lines changed
Lines changed: 63 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package com.smarttoolfactory.image.zoom
22

3-
import androidx.compose.animation.core.Animatable
4-
import androidx.compose.animation.core.VectorConverter
5-
import androidx.compose.animation.core.spring
63
import androidx.compose.foundation.gestures.detectTapGestures
7-
import androidx.compose.runtime.*
4+
import androidx.compose.runtime.rememberCoroutineScope
85
import androidx.compose.ui.Modifier
96
import androidx.compose.ui.composed
107
import androidx.compose.ui.draw.clipToBounds
118
import androidx.compose.ui.geometry.Offset
9+
import androidx.compose.ui.graphics.GraphicsLayerScope
1210
import androidx.compose.ui.graphics.graphicsLayer
1311
import androidx.compose.ui.input.pointer.pointerInput
1412
import com.smarttoolfactory.gesture.detectTransformGestures
@@ -18,9 +16,6 @@ import kotlinx.coroutines.launch
1816
* Modifier that zooms in or out of Composable set to.
1917
* @param keys are used for [Modifier.pointerInput] to restart closure when any keys assigned
2018
* change
21-
* @param initialZoom initial value of zoom
22-
* @param minZoom minimum zoom value
23-
* @param maxZoom maximum zoom value
2419
* @param clip when set to true clips to parent bounds. Anything outside parent bounds is not
2520
* drawn
2621
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
@@ -39,75 +34,44 @@ import kotlinx.coroutines.launch
3934
*/
4035
fun Modifier.zoom(
4136
vararg keys: Any?,
42-
initialZoom: Float = 1f,
43-
minZoom: Float = 1f,
44-
maxZoom: Float = 5f,
4537
clip: Boolean = true,
4638
limitPan: Boolean = true,
4739
consume: Boolean = true,
4840
zoomEnabled: Boolean = true,
4941
panEnabled: Boolean = true,
5042
rotationEnabled: Boolean = false,
43+
zoomState: ZoomState,
5144
onGestureStart: (ZoomData) -> Unit = {},
5245
onGesture: (ZoomData) -> Unit = {},
5346
onGestureEnd: (ZoomData) -> Unit = {},
5447
) = composed(
5548
factory = {
56-
5749
val coroutineScope = rememberCoroutineScope()
58-
val zoomMin = minZoom.coerceAtLeast(.5f)
59-
val zoomMax = maxZoom.coerceAtLeast(1f)
60-
val zoomInitial = initialZoom.coerceIn(zoomMin, zoomMax)
61-
62-
require(zoomMax >= zoomMin)
63-
64-
val animatablePan = remember {
65-
Animatable(Offset.Zero, Offset.VectorConverter)
66-
}
67-
val animatableZoom = remember { Animatable(zoomInitial) }
68-
val animatableRotation = remember { Animatable(0f) }
69-
70-
var zoomLevel by remember { mutableStateOf(ZoomLevel.Min) }
71-
7250
val boundPan = limitPan && !rotationEnabled
7351
val clipToBounds = (clip || boundPan)
7452

75-
7653
val transformModifier = Modifier.pointerInput(keys) {
7754
detectTransformGestures(
7855
consume = consume,
7956
onGestureStart = {
80-
onGestureStart(
81-
ZoomData(
82-
zoom = animatableZoom.value,
83-
pan = animatablePan.value,
84-
rotation = animatableRotation.value
85-
)
86-
)
57+
onGestureStart(zoomState.zoomData)
8758
},
8859
onGestureEnd = {
89-
onGestureEnd(
90-
ZoomData(
91-
zoom = animatableZoom.value,
92-
pan = animatablePan.value,
93-
rotation = animatableRotation.value
94-
)
95-
)
60+
onGestureEnd(zoomState.zoomData)
9661
},
97-
onGesture = { centroid, gesturePan, gestureZoom, gestureRotate,
98-
_,
99-
_ ->
62+
onGesture = { _, gesturePan, gestureZoom, gestureRotate, _, _ ->
63+
64+
var zoom = zoomState.zoom
65+
val offset = zoomState.pan
10066

101-
var zoom = animatableZoom.value
102-
val offset = animatablePan.value
10367
val rotation = if (rotationEnabled) {
104-
animatableRotation.value + gestureRotate
68+
zoomState.rotation + gestureRotate
10569
} else {
10670
0f
10771
}
10872

10973
if (zoomEnabled) {
110-
zoom = (zoom * gestureZoom).coerceIn(zoomMin, zoomMax)
74+
zoom = (zoom * gestureZoom).coerceIn(zoomState.zoomMin, zoomState.zoomMax)
11175
}
11276

11377
val newOffset = offset + gesturePan.times(zoom)
@@ -117,16 +81,15 @@ fun Modifier.zoom(
11781
val maxY = (size.height * (zoom - 1) / 2f)
11882
.coerceAtLeast(0f)
11983

120-
12184
if (zoomEnabled) {
12285
coroutineScope.launch {
123-
animatableZoom.snapTo(zoom)
86+
zoomState.snapZoomTo(zoom)
12487
}
12588
}
12689

12790
if (panEnabled) {
12891
coroutineScope.launch {
129-
animatablePan.snapTo(
92+
zoomState.snapPanTo(
13093
if (boundPan) {
13194
Offset(
13295
newOffset.x.coerceIn(-maxX, maxX),
@@ -141,17 +104,11 @@ fun Modifier.zoom(
141104

142105
if (rotationEnabled) {
143106
coroutineScope.launch {
144-
animatableRotation.snapTo(rotation)
107+
zoomState.snapRotationTo(rotation)
145108
}
146109
}
147110

148-
onGesture(
149-
ZoomData(
150-
zoom = animatableZoom.value,
151-
pan = animatablePan.value,
152-
rotation = animatableRotation.value
153-
)
154-
)
111+
onGesture(zoomState.zoomData)
155112
}
156113
)
157114
}
@@ -161,49 +118,31 @@ fun Modifier.zoom(
161118
onDoubleTap = {
162119

163120
val (newZoomLevel, newZoom) = calculateZoom(
164-
zoomLevel = zoomLevel,
165-
initial = zoomInitial,
166-
min = minZoom,
167-
max = maxZoom
121+
zoomLevel = zoomState.zoomLevel,
122+
initial = zoomState.zoomInitial,
123+
min = zoomState.zoomMin,
124+
max = zoomState.zoomMax
168125
)
169126

170-
zoomLevel = newZoomLevel
127+
zoomState.zoomLevel = newZoomLevel
171128

172129
coroutineScope.launch {
173-
animatablePan.animateTo(Offset.Zero, spring())
130+
zoomState.animatePanTo(Offset.Zero)
174131
}
132+
175133
coroutineScope.launch {
176-
animatableZoom.animateTo(newZoom, spring())
134+
zoomState.animateZoomTo(newZoom)
177135
}
136+
178137
coroutineScope.launch {
179-
animatableRotation.animateTo(0f, spring())
138+
zoomState.animateRotationTo(zoomState.rotationInitial)
180139
}
181140
}
182141
)
183142
}
184143

185144
val graphicsModifier = Modifier.graphicsLayer {
186-
val zoom = animatableZoom.value
187-
188-
// Set zoom
189-
scaleX = zoom
190-
scaleY = zoom
191-
192-
// Set pan
193-
val translationX = animatablePan.value.x
194-
val translationY = animatablePan.value.y
195-
this.translationX = translationX
196-
this.translationY = translationY
197-
198-
// Set rotation
199-
rotationZ = animatableRotation.value
200-
// TransformOrigin(0f, 0f).also { transformOrigin = it }
201-
onGesture(
202-
ZoomData(
203-
zoom = animatableZoom.value,
204-
pan = animatablePan.value,
205-
)
206-
)
145+
this.update(zoomState)
207146
}
208147

209148
this.then(
@@ -213,10 +152,19 @@ fun Modifier.zoom(
213152
.then(graphicsModifier)
214153

215154
)
216-
217155
},
218156
inspectorInfo = {
219-
157+
name = "zoom"
158+
properties["keys"] = keys
159+
properties["clip"] = clip
160+
properties["limitPan"] = limitPan
161+
properties["consume"] = consume
162+
properties["zoomEnabled"] = zoomEnabled
163+
properties["rotationEnabled"] = rotationEnabled
164+
properties["zoomState"] = zoomState
165+
properties["onGestureStart"] = onGestureStart
166+
properties["onGesture"] = onGesture
167+
properties["onGestureEnd"] = onGestureEnd
220168
}
221169
)
222170

@@ -228,28 +176,40 @@ fun Modifier.zoom(
228176
* drawn
229177
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
230178
* empty space on sides or edges of parent.
231-
* @param minZoom minimum zoom value
232-
* @param maxZoom maximum zoom value
233179
*/
234180
fun Modifier.zoom(
235181
vararg keys: Any?,
236-
initialZoom: Float = 1f,
237-
minZoom: Float = 1f,
238-
maxZoom: Float = 5f,
239-
rotate: Boolean = false,
182+
zoomState: ZoomState,
183+
rotationEnabled: Boolean = false,
240184
clip: Boolean = true,
241-
limitPan: Boolean = true,
242-
243-
) = zoom(
185+
limitPan: Boolean = true
186+
) = zoom(
244187
keys = keys,
245-
initialZoom = initialZoom,
246-
minZoom = minZoom,
247-
maxZoom = maxZoom,
248188
clip = clip,
249189
limitPan = limitPan,
250-
rotationEnabled = rotate,
190+
rotationEnabled = rotationEnabled,
251191
consume = true,
192+
zoomState = zoomState,
252193
onGestureStart = {},
253194
onGestureEnd = {},
254195
onGesture = {}
255-
)
196+
)
197+
198+
private fun GraphicsLayerScope.update(zoomState: ZoomState) {
199+
200+
// Set zoom
201+
val zoom = zoomState.zoom
202+
this.scaleX = zoom
203+
this.scaleY = zoom
204+
205+
// Set pan
206+
val pan = zoomState.pan
207+
val translationX = pan.x
208+
val translationY = pan.y
209+
this.translationX = translationX
210+
this.translationY = translationY
211+
212+
// Set rotation
213+
this.rotationZ = zoomState.rotation
214+
}
215+

0 commit comments

Comments
 (0)