@@ -23,11 +23,13 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
23
23
private val gestureDetector = GestureDetector (context, GestureListener ())
24
24
25
25
// Zoom and pan state
26
+ private var renderQuality = RenderQuality .NORMAL
26
27
private var scaleFactor = 1f
27
28
private var isZoomEnabled = true
28
- private var maxZoom = MAX_ZOOM
29
+ private val maxZoom get() = MAX_ZOOM * renderQuality.qualityMultiplier
29
30
private var zoomDuration = ZOOM_DURATION
30
31
private var isZoomingInProgress = false
32
+ private var isOnTop = true
31
33
32
34
// Panning offsets and touch memory
33
35
private var lastTouchX = 0f
@@ -36,6 +38,7 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
36
38
private var posY = 0f
37
39
38
40
private var zoomChangeListener: ((Boolean , Float ) -> Unit )? = null
41
+ private var scrollListener: ((Boolean ) -> Unit )? = null
39
42
40
43
init {
41
44
setWillNotDraw(false )
@@ -53,6 +56,14 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
53
56
zoomChangeListener = listener
54
57
}
55
58
59
+ fun setScrollListener (listener : (isScrolledToTop: Boolean ) -> Unit ) {
60
+ scrollListener = listener
61
+ }
62
+
63
+ fun setRenderQuality (quality : RenderQuality ) {
64
+ renderQuality = quality
65
+ }
66
+
56
67
/* *
57
68
* Handles touch interactions — zoom, pan, and scroll.
58
69
*/
@@ -67,42 +78,10 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
67
78
}
68
79
69
80
when (ev.actionMasked) {
70
- MotionEvent .ACTION_DOWN -> {
71
- lastTouchX = ev.x
72
- lastTouchY = ev.y
73
- activePointerId = ev.getPointerId(0 )
74
- }
75
- MotionEvent .ACTION_MOVE -> {
76
- if (! scaleDetector.isInProgress && scaleFactor > 1f ) {
77
- val pointerIndex = ev.findPointerIndex(activePointerId)
78
- if (pointerIndex != - 1 ) {
79
- val x = ev.getX(pointerIndex)
80
- val y = ev.getY(pointerIndex)
81
- val dx = x - lastTouchX
82
- val dy = y - lastTouchY
83
- posX + = dx
84
- posY + = dy
85
- clampPosition()
86
- invalidate()
87
-
88
- lastTouchX = x
89
- lastTouchY = y
90
- }
91
- }
92
- }
93
- MotionEvent .ACTION_POINTER_UP -> {
94
- val pointerIndex = ev.actionIndex
95
- val pointerId = ev.getPointerId(pointerIndex)
96
- if (pointerId == activePointerId) {
97
- val newPointerIndex = if (pointerIndex == 0 ) 1 else 0
98
- lastTouchX = ev.getX(newPointerIndex)
99
- lastTouchY = ev.getY(newPointerIndex)
100
- activePointerId = ev.getPointerId(newPointerIndex)
101
- }
102
- }
103
- MotionEvent .ACTION_CANCEL -> {
104
- activePointerId = INVALID_POINTER_ID
105
- }
81
+ MotionEvent .ACTION_DOWN -> onDown(ev = ev)
82
+ MotionEvent .ACTION_MOVE -> onMove(ev = ev)
83
+ MotionEvent .ACTION_POINTER_UP -> onUp(ev = ev)
84
+ MotionEvent .ACTION_CANCEL -> onCancel(ev = ev)
106
85
}
107
86
108
87
return if (scaleFactor > 1f ) true else super .onTouchEvent(ev)
@@ -171,13 +150,65 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
171
150
return (averageHeight * itemCount * scaleFactor).toInt()
172
151
}
173
152
153
+ private fun onDown (ev : MotionEvent ) {
154
+ lastTouchX = ev.x
155
+ lastTouchY = ev.y
156
+ activePointerId = ev.getPointerId(0 )
157
+ }
158
+
159
+ private fun onMove (ev : MotionEvent ) {
160
+ val pointerIndex = ev.findPointerIndex(activePointerId)
161
+ if (pointerIndex != - 1 ) {
162
+ if (! scaleDetector.isInProgress && scaleFactor > 1f ) {
163
+ val x = ev.getX(pointerIndex)
164
+ val y = ev.getY(pointerIndex)
165
+ val dx = x - lastTouchX
166
+ val dy = y - lastTouchY
167
+ posX + = dx
168
+ posY + = dy
169
+ clampPosition()
170
+ invalidate()
171
+
172
+ lastTouchX = x
173
+ lastTouchY = y
174
+ }
175
+
176
+ val isScrolledOut = ! scaleDetector.isInProgress && scaleFactor == 1f
177
+ val currentScrollOffset = computeVerticalScrollOffset()
178
+ if (currentScrollOffset == 0 && isScrolledOut && ! isOnTop) {
179
+ scrollListener?.invoke(true )
180
+ isOnTop = true
181
+ } else if ((currentScrollOffset != 0 || isScrolledOut.not ()) && isOnTop) {
182
+ scrollListener?.invoke(false )
183
+ isOnTop = false
184
+ }
185
+ }
186
+ }
187
+
188
+ private fun onUp (ev : MotionEvent ) {
189
+ val pointerIndex = ev.actionIndex
190
+ val pointerId = ev.getPointerId(pointerIndex)
191
+ if (pointerId == activePointerId) {
192
+ val newPointerIndex = if (pointerIndex == 0 ) 1 else 0
193
+ lastTouchX = ev.getX(newPointerIndex)
194
+ lastTouchY = ev.getY(newPointerIndex)
195
+ activePointerId = ev.getPointerId(newPointerIndex)
196
+ }
197
+ }
198
+
199
+ private fun onCancel (ev : MotionEvent ) {
200
+ activePointerId = INVALID_POINTER_ID
201
+ }
202
+
174
203
/* *
175
204
* Handles pinch-to-zoom scaling with focal-point centering.
176
205
*/
177
206
private inner class ScaleListener : ScaleGestureDetector .SimpleOnScaleGestureListener () {
178
207
override fun onScaleBegin (detector : ScaleGestureDetector ): Boolean {
179
208
isZoomingInProgress = true
180
209
suppressLayout(true )
210
+ scrollListener?.invoke(false )
211
+ isOnTop = false
181
212
return true
182
213
}
183
214
0 commit comments