SurfaceView 中的缩放和拖动功能

Zooming and drag feature in SurfaceView

我正在尝试创建一个可以缩放和拖动的 SurfaceView。它实现了直接绘制到 canvas

的 HTTP 图像流

我试过下面的代码,它有点工作...但它给我带来了边界问题。不知道为什么。有帮助吗?

全流:

放大后的图片:

在第二张图片中,您可以看到多条不需要存在的绿线。

这是处理此流的 class:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceView;
import android.view.WindowManager;

/**
 * Created by fil on 07/12/15.
 */
public class ZoomSurfaceView extends SurfaceView {
    //These two constants specify the minimum and maximum zoom
    private static float MIN_ZOOM = 1f;
    private static float MAX_ZOOM = 5f;

    private float scaleFactor = 1.f;
    private ScaleGestureDetector detector;

    //These constants specify the mode that we're in
    private static int NONE = 0;
    private static int DRAG = 1;
    private static int ZOOM = 2;

    private boolean dragged = false;
    private float displayWidth;
    private float displayHeight;

    private int mode;

    //These two variables keep track of the X and Y coordinate of the finger when it first
    //touches the screen
    private float startX = 0f;
    private float startY = 0f;

    //These two variables keep track of the amount we need to translate the canvas along the X
    //and the Y coordinate
    private float translateX = 0f;
    private float translateY = 0f;

    //These two variables keep track of the amount we translated the X and Y coordinates, the last time we
    //panned.
    private float previousTranslateX = 0f;
    private float previousTranslateY = 0f;

    private final Paint p = new Paint();

    private void init(Context context){
        detector = new ScaleGestureDetector(getContext(), new ScaleListener());
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();

        displayWidth = display.getWidth();
        displayHeight = display.getHeight();
    }

    public ZoomSurfaceView(Context context) {
        super(context);
        init(context);
    }

    public ZoomSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public ZoomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    public void resetZoom() {

    }

    public void drawBitmap(Canvas canvas, Bitmap b, Rect rect){

        canvas.save();

        //If translateX times -1 is lesser than zero, letfs set it to zero. This takes care of the left bound
        if((translateX * -1) > (scaleFactor - 1) * displayWidth)
        {
            translateX = (1 - scaleFactor) * displayWidth;
        }

        if(translateY * -1 > (scaleFactor - 1) * displayHeight)
        {
            translateY = (1 - scaleFactor) * displayHeight;
        }

        //We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
        //because the translation amount also gets scaled according to how much we've zoomed into the canvas.
        canvas.translate(translateX / scaleFactor, translateY / scaleFactor);

        //We're going to scale the X and Y coordinates by the same amount
        canvas.scale(scaleFactor, scaleFactor);

        canvas.drawBitmap(b, null, rect, p);

        /* The rest of your canvas-drawing code */
        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
    {
        @Override
        public boolean onScale(ScaleGestureDetector detector)
        {
            scaleFactor *= detector.getScaleFactor();
            scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        switch (event.getAction() & MotionEvent.ACTION_MASK)
        {
            case MotionEvent.ACTION_DOWN:
                mode = DRAG;

                //We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
                //amount for each coordinates This works even when we are translating the first time because the initial
                //values for these two variables is zero.
                startX = event.getX() - previousTranslateX;
                startY = event.getY() - previousTranslateY;
                break;

            case MotionEvent.ACTION_MOVE:
                translateX = event.getX() - startX;
                translateY = event.getY() - startY;

                //We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
                //This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
                double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
                        Math.pow(event.getY() - (startY + previousTranslateY), 2));

                if(distance > 0)
                {
                    dragged = true;
                    distance *= scaleFactor;
                }
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                break;

            case MotionEvent.ACTION_UP:
                mode = NONE;
                dragged = false;

                //All fingers went up, so letfs save the value of translateX and translateY into previousTranslateX and
                //previousTranslate
                previousTranslateX = translateX;
                previousTranslateY = translateY;
                break;

            case MotionEvent.ACTION_POINTER_UP:
                mode = DRAG;

                //This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
                //and previousTranslateY when the second finger goes up
                previousTranslateX = translateX;
                previousTranslateY = translateY;
                break;
        }

        detector.onTouchEvent(event);

        //We redraw the canvas only in the following cases:
        //
        // o The mode is ZOOM
        // OR
        // o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
        // set to true (meaning the finger has actually moved)
        if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM)
        {
            invalidate();
        }

        return true;
    }
}

将框架添加到表面的代码如下:

if (!b.isRecycled()){
    try {
        Rect rect = new Rect(0, 0, frame.getWidth(), frame.getHeight());
        Canvas canvas = frame.getHolder().lockCanvas();
        synchronized (frame.getHolder()) {
            if (!b.isRecycled()) {
                frame.drawBitmap(canvas, b, rect);
                b.recycle();
            }
        }
        frame.getHolder().unlockCanvasAndPost(canvas);
    } catch (java.lang.RuntimeException exc){
        Dbg.d("ERROR", exc);
    }
    lastBitmap = b;
}

您发布的代码不完整,所以很难说是什么问题。我确实将您的代码放到了一个快速演示项目中,但没有发现任何边框问题。

只要看一下屏幕截图:您的图像数据是否有可能以某种方式环绕?第二张截图看起来像是在图像顶部绘制了底部边框。如果没有可重现的代码,又很难说。

可能会尝试在重绘位图之前重绘背景

canvas.drawRect(rect, backgroundPaint);
frame.drawBitmap(canvas, b, rect);