改写了一个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" />