Skip to content

Commit c33a757

Browse files
add kdoc to enhanced zoom state and modifiers
1 parent e22120c commit c33a757

6 files changed

Lines changed: 168 additions & 89 deletions

File tree

app/src/main/java/com/smarttoolfactory/composeimage/demo/ZoomableImageDemo.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ private fun ZoomableImageDemo(contentScale: ContentScale) {
141141
.fillMaxWidth()
142142
.aspectRatio(4 / 3f)
143143
.zoom(
144-
Unit,
145144
zoomState = rememberZoomState(
146145
limitPan = false,
147146
rotationEnabled = true

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

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,64 @@ import androidx.compose.foundation.gestures.detectTapGestures
44
import androidx.compose.runtime.rememberCoroutineScope
55
import androidx.compose.ui.Modifier
66
import androidx.compose.ui.composed
7+
import androidx.compose.ui.draw.clipToBounds
78
import androidx.compose.ui.graphics.graphicsLayer
89
import androidx.compose.ui.input.pointer.pointerInput
910
import com.smarttoolfactory.gesture.detectTransformGestures
1011
import com.smarttoolfactory.image.util.update
1112
import kotlinx.coroutines.launch
1213

14+
/**
15+
* Modifier that zooms in or out of Composable set to. This zoom modifier has option
16+
* to move back to bounds with an animation or option to have fling gesture when user removes
17+
* from screen while velocity is higher than threshold to have smooth touch effect.
18+
*
19+
* @param key is used for [Modifier.pointerInput] to restart closure when any keys assigned
20+
* change
21+
* @param consume flag to prevent other gestures such as scroll, drag or transform to get
22+
* @param clip when set to true clips to parent bounds. Anything outside parent bounds is not
23+
* drawn
24+
* empty space on sides or edges of parent.
25+
* @param enhancedZoomState State of the zoom that contains option to set initial, min, max zoom,
26+
* enabling rotation, pan or zoom and contains current [ZoomData]
27+
* event propagations
28+
* @param onGestureStart callback to to notify gesture has started and return current
29+
* [EnhancedZoomData] of this modifier
30+
* @param onGesture callback to notify about ongoing gesture and return current
31+
* [EnhancedZoomData] of this modifier
32+
* @param onGestureEnd callback to notify that gesture finished return current
33+
* [EnhancedZoomData] of this modifier
34+
*/
1335
fun Modifier.enhancedZoom(
14-
enhancedZoomState: EnhancedZoomState,
1536
key: Any? = Unit,
16-
onDown: ((EnhancedZoomData) -> Unit)? = null,
17-
onMove: ((EnhancedZoomData) -> Unit)? = null,
18-
onUp: ((EnhancedZoomData) -> Unit)? = null,
37+
consume: Boolean = true,
38+
clip: Boolean = true,
39+
enhancedZoomState: EnhancedZoomState,
40+
onGestureStart: ((EnhancedZoomData) -> Unit)? = null,
41+
onGesture: ((EnhancedZoomData) -> Unit)? = null,
42+
onGestureEnd: ((EnhancedZoomData) -> Unit)? = null,
1943
) = composed(
2044

2145
factory = {
2246

2347
val coroutineScope = rememberCoroutineScope()
2448

25-
val transformModifier = Modifier.pointerInput(key) {
49+
val boundPan = enhancedZoomState.limitPan && !enhancedZoomState.rotationEnabled
50+
val clipToBounds = (clip || boundPan)
2651

52+
val transformModifier = Modifier.pointerInput(key) {
53+
// Pass size of this Composable this Modifier is attached for constraining operations
54+
// inside this bounds
55+
enhancedZoomState.size = this.size
2756
detectTransformGestures(
28-
consume = true,
57+
consume = consume,
2958
onGestureStart = {
30-
onDown?.invoke(enhancedZoomState.enhancedZoomData)
59+
onGestureStart?.invoke(enhancedZoomState.enhancedZoomData)
3160
},
3261
onGestureEnd = {
3362
coroutineScope.launch {
3463
enhancedZoomState.onGestureEnd {
35-
onUp?.invoke(enhancedZoomState.enhancedZoomData)
64+
onGestureEnd?.invoke(enhancedZoomState.enhancedZoomData)
3665
}
3766
}
3867
},
@@ -49,17 +78,20 @@ fun Modifier.enhancedZoom(
4978
)
5079
}
5180

52-
onMove?.invoke(enhancedZoomState.enhancedZoomData)
81+
onGesture?.invoke(enhancedZoomState.enhancedZoomData)
5382
}
5483
)
5584
}
5685

5786
val tapModifier = Modifier.pointerInput(key) {
87+
// Pass size of this Composable this Modifier is attached for constraining operations
88+
// inside this bounds
89+
enhancedZoomState.size = this.size
5890
detectTapGestures(
5991
onDoubleTap = {
6092
coroutineScope.launch {
6193
enhancedZoomState.onDoubleTap {
62-
onUp?.invoke(enhancedZoomState.enhancedZoomData)
94+
onGestureEnd?.invoke(enhancedZoomState.enhancedZoomData)
6395
}
6496
}
6597
}
@@ -71,7 +103,7 @@ fun Modifier.enhancedZoom(
71103
}
72104

73105
this.then(
74-
Modifier
106+
(if (clipToBounds) Modifier.clipToBounds() else Modifier)
75107
.then(tapModifier)
76108
.then(transformModifier)
77109
.then(graphicsModifier)
@@ -80,8 +112,8 @@ fun Modifier.enhancedZoom(
80112
inspectorInfo = {
81113
name = "enhancedZoom"
82114
// add name and value of each argument
83-
properties["onDown"] = onDown
84-
properties["onMove"] = onMove
85-
properties["onUp"] = onUp
115+
properties["onDown"] = onGestureStart
116+
properties["onMove"] = onGesture
117+
properties["onUp"] = onGestureEnd
86118
}
87119
)

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

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,48 @@ package com.smarttoolfactory.image.zoom
22

33
import androidx.compose.runtime.Composable
44
import androidx.compose.runtime.remember
5+
import androidx.compose.ui.Modifier
56
import androidx.compose.ui.unit.IntSize
67

78
/**
8-
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
9+
* Create and [remember] the [EnhancedZoomState] based on the currently appropriate transform
910
* configuration to allow changing pan, zoom, and rotation.
1011
*
12+
* Allows to change zoom, pan, translate, or get current state by
13+
* calling methods on this object. To be hosted and passed to [Modifier.enhancedZoom].
14+
* Also contains [EnhancedZoomData] about current transformation area of Composable and
15+
* visible are of image being zoomed, rotated, or panned. If any animation
16+
* is going on current [EnhancedZoomState.isAnimationRunning] is true
17+
* and [EnhancedZoomData] returns rectangle
18+
* that belongs to end of animation.
19+
*
1120
* [key1] is used to reset remember block to initial calculations. This can be used
1221
* when image, contentScale or any property changes which requires values to be reset to initial
1322
* values
14-
*
1523
* @param initialZoom zoom set initially
16-
* @param initialRotation rotation set initially
17-
* @param minZoom minimum zoom value this Composable can possess
18-
* @param maxZoom maximum zoom value this Composable can possess
19-
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
20-
* empty space on sides or edges of parent
24+
* @param minZoom minimum zoom value
25+
* @param maxZoom maximum zoom value
26+
* @param flingGestureEnabled when set to true dragging pointer builds up velocity. When last
27+
* pointer leaves Composable a movement invoked against friction till velocity drops down
28+
* to threshold
29+
* @param moveToBoundsEnabled when set to true if image zoom is lower than initial zoom or
30+
* panned out of image boundaries moves back to bounds with animation.
31+
* ##Note
32+
* Currently rotating back to borders is not available
2133
* @param zoomEnabled when set to true zoom is enabled
2234
* @param panEnabled when set to true pan is enabled
2335
* @param rotationEnabled when set to true rotation is enabled
36+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
37+
* empty space on sides or edges of parent
2438
*/
2539
@Composable
2640
fun rememberEnhancedZoomState(
2741
imageSize: IntSize,
28-
containerSize: IntSize,
2942
initialZoom: Float = 1f,
30-
initialRotation: Float = 0f,
3143
minZoom: Float = 1f,
3244
maxZoom: Float = 5f,
45+
flingGestureEnabled: Boolean = false,
46+
moveToBoundsEnabled: Boolean = true,
3347
zoomEnabled: Boolean = true,
3448
panEnabled: Boolean = true,
3549
rotationEnabled: Boolean = false,
@@ -39,10 +53,11 @@ fun rememberEnhancedZoomState(
3953
return remember(key1) {
4054
EnhancedZoomState(
4155
imageSize = imageSize,
42-
containerSize = containerSize,
4356
initialZoom = initialZoom,
4457
minZoom = minZoom,
4558
maxZoom = maxZoom,
59+
flingGestureEnabled = flingGestureEnabled,
60+
moveToBoundsEnabled = moveToBoundsEnabled,
4661
zoomEnabled = zoomEnabled,
4762
panEnabled = panEnabled,
4863
rotationEnabled = rotationEnabled,
@@ -52,29 +67,44 @@ fun rememberEnhancedZoomState(
5267
}
5368

5469
/**
55-
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
70+
* Create and [remember] the [EnhancedZoomState] based on the currently appropriate transform
5671
* configuration to allow changing pan, zoom, and rotation.
5772
*
73+
* Allows to change zoom, pan, translate, or get current state by
74+
* calling methods on this object. To be hosted and passed to [Modifier.enhancedZoom].
75+
* Also contains [EnhancedZoomData] about current transformation area of Composable and
76+
* visible are of image being zoomed, rotated, or panned. If any animation
77+
* is going on current [EnhancedZoomState.isAnimationRunning] is true
78+
* and [EnhancedZoomData] returns rectangle
79+
* that belongs to end of animation.
80+
*
5881
* [key1] or [key2] are used to reset remember block to initial calculations. This can be used
5982
* when image, contentScale or any property changes which requires values to be reset to initial
6083
* values
61-
*
6284
* @param initialZoom zoom set initially
63-
* @param minZoom minimum zoom value this Composable can possess
64-
* @param maxZoom maximum zoom value this Composable can possess
65-
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
66-
* empty space on sides or edges of parent
85+
* @param minZoom minimum zoom value
86+
* @param maxZoom maximum zoom value
87+
* @param flingGestureEnabled when set to true dragging pointer builds up velocity. When last
88+
* pointer leaves Composable a movement invoked against friction till velocity drops down
89+
* to threshold
90+
* @param moveToBoundsEnabled when set to true if image zoom is lower than initial zoom or
91+
* panned out of image boundaries moves back to bounds with animation.
92+
* ##Note
93+
* Currently rotating back to borders is not available
6794
* @param zoomEnabled when set to true zoom is enabled
6895
* @param panEnabled when set to true pan is enabled
6996
* @param rotationEnabled when set to true rotation is enabled
97+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
98+
* empty space on sides or edges of parent
7099
*/
71100
@Composable
72101
fun rememberEnhancedZoomState(
73102
imageSize: IntSize,
74-
containerSize: IntSize,
75103
initialZoom: Float = 1f,
76104
minZoom: Float = 1f,
77105
maxZoom: Float = 5f,
106+
flingGestureEnabled: Boolean = false,
107+
moveToBoundsEnabled: Boolean = true,
78108
zoomEnabled: Boolean = true,
79109
panEnabled: Boolean = true,
80110
rotationEnabled: Boolean = false,
@@ -85,10 +115,11 @@ fun rememberEnhancedZoomState(
85115
return remember(key1, key2) {
86116
EnhancedZoomState(
87117
imageSize = imageSize,
88-
containerSize = containerSize,
89118
initialZoom = initialZoom,
90119
minZoom = minZoom,
91120
maxZoom = maxZoom,
121+
flingGestureEnabled = flingGestureEnabled,
122+
moveToBoundsEnabled = moveToBoundsEnabled,
92123
zoomEnabled = zoomEnabled,
93124
panEnabled = panEnabled,
94125
rotationEnabled = rotationEnabled,
@@ -98,28 +129,44 @@ fun rememberEnhancedZoomState(
98129
}
99130

100131
/**
101-
* * Create and [remember] the [ZoomState] based on the currently appropriate transform
132+
* Create and [remember] the [EnhancedZoomState] based on the currently appropriate transform
102133
* configuration to allow changing pan, zoom, and rotation.
103134
*
135+
* Allows to change zoom, pan, translate, or get current state by
136+
* calling methods on this object. To be hosted and passed to [Modifier.enhancedZoom].
137+
* Also contains [EnhancedZoomData] about current transformation area of Composable and
138+
* visible are of image being zoomed, rotated, or panned. If any animation
139+
* is going on current [EnhancedZoomState.isAnimationRunning] is true
140+
* and [EnhancedZoomData] returns rectangle
141+
* that belongs to end of animation.
142+
*
104143
* @param initialZoom zoom set initially
105-
* @param minZoom minimum zoom value this Composable can possess
106-
* @param maxZoom maximum zoom value this Composable can possess
107-
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
108-
* empty space on sides or edges of parent
144+
* @param minZoom minimum zoom value
145+
* @param maxZoom maximum zoom value
146+
* @param flingGestureEnabled when set to true dragging pointer builds up velocity. When last
147+
* pointer leaves Composable a movement invoked against friction till velocity drops down
148+
* to threshold
149+
* @param moveToBoundsEnabled when set to true if image zoom is lower than initial zoom or
150+
* panned out of image boundaries moves back to bounds with animation.
151+
* ##Note
152+
* Currently rotating back to borders is not available
109153
* @param zoomEnabled when set to true zoom is enabled
110154
* @param panEnabled when set to true pan is enabled
111155
* @param rotationEnabled when set to true rotation is enabled
156+
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
157+
* empty space on sides or edges of parent
112158
* @param keys are used to reset remember block to initial calculations. This can be used
113159
* when image, contentScale or any property changes which requires values to be reset to initial
114160
* values
115161
*/
116162
@Composable
117163
fun rememberEnhancedZoomState(
118164
imageSize: IntSize,
119-
containerSize: IntSize,
120165
initialZoom: Float = 1f,
121166
minZoom: Float = 1f,
122167
maxZoom: Float = 5f,
168+
flingGestureEnabled: Boolean = false,
169+
moveToBoundsEnabled: Boolean = true,
123170
zoomEnabled: Boolean = true,
124171
panEnabled: Boolean = true,
125172
rotationEnabled: Boolean = false,
@@ -129,14 +176,15 @@ fun rememberEnhancedZoomState(
129176
return remember(keys) {
130177
EnhancedZoomState(
131178
imageSize = imageSize,
132-
containerSize = containerSize,
133179
initialZoom = initialZoom,
134180
minZoom = minZoom,
135181
maxZoom = maxZoom,
182+
flingGestureEnabled = flingGestureEnabled,
183+
moveToBoundsEnabled = moveToBoundsEnabled,
136184
zoomEnabled = zoomEnabled,
137185
panEnabled = panEnabled,
138186
rotationEnabled = rotationEnabled,
139187
limitPan = limitPan
140188
)
141189
}
142-
}
190+
}

0 commit comments

Comments
 (0)