Skip to content

Commit 7199fb4

Browse files
move remember functions for zoom states to separate file
1 parent 78746af commit 7199fb4

File tree

4 files changed

+398
-406
lines changed

4 files changed

+398
-406
lines changed
Lines changed: 14 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
package com.smarttoolfactory.image.zoom
22

3-
import androidx.compose.animation.core.exponentialDecay
4-
import androidx.compose.runtime.*
5-
import androidx.compose.ui.Modifier
6-
import androidx.compose.ui.geometry.Offset
7-
import androidx.compose.ui.geometry.Rect
8-
import androidx.compose.ui.geometry.Size
9-
import androidx.compose.ui.input.pointer.PointerInputChange
10-
import androidx.compose.ui.input.pointer.util.VelocityTracker
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.remember
115
import androidx.compose.ui.unit.IntSize
12-
import kotlinx.coroutines.coroutineScope
13-
146

157
/**
168
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
@@ -32,7 +24,8 @@ import kotlinx.coroutines.coroutineScope
3224
*/
3325
@Composable
3426
fun rememberEnhancedZoomState(
35-
size: IntSize,
27+
imageSize: IntSize,
28+
containerSize: IntSize,
3629
initialZoom: Float = 1f,
3730
initialRotation: Float = 0f,
3831
minZoom: Float = 1f,
@@ -45,9 +38,9 @@ fun rememberEnhancedZoomState(
4538
): EnhancedZoomState {
4639
return remember(key1) {
4740
EnhancedZoomState(
48-
size = size,
41+
imageSize = imageSize,
42+
containerSize = containerSize,
4943
initialZoom = initialZoom,
50-
initialRotation = initialRotation,
5144
minZoom = minZoom,
5245
maxZoom = maxZoom,
5346
zoomEnabled = zoomEnabled,
@@ -67,7 +60,6 @@ fun rememberEnhancedZoomState(
6760
* values
6861
*
6962
* @param initialZoom zoom set initially
70-
* @param initialRotation rotation set initially
7163
* @param minZoom minimum zoom value this Composable can possess
7264
* @param maxZoom maximum zoom value this Composable can possess
7365
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
@@ -78,9 +70,9 @@ fun rememberEnhancedZoomState(
7870
*/
7971
@Composable
8072
fun rememberEnhancedZoomState(
81-
size: IntSize,
73+
imageSize: IntSize,
74+
containerSize: IntSize,
8275
initialZoom: Float = 1f,
83-
initialRotation: Float = 0f,
8476
minZoom: Float = 1f,
8577
maxZoom: Float = 5f,
8678
zoomEnabled: Boolean = true,
@@ -92,9 +84,9 @@ fun rememberEnhancedZoomState(
9284
): EnhancedZoomState {
9385
return remember(key1, key2) {
9486
EnhancedZoomState(
95-
size = size,
87+
imageSize = imageSize,
88+
containerSize = containerSize,
9689
initialZoom = initialZoom,
97-
initialRotation = initialRotation,
9890
minZoom = minZoom,
9991
maxZoom = maxZoom,
10092
zoomEnabled = zoomEnabled,
@@ -110,7 +102,6 @@ fun rememberEnhancedZoomState(
110102
* configuration to allow changing pan, zoom, and rotation.
111103
*
112104
* @param initialZoom zoom set initially
113-
* @param initialRotation rotation set initially
114105
* @param minZoom minimum zoom value this Composable can possess
115106
* @param maxZoom maximum zoom value this Composable can possess
116107
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
@@ -124,9 +115,9 @@ fun rememberEnhancedZoomState(
124115
*/
125116
@Composable
126117
fun rememberEnhancedZoomState(
127-
size: IntSize,
118+
imageSize: IntSize,
119+
containerSize: IntSize,
128120
initialZoom: Float = 1f,
129-
initialRotation: Float = 0f,
130121
minZoom: Float = 1f,
131122
maxZoom: Float = 5f,
132123
zoomEnabled: Boolean = true,
@@ -137,9 +128,9 @@ fun rememberEnhancedZoomState(
137128
): EnhancedZoomState {
138129
return remember(keys) {
139130
EnhancedZoomState(
140-
size = size,
131+
imageSize = imageSize,
132+
containerSize = containerSize,
141133
initialZoom = initialZoom,
142-
initialRotation = initialRotation,
143134
minZoom = minZoom,
144135
maxZoom = maxZoom,
145136
zoomEnabled = zoomEnabled,
@@ -148,227 +139,4 @@ fun rememberEnhancedZoomState(
148139
limitPan = limitPan
149140
)
150141
}
151-
}
152-
153-
/**
154-
* * State of the zoom. Allows the developer to change zoom, pan, translate,
155-
* or get current state by
156-
* calling methods on this object. To be hosted and passed to [Modifier.zoom]
157-
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
158-
* empty space on sides or edges of parent.
159-
160-
* @param zoomEnabled when set to true zoom is enabled
161-
* @param panEnabled when set to true pan is enabled
162-
* @param rotationEnabled when set to true rotation is enabled
163-
*/
164-
open class EnhancedZoomState constructor(
165-
var size: IntSize,
166-
initialZoom: Float = 1f,
167-
initialRotation: Float = 0f,
168-
minZoom: Float = .5f,
169-
maxZoom: Float = 5f,
170-
override var zoomEnabled: Boolean = true,
171-
override var panEnabled: Boolean = true,
172-
override var rotationEnabled: Boolean = true,
173-
override var limitPan: Boolean = false
174-
) : ZoomState(
175-
initialZoom, initialRotation, minZoom, maxZoom, zoomEnabled
176-
) {
177-
178-
val isZooming = animatableZoom.isRunning
179-
val isPanning = animatablePan.isRunning
180-
val isRotating = animatableRotation.isRunning
181-
182-
val isAnimationRunning = isZooming || isPanning || isRotating
183-
184-
var rectDraw =
185-
Rect(offset = Offset.Zero, size = Size(size.width.toFloat(), size.height.toFloat()))
186-
var rectCrop by mutableStateOf(rectDraw.copy())
187-
188-
private val velocityTracker = VelocityTracker()
189-
190-
val enhancedZoomData: EnhancedZoomData
191-
get() = EnhancedZoomData(
192-
zoom = animatableZoom.targetValue,
193-
pan = animatablePan.targetValue,
194-
rotation = animatableRotation.targetValue,
195-
drawRect = rectDraw,
196-
cropRect = rectCrop
197-
)
198-
199-
private fun getBounds(): Offset {
200-
return getBounds(size)
201-
}
202-
203-
// Touch gestures
204-
open suspend fun onDown(change: PointerInputChange) {}
205-
206-
open suspend fun onMove(change: PointerInputChange) {}
207-
208-
open suspend fun onUp(change: PointerInputChange) {}
209-
210-
// Transform Gestures
211-
internal open suspend fun onGestureStart(change: PointerInputChange) {}
212-
213-
internal open suspend fun onGesture(
214-
centroid: Offset,
215-
pan: Offset,
216-
zoom: Float,
217-
rotation: Float,
218-
mainPointer: PointerInputChange,
219-
changes: List<PointerInputChange>
220-
) = coroutineScope {
221-
222-
updateZoomState(
223-
size = size,
224-
gestureZoom = zoom,
225-
gesturePan = pan,
226-
gestureRotate = rotation
227-
)
228-
229-
// Fling Gesture
230-
if (changes.size == 1) {
231-
addPosition(
232-
mainPointer.uptimeMillis,
233-
mainPointer.position
234-
)
235-
}
236-
}
237-
238-
internal suspend fun onGestureEnd(onAnimationEnd: () -> Unit) {
239-
if (zoom > 1) {
240-
fling()
241-
}
242-
resetToValidBounds()
243-
onAnimationEnd()
244-
}
245-
246-
// Double Tap
247-
internal suspend fun onDoubleTap(onAnimationEnd: () -> Unit) {
248-
resetTracking()
249-
resetWithAnimation()
250-
onAnimationEnd()
251-
}
252-
253-
/**
254-
* Resets to bounds with animation and resets tracking for fling animation
255-
*/
256-
private suspend fun resetToValidBounds() {
257-
val zoom = zoom.coerceAtLeast(1f)
258-
val bounds = getBounds()
259-
260-
val pan = Offset(
261-
pan.x.coerceIn(-bounds.x, bounds.x),
262-
pan.y.coerceIn(-bounds.y, bounds.y)
263-
)
264-
265-
resetWithAnimation(pan = pan, zoom = zoom)
266-
resetTracking()
267-
}
268-
269-
270-
// Fling gesture
271-
private fun addPosition(timeMillis: Long, position: Offset) {
272-
velocityTracker.addPosition(
273-
timeMillis = timeMillis,
274-
position = position
275-
)
276-
}
277-
278-
private suspend fun fling() {
279-
val velocityTracker = velocityTracker.calculateVelocity()
280-
val velocity = Offset(velocityTracker.x, velocityTracker.y)
281-
282-
animatablePan.animateDecay(
283-
velocity,
284-
exponentialDecay(
285-
absVelocityThreshold = 20f
286-
)
287-
)
288-
}
289-
290-
private fun resetTracking() {
291-
velocityTracker.resetTracking()
292-
}
293-
294-
override suspend fun updateZoomState(
295-
size: IntSize,
296-
gesturePan: Offset,
297-
gestureZoom: Float,
298-
gestureRotate: Float,
299-
) {
300-
val zoom = (zoom * gestureZoom).coerceIn(zoomMin, zoomMax)
301-
val rotation = if (rotationEnabled) {
302-
rotation + gestureRotate
303-
} else {
304-
0f
305-
}
306-
307-
if (panEnabled) {
308-
val newOffset = pan + gesturePan.times(zoom)
309-
snapPanTo(newOffset)
310-
}
311-
312-
if (zoomEnabled) {
313-
snapZoomTo(zoom)
314-
}
315-
316-
if (rotationEnabled) {
317-
snapRotationTo(rotation)
318-
}
319-
320-
val width = size.width
321-
val height = size.height
322-
val offsetX = (width * (zoom - 1) / 2f).coerceAtLeast(0f) - pan.x
323-
val offsetY = (height * (zoom - 1) / 2f).coerceAtLeast(0f) - pan.y
324-
325-
rectCrop = getCropRect(
326-
bitmapWidth = 1024,
327-
bitmapHeight = 768,
328-
imageWidth = width.toFloat(),
329-
imageHeight = height.toFloat(),
330-
pan = Offset(
331-
x = offsetX / zoom,
332-
y = offsetY / zoom,
333-
),
334-
zoom = zoom,
335-
rectBounds = rectDraw
336-
)
337-
println(
338-
"🔥 EnhancedZoomState updateZoomState()\n" +
339-
"offsetX: $offsetX, offsetY: $offsetY" +
340-
"size: $size, pan: $pan, zoom: $zoom\n" +
341-
"rectCrop: $rectCrop"
342-
)
343-
}
344-
}
345-
346-
/**
347-
* Get rectangle of current transformation of [pan], [zoom] and current bounds of the Composable's
348-
* selected area as [rectBounds]
349-
*/
350-
fun getCropRect(
351-
bitmapWidth: Int,
352-
bitmapHeight: Int,
353-
imageWidth: Float,
354-
imageHeight: Float,
355-
pan: Offset,
356-
zoom: Float,
357-
rectBounds: Rect
358-
): Rect {
359-
val widthRatio = bitmapWidth / imageWidth
360-
val heightRatio = bitmapHeight / imageHeight
361-
362-
val width = (widthRatio * rectBounds.width / zoom).coerceIn(0f, bitmapWidth.toFloat())
363-
val height = (heightRatio * rectBounds.height / zoom).coerceIn(0f, bitmapHeight.toFloat())
364-
365-
val offsetXInBitmap = (widthRatio * (pan.x + rectBounds.left / zoom))
366-
.coerceIn(0f, bitmapWidth - width)
367-
val offsetYInBitmap = heightRatio * (pan.y + rectBounds.top / zoom)
368-
.coerceIn(0f, bitmapHeight - height)
369-
370-
return Rect(
371-
offset = Offset(offsetXInBitmap, offsetYInBitmap),
372-
size = Size(width, height)
373-
)
374142
}

0 commit comments

Comments
 (0)