Zoomable画布(SurfaceView)

时间:2013-12-26 10:59:44

标签: android surfaceview ondraw pinchzoom

在过去的几周里,我一直在寻找合适的源代码,展示如何在自定义视图上启用缩放和平移功能。我找到的所有解决方案都存在一些问题。例如,在缩放(双指)操作之后释放一个手指时,移动/缩放不够平滑或者视图跳跃。所以我想出了一个我希望与您分享的修改后的解决方案。欢迎提出建议和改进。

有什么不同?

在每个单个事件之间计算任何交互(平移,缩放)的差异(距离向量)并使用它来设置新值是不好的做法。如果这样做,操作看起来不平滑,视图可能会闪烁(在某些像素中跳转)。更好的方法是在动作开始时(onScaleBegin,touch-down)记住值,并计算每个事件与这些起始值相比的距离。

您可以在onTouchEvent中处理手指索引,以更好地区分平移/移动和缩放/缩放交互。

public class CanvasView extends SurfaceView implements SurfaceHolder.Callback {
    final static String TAG = "CanvasView";
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    ScaleGestureDetector mScaleDetector;
    InteractionMode mode;
    Matrix mMatrix = new Matrix();
    float mScaleFactor = 1.f;
    float mTouchX;
    float mTouchY;
    float mTouchBackupX;
    float mTouchBackupY;
    float mTouchDownX;
    float mTouchDownY;
    Rect boundingBox = new Rect();

    public CanvasView(Context context) {
        super(context);

        // we need to get a call for onSurfaceCreated
        SurfaceHolder sh = this.getHolder();
        sh.addCallback(this);

        // for zooming (scaling) the view with two fingers
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

        boundingBox.set(0, 0, 1024, 768);

        paint.setColor(Color.GREEN);
        paint.setStyle(Style.STROKE);

        setFocusable(true);

        // initial center/touch point of the view (otherwise the view would jump
        // around on first pan/move touch
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        mTouchX = metrics.widthPixels / 2;
        mTouchY = metrics.heightPixels / 2;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleDetector.onTouchEvent(event);

        if (!this.mScaleDetector.isInProgress()) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                // similar to ScaleListener.onScaleEnd (as long as we don't
                // handle indices of touch events)
                mode = InteractionMode.None;
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "Touch down event");

                mTouchDownX = event.getX();
                mTouchDownY = event.getY();
                mTouchBackupX = mTouchX;
                mTouchBackupY = mTouchY;

                // pan/move started
                mode = InteractionMode.Pan;
                break;
            case MotionEvent.ACTION_MOVE:
                // make sure we don't handle the last move event when the first
                // finger is still down and the second finger is lifted up
                // already after a zoom/scale interaction. see
                // ScaleListener.onScaleEnd
                if (mode == InteractionMode.Pan) {
                    Log.d(TAG, "Touch move event");

                    // get current location
                    final float x = event.getX();
                    final float y = event.getY();

                    // get distance vector from where the finger touched down to
                    // current location
                    final float diffX = x - mTouchDownX;
                    final float diffY = y - mTouchDownY;

                    mTouchX = mTouchBackupX + diffX;
                    mTouchY = mTouchBackupY + diffY;

                    CalculateMatrix(true);
                }

                break;
            }
        }

        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

        int saveCount = canvas.getSaveCount();
        canvas.save();
        canvas.concat(mMatrix);

        canvas.drawColor(Color.BLACK);
        canvas.drawRect(boundingBox, paint);

        canvas.restoreToCount(saveCount);
    }

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
        // otherwise onDraw(Canvas) won't be called
        this.setWillNotDraw(false);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
    }

    void CalculateMatrix(boolean invalidate) {
        float sizeX = this.getWidth() / 2;
        float sizeY = this.getHeight() / 2;

        mMatrix.reset();

        // move the view so that it's center point is located in 0,0
        mMatrix.postTranslate(-sizeX, -sizeY);

        // scale the view
        mMatrix.postScale(mScaleFactor, mScaleFactor);

        // re-move the view to it's desired location
        mMatrix.postTranslate(mTouchX, mTouchY);

        if (invalidate)
            invalidate(); // re-draw
    }

    private class ScaleListener extends
            ScaleGestureDetector.SimpleOnScaleGestureListener {

        float mFocusStartX;
        float mFocusStartY;
        float mZoomBackupX;
        float mZoomBackupY;

        public ScaleListener() {
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {

            mode = InteractionMode.Zoom;

            mFocusStartX = detector.getFocusX();
            mFocusStartY = detector.getFocusY();
            mZoomBackupX = mTouchX;
            mZoomBackupY = mTouchY;

            return super.onScaleBegin(detector);
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {

            mode = InteractionMode.None;

            super.onScaleEnd(detector);
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {

            if (mode != InteractionMode.Zoom)
                return true;

            Log.d(TAG, "Touch scale event");

            // get current scale and fix its value
            float scale = detector.getScaleFactor();
            mScaleFactor *= scale;
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            // get current focal point between both fingers (changes due to
            // movement)
            float focusX = detector.getFocusX();
            float focusY = detector.getFocusY();

            // get distance vector from initial event (onScaleBegin) to current
            float diffX = focusX - mFocusStartX;
            float diffY = focusY - mFocusStartY;

            // scale the distance vector accordingly
            diffX *= scale;
            diffY *= scale;

            // set new touch position
            mTouchX = mZoomBackupX + diffX;
            mTouchY = mZoomBackupY + diffY;

            CalculateMatrix(true);

            return true;
        }

    }
}

0 个答案:

没有答案