/**
* Author:guojiaojiao
* Date:2024/1/19 11:11
* Description:
*/
public class MyMap extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = MyMap.class.getSimpleName();
private static final long DOUBLE_CLICK_TIME_SPACE = 300;
private final List<MarkObject> markList = new ArrayList();
Canvas canvas = null;
private float mCurrentScaleMax;//最大缩放值
private float mCurrentScale = 1.0f;//当前缩放值
private float mCurrentScaleMin;//最小缩放值
private float windowWidth, windowHeight;//屏幕的宽和高
private Bitmap mBitmap;//当前的底图
private Paint mPaint;
private PointF mStartPoint;//起始坐标
private volatile PointF mapCenter;// mapCenter表示地图中心在屏幕上的坐标
private long lastClickTime;// 记录上一次点击屏幕的时间,以判断双击事件
private Status mStatus = Status.NONE;
private float oldRate = 1;
private float oldDist = 1;
private float offsetX, offsetY;
private boolean isShu = true;
private DrawThread mDrawThread;
Runnable runnable = new Runnable() {
@Override
public void run() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
}
for (MarkObject object : markList) {
if (object.getmBitmap() != null) {
object.getmBitmap().recycle();
}
}
mDrawThread.mHandler.getLooper().quit();
}
};
public MyMap(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}
public MyMap(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}
public MyMap(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}
private void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// 获取屏幕的宽和高
windowWidth = getResources().getDisplayMetrics().widthPixels;
windowHeight = getResources().getDisplayMetrics().heightPixels;
mPaint = new Paint();
mStartPoint = new PointF();
mapCenter = new PointF();
mDrawThread = new DrawThread();// 开启异步线程绘图
mDrawThread.start();
}
public void setBitmap(Bitmap bitmap) {
// LogUtils.e("draw", Thread.currentThread());
if (bitmap == mBitmap || bitmap == null) {
LogUtils.e("draw", "bitmap====is null!!!!!!!!");
return;
}
if (bitmap.isRecycled()) {
LogUtils.e("draw", "setBimap bitmap isRecycler!!!!!!!!");
return;
}
if (mDrawThread != null && mDrawThread.mHandler != null) {
mDrawThread.mHandler.removeCallbacksAndMessages(null);
Runnable runnable = new Runnable() {
@Override
public void run() {
// 把你的setBitMap放到这个线程里来
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
}
if (bitmap.isRecycled()) {
return;
}
mBitmap = bitmap;
// 设置最小缩放为铺满屏幕,最大缩放为最小缩放的4倍
mCurrentScaleMin = Math.min(windowHeight / mBitmap.getHeight(),
windowWidth / mBitmap.getWidth());
mCurrentScale = mCurrentScaleMin;
mCurrentScaleMax = mCurrentScaleMin * 5;
// mCurrentScaleMax=Math.max(windowHeight / mBitmap.getHeight(),
//
// windowWidth / mBitmap.getWidth());
// mapCenter.set(mBitmap.getWidth() * mCurrentScale / 2,
//
// mBitmap.getHeight() * mCurrentScale / 2);
mapCenter.set(windowWidth / 2, windowHeight / 2);
//float bitmapRatio = mBitmap.getHeight() / mBitmap.getWidth();
float bitmapRatio = mBitmap.getHeight() / mBitmap.getWidth();
float winRatio = windowHeight / windowWidth;
// 判断屏幕铺满的情况,isShu为true表示屏幕横向被铺满,为false表示屏幕纵向被铺满
isShu = bitmapRatio <= winRatio;
}
};
FutureTask<Void> task = new FutureTask<Void>(runnable, null);
boolean success = mDrawThread.mHandler.post(task);
if (success) {
try {
task.get();
} catch (Throwable e) {
e.printStackTrace();
}
} else {
}
}
draw();
}
/**
* 为当前地图添加标记
*
* @param object
*/
public void addMark(MarkObject object) {
markList.add(object);
draw();
}
public void clearMark() {
markList.clear();
draw();
}
/**
* 地图放大
*/
public void zoomIn() {
mCurrentScale *= 1.5f;
if (mCurrentScale > mCurrentScaleMax) {
mCurrentScale = mCurrentScaleMax;
}
draw();
}
/**
* 地图缩小
*/
public void zoomOut() {
mCurrentScale /= 1.5f;
if (mCurrentScale < mCurrentScaleMin) {
mCurrentScale = mCurrentScaleMin;
}
if (isShu) {
if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {
mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
} else if (mapCenter.x + mBitmap.getWidth() * mCurrentScale / 2 < windowWidth) {
mapCenter.x = windowWidth - mBitmap.getWidth() * mCurrentScale
/ 2;
}
if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {
mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
}
} else {
if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {
mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
} else if (mapCenter.y + mBitmap.getHeight() * mCurrentScale / 2 < windowHeight) {
mapCenter.y = windowHeight - mBitmap.getHeight()
* mCurrentScale / 2;
}
if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {
mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
}
}
draw();
}
public void onDestory() {
FutureTask<Void> task = new FutureTask<Void>(runnable, null);
if (mDrawThread != null && mDrawThread.mHandler != null) {
boolean success = mDrawThread.mHandler.post(task);
if (success) {
try {
task.get(100, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
/* if (mDrawThread != null) {
mDrawThread.mHandler.sendEmptyMessage(1);
}*/
}
// 处理拖拽事件
private void drag(MotionEvent event) {
PointF currentPoint = new PointF();
currentPoint.set(event.getX(), event.getY());
offsetX = currentPoint.x - mStartPoint.x;
offsetY = currentPoint.y - mStartPoint.y;
// 以下是进行判断,防止出现图片拖拽离开屏幕
if (mBitmap != null && !mBitmap.isRecycled()) {
if (offsetX > 0
&& mapCenter.x + offsetX - mBitmap.getWidth() * mCurrentScale
/ 2 > 0) {
offsetX = 0;
}
}
if (offsetX < 0
&& mapCenter.x + offsetX + mBitmap.getWidth() * mCurrentScale
/ 2 < windowWidth) {
offsetX = 0;
}
if (offsetY > 0
&& mapCenter.y + offsetY - mBitmap.getHeight() * mCurrentScale
/ 2 > 0) {
offsetY = 0;
}
if (offsetY < 0
&& mapCenter.y + offsetY + mBitmap.getHeight() * mCurrentScale
/ 2 < windowHeight) {
offsetY = 0;
}
mapCenter.x += offsetX;
mapCenter.y += offsetY;
draw();
mStartPoint = currentPoint;
}
// 处理多点触控缩放事件
private void zoomAction(MotionEvent event) {
float newDist = spacing(event);
if (newDist > 10.0f) {
mCurrentScale = oldRate * (newDist / oldDist);
if (mCurrentScale < mCurrentScaleMin) {
mCurrentScale = mCurrentScaleMin;
} else if (mCurrentScale > mCurrentScaleMax) {
mCurrentScale = mCurrentScaleMax;
}
if (isShu) {
if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {
mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
// mapCenter.x=windowWidth/2;
} else if (mapCenter.x + mBitmap.getWidth() * mCurrentScale / 2 < windowWidth) {
mapCenter.x = windowWidth - mBitmap.getWidth()
* mCurrentScale / 2;
// mapCenter.x = windowWidth - mBitmap.getWidth()
//
// / 2;
}
if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {
// mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2 + (windowHeight - mBitmap.getHeight() * mCurrentScale) / 2;
// mapCenter.y=windowHeight/2-(mBitmap.getHeight()*mCurrentScale)+(mBitmap.getHeight()*mCurrentScale)/2;
}
} else {
if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {
// mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2 + (windowHeight - mBitmap.getHeight() * mCurrentScale) / 2;
} else if (mapCenter.y + mBitmap.getHeight() * mCurrentScale
/ 2 < windowHeight) {
mapCenter.y = windowHeight - mBitmap.getHeight()
* mCurrentScale / 2;
}
if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {
mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
// mapCenter.x = windowWidth / 2;
}
}
}
draw();
}
// 处理点击标记的事件
private void clickAction(MotionEvent event) {
int clickX = (int) event.getX();
int clickY = (int) event.getY();
for (MarkObject object : markList) {
Bitmap location = object.getmBitmap();
int objX = (int) (mapCenter.x - location.getWidth() / 2
- mBitmap.getWidth() * mCurrentScale / 2 + mBitmap
.getWidth() * object.getMapX() * mCurrentScale);
int objY = (int) (mapCenter.y - location.getHeight()
- mBitmap.getHeight() * mCurrentScale / 2 + mBitmap
.getHeight() * object.getMapY() * mCurrentScale);
// 判断当前object是否包含触摸点,在这里为了得到更好的点击效果,我将标记的区域放大了
if (objX - location.getWidth() < clickX
&& objX + location.getWidth() > clickX
&& objY + location.getHeight() > clickY
&& objY - location.getHeight() < clickY) {
if (object.getMarkListener() != null) {
object.getMarkListener()
.onMarkClick(clickX, clickY, object.getScenicSpotViewSpot());
}
break;
}
}
}
// 计算两个触摸点的距离
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);
}
private void draw() {
Log.e("gjj", "=========draw=====" + Integer.toHexString(getWindowVisibility()));
if (mDrawThread != null && mDrawThread.mHandler != null
&& getWindowVisibility() == VISIBLE) {
mDrawThread.mHandler.sendEmptyMessage(0);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if (event.getPointerCount() == 1) {
// 如果两次点击时间间隔小于一定值,则默认为双击事件
if (event.getEventTime() - lastClickTime < DOUBLE_CLICK_TIME_SPACE) {
zoomIn();
} else {
mStartPoint.set(event.getX(), event.getY());
mStatus = Status.DRAG;
}
}
lastClickTime = event.getEventTime();
break;
case MotionEvent.ACTION_POINTER_DOWN:
float distance = spacing(event);
if (distance > 10f) {
mStatus = Status.ZOOM;
oldDist = distance;
}
break;
case MotionEvent.ACTION_MOVE:
if (mStatus == Status.DRAG) {
try {
drag(event);
} catch (Exception e) {
e.printStackTrace();
}
} else if (mStatus == Status.ZOOM) {
zoomAction(event);
}
break;
case MotionEvent.ACTION_UP:
if (mStatus != Status.ZOOM) {
clickAction(event);
}
case MotionEvent.ACTION_POINTER_UP:
oldRate = mCurrentScale;
mStatus = Status.NONE;
break;
default:
break;
}
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// LogUtils.e("draw","surfaceCreated()");
Log.e("gjj", "surfaceCreated:");
draw();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
Log.e("gjj", "onWindowVisibilityChanged:" + Integer.toHexString(visibility));
super.onWindowVisibilityChanged(visibility);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.e("gjj", "surfaceDestroyed:" + canvas);
mDrawThread.mHandler.removeCallbacksAndMessages(null);
FutureTask<Void> task = new FutureTask<Void>(new Runnable() {
@Override
public void run() {
Log.e("gjj", "run:结束");
}
}, null);
if (mDrawThread.mHandler.post(task)) {
try {
task.get();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
private enum Status {
NONE, ZOOM, DRAG
}
public class DrawThread extends Thread {
public Handler mHandler;
@Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
synchronized (this) {
mHandler = new Handler(Looper.myLooper()) {// 当接收到绘图消息时,重新进行绘图
@Override
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
Log.e("gjj", "sendMessageAtTime:" + msg.what + " run:" + msg.getCallback());
if (getWindowVisibility() != View.VISIBLE && msg.getCallback() == null) {
Log.e("gjj", Log.getStackTraceString(new NullPointerException("是谁还在发消息")));
}
return super.sendMessageAtTime(msg, uptimeMillis);
}
@Override
public void dispatchMessage(@NonNull Message msg) {
try {
super.dispatchMessage(msg);
} catch (Throwable throwable) {
Log.e("gjj", Log.getStackTraceString(throwable));
}
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:// 销毁绘图线程
Looper.myLooper().quit();
break;
default:// 绘图
Log.e("gjj", "draw开始:" + Integer.toHexString(getWindowVisibility()));
if (getWindowVisibility() != VISIBLE) {
return;
}
Canvas canvas = null;
Bitmap bitmap = mBitmap;
try {
canvas = getHolder().lockCanvas();
if (canvas != null) {
MyMap.this.canvas = canvas;
}
if (canvas != null && bitmap != null && !bitmap.isRecycled()) {
canvas.drawColor(getContext().getColor(R.color.color_FFFEFD));
Matrix matrix = new Matrix();
matrix.setScale(mCurrentScale, mCurrentScale, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
matrix.postTranslate(
mapCenter.x - bitmap.getWidth() / 2,
mapCenter.y - bitmap.getHeight() / 2);
canvas.drawBitmap(bitmap, matrix, mPaint);
for (MarkObject object : markList) {
Bitmap location = object.getmBitmap();
matrix.setScale(1.0f, 1.0f);
// 使用Matrix使得Bitmap的宽和高发生变化,在这里使用的mapX和mapY都是相对值
float x = mapCenter.x - location.getWidth() / 2 - bitmap.getWidth()
* mCurrentScale / 2
+ bitmap.getWidth()
* object.getMapX()
* mCurrentScale;
float y = mapCenter.y - location.getHeight()
- bitmap.getHeight()
* mCurrentScale / 2
+ bitmap.getHeight()
* object.getMapY()
* mCurrentScale;
matrix.postTranslate(x, y);
// matrix.postTranslate(20,20);
canvas.drawBitmap(location, matrix, mPaint);
}
}
} catch (Throwable throwable) {
} finally {
Log.e("gjj", "draw结束" + canvas + " ?"
+ getHolder().getSurface().isValid() + " " + Integer.toHexString(getWindowVisibility()));
if (canvas != null) {
MyMap.this.canvas = null;
if (getHolder().getSurface().isValid()) {
getHolder().unlockCanvasAndPost(canvas);
} else {
Log.e("gjj", "没有释放canvas");
}
}
}
break;
}
}
};
}
Looper.loop();
}
}
}