uu00soldier
uu00soldier
2017-11-22 08:31

SurfaceView缩放、拖拽、涂鸦功能,进行坐标换算

5
  • canvas
  • surfaceview
  • 涂鸦
  • 缩放
  • 拖拽

改写了一个surfaceView,之前有缩放、拖拽功能,根据我的应用需求,我需要加上涂鸦功能,
在这个过程中,我可以再上面画矩形。画矩形没有问题,但是在画好图形进行缩放的时候,画的矩形来回抖动,不能固定,在坐标换算的时候出问题了,一直找不到问题在哪里,求高人指点。
这里坐标换算,是换算成相对于bitmap的坐标,因为bitmap是等比例缩放的,不是铺满整个surfaceview的
调用源码:

        public MySurfaceView3 msvZoom;

         Bitmap bm = BitmapFactory.decodeFile(imgPath);
         msvZoom.setBitmap(bm);

自定义SurfaceView代码


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import com.github.niqdev.mjpeg.OnDrawCompleteListener;
import com.github.niqdev.mjpeg.bean.Data;

/**
 * @Author hyj
 * @Date 2017/10/19 19:16
 */
public class MySurfaceView3 extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
    private static final int NONE = 0;// 原始
    private static final int DRAG = 1;// 拖动
    private static final int ZOOM = 2;// 放大
    private int mStatus = NONE;

    private static final float MAX_ZOOM_SCALE = 5f;
    private static final float MIN_ZOOM_SCALE = 1.0f;
    private static final float FLOAT_TYPE = 1.0f;
    private float mCurrentMaxScale = MAX_ZOOM_SCALE;
    private float mCurrentScale = 1.0f;

    private Rect mRectSrc = new Rect(); // used for render image.  //要绘图的位置
    private Rect mRectDes = new Rect(); // used for store size of monitor. 要画图的部分

    private int mCenterX, mCenterY;
    int mSurfaceHeight, mSurfaceWidth, mImageHeight, mImageWidth;

    private PointF mStartPoint = new PointF();
    private float mStartDistance = 0f;

    private SurfaceHolder mSurHolder = null;
    private Bitmap mBitmap;

    /********************画图部分********************/
    private Paint shapePaint;
    private String paintColor = "FA1418";//当前画笔颜色
    private int paintWidth = 3;//画笔宽度
    private String action = Data.ACTION_DRAW;//画图动作
    private String style = Data.STYLE_RECTANGLE;//画图形状
    private float startX = 0, startY = 0, endX = 0, endY = 0;//位置比例
    private float sx = 0, sy = 0, ex = 0, ey = 0; //计算出来当前屏幕中真实位置
    private boolean isDrawShape = false;//当前是否处于画图状态

    private ArrayMap<String, Data> amShape;
    private Data tempShape;

    private OnDrawCompleteListener<Data> onDrawCompleteListener;//标记绘制完成

    /**
     * 还原所有画图的参数
     */
    private void restoreDrawParams() {
        startX = startY = endX = endY = 0;
        sx = sy = ex = ey = 0;
        isDrawShape = false;
        amShape.clear();
    }

    /**
     * 获得画图状态
     *
     * @return
     */
    public boolean isDrawShape() {
        return isDrawShape;
    }

    /**
     * 设置画图状态
     *
     * @param drawShape
     */
    public void setDrawShape(boolean drawShape) {
        isDrawShape = drawShape;
    }

    /**
     * 清空所所有标记、注解
     */
    public void addMarker(Data dataMarker) {
        if (null == dataMarker || TextUtils.isEmpty(dataMarker.getAction())) {
            return;
        }

        switch (dataMarker.getAction()) {
            case Data.ACTION_DRAW:
                amShape.put(dataMarker.getId(), dataMarker);
                break;

            case Data.ACTION_DEL:
                //TODO 删除指定的图形,预留接口
                break;

            case Data.ACTION_DELALL:
                restoreDrawParams();
                break;
        }
        showBitmap();
    }

    /**
     * 一个图形画完之后的回调事件
     *
     * @param onDrawCompleteListener
     */
    public void setOnDrawCompleteListener(OnDrawCompleteListener onDrawCompleteListener) {
        this.onDrawCompleteListener = onDrawCompleteListener;
    }

    /**
     * 初始化画图属性
     */
    private void initDrawShape() {
        amShape = new ArrayMap<String, Data>();

        shapePaint = new Paint();
        shapePaint.setAntiAlias(false);
        shapePaint.setStyle(Paint.Style.STROKE);
        shapePaint.setStrokeCap(Paint.Cap.ROUND);
        shapePaint.setStrokeJoin(Paint.Join.ROUND);
        shapePaint.setColor(Color.parseColor("#" + paintColor));
        shapePaint.setStrokeWidth(paintWidth);
    }

    /**
     * 计算点X到图片左上角距离与图片宽度的比值
     *
     * @param bmW    图片宽
     * @param pointX 点相对于屏幕左上角的X距离
     * @return
     */
    private float pointScreen2ImageX(int bmW, float pointX) {
        return (pointX / mWidthScale - mRectDes.left + mRectSrc.left) * 1.0f / (bmW);
    }

    /**
     * 计算点Y到图片左上角与图片高度的比值
     *
     * @param bmH    图片高度
     * @param pointY 点相对于屏幕左上角的Y距离
     * @return
     */
    private float pointScreen2ImageY(int bmH, float pointY) {
        return (pointY / mHeightScale - mRectDes.top + mRectSrc.top) * 1.0f / (bmH);
    }

    /**
     * 把相对于图片的宽比值转换成相对于屏幕上具体的点
     *
     * @param bmW    图片宽度
     * @param scaleX 点的X值相对于图片宽度的比值
     * @return
     */
    private float pointImage2ScreenX(int bmW, float scaleX) {
        return (scaleX * bmW + mRectDes.left - mRectSrc.left) * mWidthScale;
    }

    /**
     * 把相对于图片的高度壁纸转换成相对于屏幕上具体的点
     *
     * @param bmH    图片高度
     * @param scaleY 点的Y值相对于图片高度的比值
     * @return
     */
    private float pointImage2ScreenY(int bmH, float scaleY) {
        return (scaleY * bmH + mRectDes.top - mRectSrc.top) * mHeightScale;
    }

    /********************画图部分********************/

    public MySurfaceView3(Context context, AttributeSet attrs) {
        super(context, attrs);
        mSurHolder = getHolder();
        mSurHolder.addCallback(this);
        this.setOnTouchListener(this);
    }

    private void init() {
        mCurrentMaxScale = Math.max(MIN_ZOOM_SCALE,
                4 * Math.min(FLOAT_TYPE * mImageHeight / mSurfaceHeight, 1.0f * mImageWidth / mSurfaceWidth));
        mCurrentScale = MIN_ZOOM_SCALE;
        mCenterX = mImageWidth / 2;
        mCenterY = mImageHeight / 2;
        initDrawShape();
        calcRect();
    }

    private void adjustCenter() {
        int w = mRectSrc.right - mRectSrc.left;
        int h = mRectSrc.bottom - mRectSrc.top;

        if (mCenterX - w / 2 < 0) {
            mCenterX = w / 2;
            mRectSrc.left = 0;
            mRectSrc.right = w;
        } else if (mCenterX + w / 2 >= mImageWidth) {
            mCenterX = mImageWidth - w / 2;
            mRectSrc.right = mImageWidth;
            mRectSrc.left = mRectSrc.right - w;
        } else {
            mRectSrc.left = mCenterX - w / 2;
            mRectSrc.right = mRectSrc.left + w;
        }

        if (mCenterY - h / 2 < 0) {
            mCenterY = h / 2;
            mRectSrc.top = 0;
            mRectSrc.bottom = h;
        } else if (mCenterY + h / 2 >= mImageHeight) {
            mCenterY = mImageHeight - h / 2;
            mRectSrc.bottom = mImageHeight;
            mRectSrc.top = mRectSrc.bottom - h;
        } else {
            mRectSrc.top = mCenterY - h / 2;
            mRectSrc.bottom = mRectSrc.top + h;
        }
    }

    private float mWidthScale = 1.0f;//当前宽的缩放比例
    private float mHeightScale = 1.0f;//当前高的缩放比例
    float distanceX;
    float distancY;

    private void calcRect() {
        int w, h;
        float imageRatio, surfaceRatio;
        imageRatio = FLOAT_TYPE * mImageWidth / mImageHeight;
        surfaceRatio = FLOAT_TYPE * mSurfaceWidth / mSurfaceHeight;

        if (imageRatio < surfaceRatio) {
            h = mSurfaceHeight;
            w = (int) (h * imageRatio);
        } else {
            w = mSurfaceWidth;
            h = (int) (w / imageRatio);
        }

        if (mCurrentScale > MIN_ZOOM_SCALE) {   //如果显示区域超过屏幕宽高,则取屏幕宽高
            w = Math.min(mSurfaceWidth, (int) (w * mCurrentScale));
            h = Math.min(mSurfaceHeight, (int) (h * mCurrentScale));
        } else {
            mCurrentScale = MIN_ZOOM_SCALE;
        }

        String msg = "imageRatio:" + imageRatio + "   surfaceRatio:" + surfaceRatio + "   mCurrentScale:" + mCurrentScale
                + "     w:" + w + "     h:" + h
                + "      mSurfaceWidth:" + mSurfaceWidth + "     mSurfaceHeight:" + mSurfaceHeight
                + "      mImageWidth:" + mImageWidth + "     mImageHeight:" + mImageHeight;
        Log.i("TAG  radio", msg);

        mRectDes.left = (mSurfaceWidth - w) / 2;
        mRectDes.top = (mSurfaceHeight - h) / 2;
        mRectDes.right = mRectDes.left + w;
        mRectDes.bottom = mRectDes.top + h;

        float curImageRatio = FLOAT_TYPE * w / h;
        int h2, w2;
        if (curImageRatio > imageRatio) {
            h2 = (int) (mImageHeight / mCurrentScale);
            w2 = (int) (h2 * curImageRatio);
        } else {
            w2 = (int) (mImageWidth / mCurrentScale);
            h2 = (int) (w2 / curImageRatio);
        }

        mRectSrc.left = mCenterX - w2 / 2;
        mRectSrc.top = mCenterY - h2 / 2;
        mRectSrc.right = mRectSrc.left + w2;
        mRectSrc.bottom = mRectSrc.top + h2;

        distanceX = (mSurfaceWidth - w) / 2 - (mCenterX - w2 / 2);
        distancY = (mSurfaceHeight - h) / 2 - (mCenterY - h2 / 2);

        mWidthScale = w * FLOAT_TYPE / w2;//计算当前宽度缩放比例
        mHeightScale = h * FLOAT_TYPE / h2;//计算当前高度缩放比例
    }

    public void setMaxZoom(float value) {
        mCurrentMaxScale = value;
    }

    public void setBitmap(Bitmap b) {
        if (b == null) {
            return;
        }
        synchronized (MySurfaceView3.class) {
            mBitmap = b;
            if (mImageHeight != mBitmap.getHeight() || mImageWidth != mBitmap.getWidth()) {
                mImageHeight = mBitmap.getHeight();
                mImageWidth = mBitmap.getWidth();
                init();
            }
            showBitmap();
        }
    }

    private void showBitmap() {
        synchronized (MySurfaceView3.class) {
            Canvas c = getHolder().lockCanvas();
            if (c != null && mBitmap != null) {
                c.drawColor(Color.GRAY);
                c.drawBitmap(mBitmap, mRectSrc, mRectDes, null);
                drawShape2Bitmap(c);
                getHolder().unlockCanvasAndPost(c);
            }
        }
    }

    private void drawShape2Bitmap(Canvas c) {
        if (amShape != null && amShape.size() > 0) {
            for (Data obj : amShape.values()) {
                shapePaint.setStrokeWidth(obj.getBorderWidth());
                shapePaint.setColor(Color.parseColor(obj.getColor2Paint()));
                switch (obj.getStyle()) {
                    case Data.STYLE_RECTANGLE:
                        sx = pointImage2ScreenX(mImageWidth, obj.getStartPointX());
                        sy = pointImage2ScreenY(mImageHeight, obj.getStartPointY());
                        ex = pointImage2ScreenX(mImageWidth, obj.getEndPointX());
                        ey = pointImage2ScreenY(mImageHeight, obj.getEndPointY());
                        c.drawRect(sx, sy, ex, ey, shapePaint);
                        break;

                    case Data.STYLE_CIRCLE:
                        //TODO 预留接口画圆
                        break;
                }
            }
        }
        //将图片上的点转换为屏幕上的点
        if (startX != endX || startY != endY) {
            sx = pointImage2ScreenX(mImageWidth, startX);
            sy = pointImage2ScreenY(mImageHeight, startY);
            ex = pointImage2ScreenX(mImageWidth, endX);
            ey = pointImage2ScreenY(mImageHeight, endY);
            c.drawRect(sx, sy, ex, ey, shapePaint);

            String msg = "mRectSrc:" + mRectSrc.left + "," + mRectSrc.top + "," + mRectSrc.right + "," + mRectSrc.bottom;
            Log.i("TAG mRectSrc", msg);

            msg = "mRectDes:" + mRectDes.left + "," + mRectDes.top + "," + mRectDes.right + "," + mRectDes.bottom;
            Log.i("TAG mRectDes", msg);

            msg = "pintScale: " + startX + "," + startY + "," + endX + "," + endY;
            Log.i("TAG pintScale", msg);

            msg = "point:" + sx + "," + sy + "," + ex + "," + ey;
            Log.i("TAG point", msg);

            msg = "bitmap:" + mBitmap.getWidth() + "," + mBitmap.getHeight() + ",放大倍数:" + mCurrentScale + ",mWidthScale:" + mWidthScale + ",mHeightScale:" + mHeightScale;
            Log.i("TAG bitmap", msg);
        }
    }

    private void dragAction(MotionEvent event) {
        final int dragScale = 3;//放慢拖动速率
        synchronized (MySurfaceView3.class) {
            PointF currentPoint = new PointF();
            currentPoint.set(event.getX(), event.getY());
            int offsetX = (int) (currentPoint.x - mStartPoint.x) / dragScale;
            int offsetY = (int) (currentPoint.y - mStartPoint.y) / dragScale;
            mStartPoint = currentPoint;

            mCenterX -= offsetX;
            mCenterY -= offsetY;

            adjustCenter();
            showBitmap();
        }
    }

    private void zoomAcition(MotionEvent event) {
        synchronized (MySurfaceView3.class) {
            float newDist = spacing(event);
            float scale = newDist / mStartDistance;
            mStartDistance = newDist;

            mCurrentScale *= scale;
            mCurrentScale = Math.max(FLOAT_TYPE, Math.min(mCurrentScale, mCurrentMaxScale));

            calcRect();
            adjustCenter();
            showBitmap();
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (isDrawShape) {
            if (mRectDes.left > event.getX() || mRectDes.right < event.getX()) {
                return false;
            } else if (mRectDes.top > event.getY() || mRectDes.bottom < event.getY()) {
                return false;
            }

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startX = pointScreen2ImageX(mImageWidth, event.getX());
                    startY = pointScreen2ImageY(mImageHeight, event.getY());
                    endX = startX;
                    endY = startY;
                    tempShape = new Data(action, style, paintColor, paintWidth);
                    break;

                case MotionEvent.ACTION_MOVE:
                    endX = pointScreen2ImageX(mImageWidth, event.getX());
                    endY = pointScreen2ImageY(mImageHeight, event.getY());
                    break;

                case MotionEvent.ACTION_UP:
                    endX = pointScreen2ImageX(mImageWidth, event.getX());
                    endY = pointScreen2ImageY(mImageHeight, event.getY());

                    tempShape.setStartPoint(startX, startY);
                    tempShape.setEndPoint(endX, endY);
                    if (null != onDrawCompleteListener) {
                        tempShape = onDrawCompleteListener.onDrawCoomplete(tempShape);
                    }
                    amShape.put(tempShape.getId(), tempShape.clone());
                    break;
            }

            showBitmap();
            return true;
        }

        //缩放部分
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                mStartPoint.set(event.getX(), event.getY());
                mStatus = DRAG;
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                float distance = spacing(event);
                if (distance > 10f) {
                    mStatus = ZOOM;
                    mStartDistance = distance;
                }
                break;

            case MotionEvent.ACTION_MOVE:
                if (mStatus == DRAG) {
                    dragAction(event);
                } else {
                    if (event.getPointerCount() == 1)
                        return true;
                    zoomAcition(event);
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                mStatus = NONE;
                break;
        }

        return true;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
    }

    // 初始化
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        synchronized (MySurfaceView3.class) {
            mRectDes.set(0, 0, width, height);
            mSurfaceHeight = height;
            mSurfaceWidth = width;
            init();
            if (mBitmap != null) {
                showBitmap();
            }
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}

XML文件代码

     <MySurfaceView3
        android:id="@+id/calledMsvZoom"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
  • 点赞
  • 回答
  • 收藏
  • 复制链接分享

0条回答