@@ -23,11 +23,13 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
2323    private  val  gestureDetector =  GestureDetector (context, GestureListener ())
2424
2525    //  Zoom and pan state
26+     private  var  renderQuality =  RenderQuality .NORMAL 
2627    private  var  scaleFactor =  1f 
2728    private  var  isZoomEnabled =  true 
28-     private  var  maxZoom =  MAX_ZOOM 
29+     private  val  maxZoom get()  =  MAX_ZOOM   *  renderQuality.qualityMultiplier 
2930    private  var  zoomDuration =  ZOOM_DURATION 
3031    private  var  isZoomingInProgress =  false 
32+     private  var  isOnTop =  true 
3133
3234    //  Panning offsets and touch memory
3335    private  var  lastTouchX =  0f 
@@ -36,6 +38,7 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
3638    private  var  posY =  0f 
3739
3840    private  var  zoomChangeListener:  ((Boolean , Float ) ->  Unit )?  =  null 
41+     private  var  scrollListener:  ((Boolean ) ->  Unit )?  =  null 
3942
4043    init  {
4144        setWillNotDraw(false )
@@ -53,6 +56,14 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
5356        zoomChangeListener =  listener
5457    }
5558
59+     fun  setScrollListener (listener :  (isScrolledToTop: Boolean ) ->  Unit ) {
60+         scrollListener =  listener
61+     }
62+ 
63+     fun  setRenderQuality (quality :  RenderQuality ) {
64+         renderQuality =  quality
65+     }
66+ 
5667    /* *
5768     * Handles touch interactions — zoom, pan, and scroll. 
5869     */  
@@ -67,42 +78,10 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
6778        }
6879
6980        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)
10685        }
10786
10887        return  if  (scaleFactor >  1f ) true  else  super .onTouchEvent(ev)
@@ -171,13 +150,65 @@ class PinchZoomRecyclerView @JvmOverloads constructor(
171150        return  (averageHeight *  itemCount *  scaleFactor).toInt()
172151    }
173152
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+ 
174203    /* *
175204     * Handles pinch-to-zoom scaling with focal-point centering. 
176205     */  
177206    private  inner  class  ScaleListener  : ScaleGestureDetector .SimpleOnScaleGestureListener () {
178207        override  fun  onScaleBegin (detector :  ScaleGestureDetector ): Boolean  {
179208            isZoomingInProgress =  true 
180209            suppressLayout(true )
210+             scrollListener?.invoke(false )
211+             isOnTop =  false 
181212            return  true 
182213        }
183214
0 commit comments