android 景区地图,Android开发--仿景点通景区地图SurfaceView实现

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

双击放大

多点触摸放大

地图拖拽

添加地图标记

效果图一张:

aef8e496df85a22040d39c412c248b4c.gif

代码思路

1.处理缩放和拖拽事件

在这里我利用了Matrix类提供的图片操作方法去进行图片的缩放和平移处理,关于该方面的知识可以参考

Android开发–利用Matrix进行图片操作

2.双击放大

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

3.多点触摸放大

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

//计算两个触摸点的距离

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

}

利用一个变量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进行判断当前触摸点是否被包含在当前的标记区域中;

6.异步线程绘图方法

首先,感谢 Tiger_老虎 朋友的热心提醒:现将draw方法修改如下,将原来每一次开启一个新的线程重新绘制地图的方式弃用,修改代码为:

首先在构造方法中开启一个新的DrawThread处理绘图事件,之后当我们每一次需要重写绘图时通过Handler传递绘图消息,接着在DrawThread去处理该消息,调用绘图方法进行异步绘图。

参考代码

MyMap类:

package com.example.maptest;

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.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

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 float mCurrentScaleMax;

private float mCurrentScale;

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;

private enum Status {

NONE, ZOOM, DRAG

};

private List markList = new ArrayList();

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) {

if (mBitmap != null) {

mBitmap.recycle();

}

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

*/

public void addMark(MarkObject object) {

markList.add(object);

}

/**

* 地图放大

*/

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() {

if (mBitmap != null) {

mBitmap.recycle();

}

for (MarkObject object : markList) {

if (object.getmBitmap() != null) {

object.getmBitmap().recycle();

}

}

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

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

}

// 处理点击标记的事件

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(object, clickX, clickY);

}

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() {

// TODO Auto-generated method stub

if (mDrawThread != null && mDrawThread.mHandler != null) {

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) {

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;

}

@Override

public void surfaceCreated(SurfaceHolder holder) {

// TODO Auto-generated method stub

draw();

}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width,

int height) {

// TODO Auto-generated method stub

}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

// TODO Auto-generated method stub

}

public class DrawThread extends Thread {

public Handler mHandler;

@Override

public void run() {

// TODO Auto-generated method stub

Looper.prepare();

synchronized (this) {

mHandler = new Handler() {// 当接收到绘图消息时,重新进行绘图

@Override

public void handleMessage(Message msg) {

// TODO Auto-generated method stub

super.handleMessage(msg);

switch (msg.what) {

case 1:// 销毁绘图线程

Looper.myLooper().quit();

break;

default:// 绘图

Canvas 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);

}

break;

}

}

};

}

Looper.loop();

}

}

}

MarkObject类用于存储标记信息:

package com.example.maptest;

import android.graphics.Bitmap;

import android.graphics.Rect;

public class MarkObject {

private Bitmap mBitmap;

private float mapX;

private float mapY;

private MarkClickListener listener;

public MarkObject() {

}

public MarkObject(Bitmap mBitmap, float mapX, float mapY) {

super();

this.mBitmap = mBitmap;

this.mapX = mapX;

this.mapY = mapY;

}

/**

*@return the mBitmap

*/

public Bitmap getmBitmap() {

return mBitmap;

}

/**

*@param mBitmap

* the mBitmap to set

*/

public void setmBitmap(Bitmap mBitmap) {

this.mBitmap = mBitmap;

}

/**

*@return the mapX

*/

public float getMapX() {

return mapX;

}

/**

*@param mapX

* the mapX to set

*/

public void setMapX(float mapX) {

this.mapX = mapX;

}

/**

*@return the mapY

*/

public float getMapY() {

return mapY;

}

/**

*@param mapY

* the mapY to set

*/

public void setMapY(float mapY) {

this.mapY = mapY;

}

public MarkClickListener getMarkListener() {

return listener;

}

public void setMarkListener(MarkClickListener listener) {

this.listener = listener;

}

public interface MarkClickListener {

public void onMarkClick(int x, int y);

}

}

注意问题

1.每次使用Matrix进行缩放时,均设置缩放中心为图片地图中心(这里是相对图片来说的,所以是

(mBitmap.getWidth() / 2, mBitmap.getHeight() / 2)的位置,而不是mapCenter;),这样在我们处理图片的缩放时mapCenter的位置不会改变,如果不这样做的话,处理mapCenter的位置变化十分困难。

2.为了避免不同分辨率的手机获得的图片高度,宽度不一致的情况,这里采用的是标记相对于图片的整体位置值,即标记在图片中的像素坐标除以图片的高或宽。

3.为了获得良好的用户体验,当我们拖拽图片离开了屏幕边缘的时候,应当重新设定mapCenter以避免这种情况;同时在处理缩放事件时也应当注意。

4.为了获得高效率,我们利用SurfaceView来做,并在异步线程中进行地图更新,关于SurfaceView的用法可以参考

Android开发–SurfaceView的基本用法

5.在surfaceDestroyed记得回收Bitmap资源。

源码下载

点击下载源码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值