|
@@ -0,0 +1,319 @@
|
|
|
|
|
+package com.onlylemi.mapview.library;
|
|
|
|
|
+
|
|
|
|
|
+import android.content.Context;
|
|
|
|
|
+import android.graphics.Bitmap;
|
|
|
|
|
+import android.graphics.Canvas;
|
|
|
|
|
+import android.graphics.Color;
|
|
|
|
|
+import android.graphics.Matrix;
|
|
|
|
|
+import android.graphics.Picture;
|
|
|
|
|
+import android.graphics.PointF;
|
|
|
|
|
+import android.graphics.SurfaceTexture;
|
|
|
|
|
+import android.util.AttributeSet;
|
|
|
|
|
+import android.view.MotionEvent;
|
|
|
|
|
+import android.view.TextureView;
|
|
|
|
|
+
|
|
|
|
|
+import com.grkj.iscs.util.log.LogUtil;
|
|
|
|
|
+import com.onlylemi.mapview.library.layer.MapBaseLayer;
|
|
|
|
|
+import com.onlylemi.mapview.library.layer.MapLayer;
|
|
|
|
|
+import com.onlylemi.mapview.library.utils.MapMath;
|
|
|
|
|
+import com.onlylemi.mapview.library.utils.MapUtils;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Java版:TextureView替代SurfaceView的MapView,实现独立画布
|
|
|
|
|
+ * 修复:支持在任意缩放级别下既能放大也能缩小。
|
|
|
|
|
+ */
|
|
|
|
|
+public class MapView extends TextureView implements TextureView.SurfaceTextureListener {
|
|
|
|
|
+
|
|
|
|
|
+ private SurfaceTexture surface;
|
|
|
|
|
+ private boolean isMapLoadFinish = false;
|
|
|
|
|
+ private final List<MapBaseLayer> layers = new ArrayList<>();
|
|
|
|
|
+ private MapLayer mapLayer;
|
|
|
|
|
+
|
|
|
|
|
+ private final Matrix saveMatrix = new Matrix();
|
|
|
|
|
+ private final Matrix currentMatrix = new Matrix();
|
|
|
|
|
+ private float currentZoom = 1.0f;
|
|
|
|
|
+ private float saveZoom = 1.0f; // 默认初始保存zoom为1
|
|
|
|
|
+ private float currentRotateDegrees = 0.0f;
|
|
|
|
|
+ private float saveRotateDegrees = 0.0f;
|
|
|
|
|
+
|
|
|
|
|
+ private float minZoom = 0.5f;
|
|
|
|
|
+ private float maxZoom = 3.0f;
|
|
|
|
|
+ private boolean isScaleAndRotateTogether = false;
|
|
|
|
|
+
|
|
|
|
|
+ private final PointF startTouch = new PointF();
|
|
|
|
|
+ private final PointF lastMove = new PointF();
|
|
|
|
|
+ private final PointF mid = new PointF();
|
|
|
|
|
+ private float oldDist = 0f;
|
|
|
|
|
+ private float oldDegree = 0f;
|
|
|
|
|
+ private int currentTouchState = TOUCH_STATE_NO;
|
|
|
|
|
+
|
|
|
|
|
+ private MapViewListener mapViewListener;
|
|
|
|
|
+
|
|
|
|
|
+ public static final int TOUCH_STATE_NO = 0;
|
|
|
|
|
+ public static final int TOUCH_STATE_SCROLL = 1;
|
|
|
|
|
+ public static final int TOUCH_STATE_SCALE = 2;
|
|
|
|
|
+ public static final int TOUCH_STATE_ROTATE = 3;
|
|
|
|
|
+ public static final int TOUCH_STATE_TWO_POINTED = 4;
|
|
|
|
|
+
|
|
|
|
|
+ public MapView(Context context) {
|
|
|
|
|
+ this(context, null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public MapView(Context context, AttributeSet attrs) {
|
|
|
|
|
+ this(context, attrs, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public MapView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
|
|
+ init();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void init() {
|
|
|
|
|
+ setSurfaceTextureListener(this);
|
|
|
|
|
+ setOpaque(true);
|
|
|
|
|
+ setClickable(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
|
|
|
|
|
+ surface = st;
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
|
|
|
|
|
+ surface = null;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onSurfaceTextureUpdated(SurfaceTexture st) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void loadMap(Bitmap bitmap) {
|
|
|
|
|
+ loadMap(MapUtils.getPictureFromBitmap(bitmap));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void loadMap(final Picture picture) {
|
|
|
|
|
+ isMapLoadFinish = false;
|
|
|
|
|
+ new Thread(new Runnable() {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ if (picture != null) {
|
|
|
|
|
+ if (mapLayer == null) {
|
|
|
|
|
+ mapLayer = new MapLayer(MapView.this);
|
|
|
|
|
+ layers.add(mapLayer);
|
|
|
|
|
+ }
|
|
|
|
|
+ mapLayer.setImage(picture);
|
|
|
|
|
+ if (mapViewListener != null) {
|
|
|
|
|
+ mapViewListener.onMapLoadSuccess();
|
|
|
|
|
+ }
|
|
|
|
|
+ isMapLoadFinish = true;
|
|
|
|
|
+ post(new Runnable() {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (mapViewListener != null) {
|
|
|
|
|
+ mapViewListener.onMapLoadFail();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }).start();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void refresh() {
|
|
|
|
|
+ if (surface == null || !isMapLoadFinish) return;
|
|
|
|
|
+ Canvas canvas = lockCanvas();
|
|
|
|
|
+ if (canvas != null) {
|
|
|
|
|
+ canvas.drawColor(Color.WHITE);
|
|
|
|
|
+ for (MapBaseLayer layer : layers) {
|
|
|
|
|
+ if (layer.isVisible) {
|
|
|
|
|
+ layer.draw(canvas, currentMatrix, currentZoom, currentRotateDegrees);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ unlockCanvasAndPost(canvas);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean onTouchEvent(MotionEvent event) {
|
|
|
|
|
+ if (!isMapLoadFinish) return false;
|
|
|
|
|
+ int action = event.getAction() & MotionEvent.ACTION_MASK;
|
|
|
|
|
+ switch (action) {
|
|
|
|
|
+ case MotionEvent.ACTION_DOWN:
|
|
|
|
|
+ saveMatrix.set(currentMatrix);
|
|
|
|
|
+ startTouch.set(event.getX(), event.getY());
|
|
|
|
|
+ lastMove.set(event.getX(), event.getY());
|
|
|
|
|
+ currentTouchState = TOUCH_STATE_SCROLL;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case MotionEvent.ACTION_POINTER_DOWN:
|
|
|
|
|
+ if (event.getPointerCount() == 2) {
|
|
|
|
|
+ saveMatrix.set(currentMatrix);
|
|
|
|
|
+ saveZoom = currentZoom;
|
|
|
|
|
+ saveRotateDegrees = currentRotateDegrees;
|
|
|
|
|
+ mid.set(midPoint(event));
|
|
|
|
|
+ oldDist = distance(event, mid);
|
|
|
|
|
+ oldDegree = rotation(event, mid);
|
|
|
|
|
+ currentTouchState = TOUCH_STATE_TWO_POINTED;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case MotionEvent.ACTION_UP:
|
|
|
|
|
+ if (withFloorPlan(event.getX(), event.getY())) {
|
|
|
|
|
+ for (MapBaseLayer layer : layers) {
|
|
|
|
|
+ layer.onTouch(event);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ currentTouchState = TOUCH_STATE_NO;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case MotionEvent.ACTION_MOVE:
|
|
|
|
|
+ if (currentTouchState == TOUCH_STATE_SCROLL) {
|
|
|
|
|
+ currentMatrix.set(saveMatrix);
|
|
|
|
|
+ currentMatrix.postTranslate(event.getX() - startTouch.x,
|
|
|
|
|
+ event.getY() - startTouch.y);
|
|
|
|
|
+ } else if (currentTouchState == TOUCH_STATE_TWO_POINTED) {
|
|
|
|
|
+ // 始终按缩放处理,避免旋转误判让缩小失效
|
|
|
|
|
+ float newDist = distance(event, mid);
|
|
|
|
|
+ float scale = newDist / oldDist;
|
|
|
|
|
+ if (scale * saveZoom < minZoom) {
|
|
|
|
|
+ scale = minZoom / saveZoom;
|
|
|
|
|
+ } else if (scale * saveZoom > maxZoom) {
|
|
|
|
|
+ scale = maxZoom / saveZoom;
|
|
|
|
|
+ }
|
|
|
|
|
+ currentZoom = scale * saveZoom;
|
|
|
|
|
+ LogUtil.INSTANCE.i("当前比例:" + currentZoom);
|
|
|
|
|
+ currentMatrix.set(saveMatrix);
|
|
|
|
|
+ currentMatrix.postScale(scale, scale, mid.x, mid.y);
|
|
|
|
|
+ }
|
|
|
|
|
+ lastMove.set(event.getX(), event.getY());
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setMapViewListener(MapViewListener listener) {
|
|
|
|
|
+ this.mapViewListener = listener;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public float[] convertMapXYToScreenXY(float x, float y) {
|
|
|
|
|
+ Matrix inv = new Matrix();
|
|
|
|
|
+ currentMatrix.invert(inv);
|
|
|
|
|
+ float[] pts = {x, y};
|
|
|
|
|
+ inv.mapPoints(pts);
|
|
|
|
|
+ return pts;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public boolean isMapLoadFinish() {
|
|
|
|
|
+ return isMapLoadFinish;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void addLayer(MapBaseLayer layer) {
|
|
|
|
|
+ layers.add(layer);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public List<MapBaseLayer> getLayers() {
|
|
|
|
|
+ return layers;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void translate(float x, float y) {
|
|
|
|
|
+ currentMatrix.postTranslate(x, y);
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void mapCenterWithPoint(float x, float y) {
|
|
|
|
|
+ float[] pts = {x, y};
|
|
|
|
|
+ currentMatrix.mapPoints(pts);
|
|
|
|
|
+ float dx = getWidth() / 2f - pts[0];
|
|
|
|
|
+ float dy = getHeight() / 2f - pts[1];
|
|
|
|
|
+ currentMatrix.postTranslate(dx, dy);
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public float getCurrentRotateDegrees() {
|
|
|
|
|
+ return currentRotateDegrees;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setCurrentRotateDegrees(float degrees) {
|
|
|
|
|
+ mapCenterWithPoint(
|
|
|
|
|
+ mapLayer != null ? mapLayer.getImage().getWidth() / 2f : 0f,
|
|
|
|
|
+ mapLayer != null ? mapLayer.getImage().getHeight() / 2f : 0f
|
|
|
|
|
+ );
|
|
|
|
|
+ setCurrentRotateDegrees(degrees, getWidth() / 2f, getHeight() / 2f);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setCurrentRotateDegrees(float degrees, float px, float py) {
|
|
|
|
|
+ currentMatrix.postRotate(degrees - currentRotateDegrees, px, py);
|
|
|
|
|
+ currentRotateDegrees = (degrees % 360 + 360) % 360;
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public float getCurrentZoom() {
|
|
|
|
|
+ return currentZoom;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setCurrentZoom(float zoom) {
|
|
|
|
|
+ setCurrentZoom(zoom, getWidth() / 2f, getHeight() / 2f);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setCurrentZoom(float zoom, float px, float py) {
|
|
|
|
|
+ currentMatrix.postScale(zoom / currentZoom, zoom / currentZoom, px, py);
|
|
|
|
|
+ currentZoom = zoom;
|
|
|
|
|
+ refresh();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public boolean isScaleAndRotateTogether() {
|
|
|
|
|
+ return isScaleAndRotateTogether;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setScaleAndRotateTogether(boolean flag) {
|
|
|
|
|
+ this.isScaleAndRotateTogether = flag;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setMaxZoom(float max) {
|
|
|
|
|
+ this.maxZoom = max;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setMinZoom(float min) {
|
|
|
|
|
+ LogUtil.INSTANCE.i("最小缩放比:" + min);
|
|
|
|
|
+ this.minZoom = min;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private PointF midPoint(MotionEvent event) {
|
|
|
|
|
+ return MapMath.getMidPointBetweenTwoPoints(
|
|
|
|
|
+ event.getX(0), event.getY(0), event.getX(1), event.getY(1));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private float distance(MotionEvent event, PointF mid) {
|
|
|
|
|
+ return MapMath.getDistanceBetweenTwoPoints(
|
|
|
|
|
+ event.getX(0), event.getY(0), mid.x, mid.y);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private float rotation(MotionEvent event, PointF mid) {
|
|
|
|
|
+ return MapMath.getDegreeBetweenTwoPoints(
|
|
|
|
|
+ event.getX(0), event.getY(0), mid.x, mid.y);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public boolean withFloorPlan(float x, float y) {
|
|
|
|
|
+ float[] pts = convertMapXYToScreenXY(x, y);
|
|
|
|
|
+ Picture img = mapLayer != null ? mapLayer.getImage() : null;
|
|
|
|
|
+ return img != null && pts[0] > 0 && pts[0] < img.getWidth() && pts[1] > 0 && pts[1] < img.getHeight();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public float getMapWidth() {
|
|
|
|
|
+ return mapLayer != null ? mapLayer.getImage().getWidth() : 0f;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public float getMapHeight() {
|
|
|
|
|
+ return mapLayer != null ? mapLayer.getImage().getHeight() : 0f;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|