Q: How to implement pan and zoom on the entire RecyclerView I have my own custom view that extends RecyclerView and I use it to render a document with multiple pages. Each item in the adapter is a page.
I want to add the ability to zoom and pan the entire document (not each page individually).
I have tried following the Android guide here but I still can't get both zoom and pan to work properly.
My current approach is this:


*

*Have a GestureDetector to detect pan gestures

*Have a ScaleGestureDetector to detect pinch gestures

*When each of these detectors register events, save the information regarding the current scale factor and the pan, call invalidate() and modify the Canvas in the drawChild method appropriately.


It is the last step that I'm unsure how to implement properly.
I'm using this data class to keep track of the view's current pan and zoom:
private data class PanAndZoom(
    var scaleFactor: Float, 
    var focusX: Float, 
    var focusY: Float, 
    var panX: Float , 
    var panY: Float
)

// field in my custom view initialised like this:
private val panAndZoom = PanAndZoom(1f, 0f, 0f, 0f, 0f)

and here is how I update the values when gestures are received:
private val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScale(detector: ScaleGestureDetector): Boolean {
        panAndZoom.scaleFactor *= detector.scaleFactor
        panAndZoom.focusX = detector.focusX
        panAndZoom.focusY = detector.focusY
        invalidate()
        return true
    }
}
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
        panAndZoom.panX -= distanceX
        panAndZoom.panY -= distanceY
        invalidate()
        return true
    }
}

I override two methods in my custom view, one to pass touch events to my gesture detectors and one to draw child view:
override fun onTouchEvent(e: MotionEvent): Boolean {
    gestureDetector.onTouchEvent(e)
    scaleGestureDetector.onTouchEvent(e)
    return super.onTouchEvent(e)
}

override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
    val save = canvas.save()
    canvas.scale(panAndZoom.scaleFactor, panAndZoom.scaleFactor, panAndZoom.focusX, panAndZoom.focusY)
    canvas.translate(panAndZoom.panX, panAndZoom.panY)
    val result = super.drawChild(canvas, child, drawingTime)
    canvas.restoreToCount(save)
    return result
}

However this doesn't give the expected result, namely: 


*

*I am able to overscroll outside the RV altogether (I realise I'm not restricting my pan values, but how do I do that)?

*Pan seems to be going very fast when zoomed in (how do I scale my pan values so that they respect the scale factor)?

*If I am on page 2 of the document, and I zoom in, then pan to page 3, the layout manager doesn't realise that the view was actually scrolled (meaning, calling findFirstVisibleItem() will return the second page when it isn't actually visible.

*Most likely related to the point above: I can pan past a page, and because the layout manager doesn't realise that another view is now displayed it doesn't render it (because it still thinks that view is hidden from view and so it optimises by not rendering it).

