Skip to content

TransformGestureDemo.kt incorrectly updates offset when zooming #8

@calvin-godfrey

Description

@calvin-godfrey

Specifically, this bit right here when computing the new offset after accounting for zooming and panning:

            // For natural zooming and rotating, the centroid of the gesture should
            // be the fixed point where zooming and rotating occurs.
            // We compute where the centroid was (in the pre-transformed coordinate
            // space), and then compute where it will be after this delta.
            // We then compute what the new offset should be to keep the centroid
            // visually stationary for rotating and zooming, and also apply the pan.
            offset = (offset + gestureCentroid / oldScale).rotateBy(gestureRotate) -
                    (gestureCentroid / newScale + gesturePan / oldScale)

gestureCentroid uses a coordinate system where (0,0) is the top-left corner of the screen, but zooming uses a coordinate system where (0,0) is the center of the screen. As a result, all zooming gestures are biased towards the lower-right corner of the screen.

For example, to actually zoom towards the center of the screen, it would be necessary for offset to not change, which would require gestureCentroid to be (0,0) which would require pinching in the top left corner. Having the centroid actually be in the middle of the screen causes the screen to zoom more towards the lower-right corner. The fix is simple, to just change the coordinates for the centroid.

            val width = MyApplication.getScreenWidth()
            val height = MyApplication.getScreenHeight()
            val centroidRelativeToScreenCenter = gestureCentroid.minus(Offset(width / 2f, height / 2f))
            offset = (offset + centroidRelativeToScreenCenter / oldScale).rotateBy(gestureRotate) -
                    (centroidRelativeToScreenCenter / newScale + gesturePan / oldScale)

I would make a pull request, but I'm not familiar enough with Kotlin to know the proper way to include methods to get the screen width/height into this repo. I'll put them here, just for reference:

        fun getScreenWidth(): Int {
            val windowManager = (getContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager)
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                val windowMetrics = windowManager.currentWindowMetrics
                val bounds = windowMetrics.bounds
                val insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
                    WindowInsets.Type.systemBars()
                )
                if (getContext().resources.configuration.orientation
                    == Configuration.ORIENTATION_LANDSCAPE
                    && getContext().resources.configuration.smallestScreenWidthDp < 600
                ) { // landscape and phone
                    val navigationBarSize = insets.right + insets.left
                    bounds.width() - navigationBarSize
                } else { // portrait or tablet
                    bounds.width()
                }
            } else {
                val outMetrics = DisplayMetrics()
                windowManager.defaultDisplay.getMetrics(outMetrics)
                outMetrics.widthPixels
            }
        }

        fun getScreenHeight(): Int {
            val windowManager = (getContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager)
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                val insets: Insets = windowManager.currentWindowMetrics.windowInsets.getInsetsIgnoringVisibility(
                    WindowInsets.Type.systemBars()
                )

                // https://stackoverflow.com/questions/63407883/getting-screen-width-on-api-level-30-android-11-getdefaultdisplay-and-getme
                val windowMetrics = windowManager.currentWindowMetrics
                val bounds: Rect = windowMetrics.bounds
                bounds.height() - insets.top - insets.bottom
            } else {
                val outMetrics = DisplayMetrics()
                windowManager.defaultDisplay.getMetrics(outMetrics)
                outMetrics.heightPixels
            }
        }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions