Android:缩放 canvas 上的圆圈

Android: Scaling circles on a canvas with pinch

我目前可以在 canvas 上放置和拖动圆圈,但我无法理解如何允许用户使用捏合手势缩放圆圈。

圆是用'canvas.drawCircle(x,y,radius,paint)'画的。我最初的想法是使用自定义 'SimpleOnScaleGestureListener' 监听捏合手势,然后将所选圆的半径乘以比例因子。这产生了一些非常不可预测的结果,用两根手指快速点击,圆圈被缩放到微小或史诗般的比例。

这是我的自定义侦听器:

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

            @Override
            public boolean onScale(ScaleGestureDetector detector){
                mScaleFactor *= detector.getScaleFactor()

                if (isCircleToolSelected) {
                    // Checks if gesture was made inside of an existing circle
                    // CircleDrawing is a private class that holds x,y,radius,paint values
                    CircleDrawing circle = getCircle(detector.getFocusX(), 
                                                     detector.getFocusY());

                    if (circle != null) {
                       touchedCircle.radius *= mScaleFactor;
                    }
                }
                return true;
            }
        }

这会检测手势并知道捏合是否在我 canvas 的圆圈内,但应用的缩放完全失控且无法使用。

我只尝试在当前和之前的比例因子之间的差异超过某个阈值时进行缩放,但这只会让缩放更难预测且更不稳定。

如果有人实现了类似的东西,我会喜欢一些方向。

当你select圈

时你必须得到比例因子
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {

            if (isCircleToolSelected) {
                mScaleFactor *= detector.getScaleFactor()
                // Checks if gesture was made inside of an existing circle
                // CircleDrawing is a private class that holds x,y,radius,paintvalues
                CircleDrawing circle = getCircle(detector.getFocusX(), 
                                                 detector.getFocusY());

                if (circle != null) {
                   touchedCircle.radius *= mScaleFactor;
                }
            }
        return true;
    }
}

回答我自己的问题,以防其他人确信您必须使用 ScaleFactor 来缩放所有内容。对于圆的基本缩放,您只需要找到 getCurrentSpan() 的当前值和先前值之间的差异。

这是我最后使用的代码:

//Used elsewhere to detect if we've touched an existing circle
CircleDrawing touchedCircle;

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        private float lastSpan;
        private float lastRadius;

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {

            //If the user has not already selected a circle
            if (null == touchedCircle) {

                // Check if we started scale gesture within existing circle
                touchedCircle = getCircle(detector.getFocusX(), detector.getFocusY());
            }

            //If a circle has been selected, save radius and span
            if (touchedCircle != null) {
                lastRadius = touchedCircle.radius;
                lastSpan = detector.getCurrentSpan();
            }
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector){

            //If the user has the shapes tool selected - handled elsewhere
            if (isShapesSelected) {

                //If the user is touching a circle
                if (touchedCircle != null) {

                    //Value used to calculate new radius
                    float currentSpan = detector.getCurrentSpan();
                    float diameterDiff = currentSpan - lastSpan;

                    //Keep track of scaling direction for min and max sizes
                    boolean scalingUp = diameterDiff > 0;

                    //Only scale within radius sizes between 150 and 500
                    //Likely will change these values to relate to size of canvas
                    if ((!scalingUp && (lastRadius >= 150)) || (scalingUp && (lastRadius < 500))) {

                        //Calculate and set new radius
                        touchedCircle.radius += (diameterDiff)/2;

                        //Save radius and span for next frame
                        lastRadius = touchedCircle.radius;
                        lastSpan = detector.getCurrentSpan();
                    }
                }
            }
            return true;
        }
    }