仿景点通景区地图SurfaceView实现

最近在帮老师做一个项目,类似于景点通的App手机应用,我们是要精细化一些室内的地图,室内的地图采用的是自己的一套定位机制,所有室内地图也要自己来实现,参考了网上一些例子,考虑到效率的问题,最后决定使用SurfaceView来进行地图绘制,实现的功能有:

添加地图标记 效果图一张:

代码思路

1.处理缩放和拖拽事件 在这里我利用了Matrix类提供的图片操作方法去进行图片的缩放和平移处理,关于该方面的知识可以参考 Android开发–利用Matrix进行图片操作

2.双击放大 为了实现双击放大,在这里我们MyMap类中设置了一个成员变量lastClickTime用来记录上一次点击屏幕的时间(点击屏幕的时间值可以通过MotionEvent的getEventTime方法去获得,单位是ms),如果当前点击事件的时间与上次点击事件的时间差值小于300ms则执行放大事件。

3.多点触摸放大 通过MotionEvent中的方法来获得两个触摸点之间的距离大小, 如下:

(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);}

利用一个变量oldDist表示前一次两个触摸点的距离,利用一个oldRate表示前一次的缩放,在onTouchEvent方法中move的情况下不断更新当前缩放mCurrentScale = oldRate * (newDist / oldDist);

4.地图拖拽 利用一个PointF变量mapCenter表示当前地图中心的位置在手机屏幕上的坐标,当拖拽事件发生时通过手指移动的距离来不同更新mapCenter的值,并在draw方法中利用Matrix类操作图片

matrix.postTranslate(mapCenter.x – mBitmap.getWidth() / 2, mapCenter.y – mBitmap.getHeight() / 2);

5.添加地图标记 编写一个MarkObject类来表示地图标记,在该类之下存储了标记的Bitmap对象,该标记相对于整张地图的位置,以及点击标记的回调事件的处理。 在MyMap类中利用一个List变量markList来记录所有已经添加的地图标记。 1)处理标记随拖拽和缩放事件而改变位置:这里主要是根据mapCenter的点来进行计算,具体的计算大家可以参考代码; 2)处理点击事件:在onTouchEvent方法中up情况时,遍历markList中的MarkObject进行判断当前触摸点是否被包含在当前的标记区域中;

参考代码

MyMap类:

package com.example.maptest;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.List;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.PointF;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;.String TAG = MyMap.class.getSimpleName();DOUBLE_CLICK_TIME_SPACE = 300;private float mCurrentScaleMax;private float mCurrentScale;private float mCurrentScaleMin;private float windowWidth, windowHeight;private Bitmap mBitmap;private Paint mPaint;lastClickTime;// 记录上一次点击屏幕的时间,以判断双击事件private Status mStatus = Status.NONE;private float oldRate = 1;private float oldDist = 1;private float offsetX, offsetY;private boolean isShu = true;private enum Status {NONE, ZOOM, DRAG};private List<MarkObject> markList = new ArrayList<MarkObject>();public MyMap(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stubinit();}public MyMap(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinit();}public MyMap(Context context) {super(context);// TODO Auto-generated constructor stubinit();}() {SurfaceHolder holder = getHolder();holder.addCallback(this);// 获取屏幕的宽和高windowWidth = getResources().getDisplayMetrics().widthPixels;windowHeight = getResources().getDisplayMetrics().heightPixels- getStatusBarHeight();mPaint = new Paint();mStartPoint = new PointF();mapCenter = new PointF();}(Bitmap bitmap) {this.mBitmap = bitmap;// 设置最小缩放为铺满屏幕,,最大缩放为最小缩放的4倍mCurrentScaleMin = Math.min(windowHeight / mBitmap.getHeight(),windowWidth / mBitmap.getWidth());mCurrentScale = mCurrentScaleMin;mCurrentScaleMax = mCurrentScaleMin * 4;mapCenter.set(mBitmap.getWidth() * mCurrentScale / 2,mBitmap.getHeight() * mCurrentScale / 2);float bitmapRatio = mBitmap.getHeight() / mBitmap.getWidth();float winRatio = windowHeight / windowWidth;// 判断屏幕铺满的情况,isShu为true表示屏幕横向被铺满,为false表示屏幕纵向被铺满if (bitmapRatio <= winRatio) {isShu = true;} else {isShu = false;}draw();}/*** 为当前地图添加标记** @param object*/(MarkObject object) {markList.add(object);}/*** 地图放大*/() {mCurrentScale *= 1.5f;if (mCurrentScale > mCurrentScaleMax) {mCurrentScale = mCurrentScaleMax;}draw();}/*** 地图缩小*/() {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();}(MotionEvent event) {PointF currentPoint = new PointF();currentPoint.set(event.getX(), event.getY());offsetX = currentPoint.x – mStartPoint.x;offsetY = currentPoint.y – mStartPoint.y;// 以下是进行判断,防止出现图片拖拽离开屏幕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;}(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;} 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();}(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);}break;}}}(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);}() {new Thread(new Runnable() {() {// TODO Auto-generated method stubCanvas canvas = getHolder().lockCanvas();if (canvas != null && mBitmap != null) {canvas.drawColor(Color.GRAY);Matrix matrix = new Matrix();matrix.setScale(mCurrentScale, mCurrentScale,mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);matrix.postTranslate(mapCenter.x – mBitmap.getWidth() / 2,mapCenter.y – mBitmap.getHeight() / 2);canvas.drawBitmap(mBitmap, matrix, mPaint);for (MarkObject object : markList) {Bitmap location = object.getmBitmap();matrix.setScale(1.0f, 1.0f);// 使用Matrix使得Bitmap的宽和高发生变化,在这里使用的mapX和mapY都是相对值matrix.postTranslate(mapCenter.x – location.getWidth() / 2- mBitmap.getWidth() * mCurrentScale/ 2 + mBitmap.getWidth()* object.getMapX() * mCurrentScale,mapCenter.y – location.getHeight()- mBitmap.getHeight() * mCurrentScale/ 2 + mBitmap.getHeight()* object.getMapY() * mCurrentScale);canvas.drawBitmap(location, matrix, mPaint);}}if (canvas != null) {getHolder().unlockCanvasAndPost(canvas);}}}).start();}(MotionEvent event) {// TODO Auto-generated method stubswitch (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) {drag(event);} 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;}(SurfaceHolder holder) {// TODO Auto-generated method stubdraw();}(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub}(SurfaceHolder holder) {// TODO Auto-generated method stubif (mBitmap != null) {mBitmap.recycle();}for (MarkObject object : markList) {if (object.getmBitmap() != null) {object.getmBitmap().recycle();}}}() {Class<?> c = null;Object obj = null;Field field = null;int x = 0;try {c = Class.forName(“com.android.internal.R$dimen”);obj = c.newInstance();field = c.getField(“status_bar_height”);x = Integer.parseInt(field.get(obj).toString());return getResources().getDimensionPixelSize(x);} catch (Exception e1) {e1.printStackTrace();return 75;}}}人之所以能,是相信能。

仿景点通景区地图SurfaceView实现

相关文章:

你感兴趣的文章:

标签云: