用SVG实现动态交互式地图
SVG是目前最火热的图像文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形,它是基于XML,由W3C联盟进行开发。SVG可以实现无损缩放,在性能上要比其它格式的图片更好,今天教大家用SVG实现一个动态交互式地图,先上效果图:
一、 实现思路
下载含有台湾地图的SVG,地图资源可以在这个网站上https://www.amcharts.com/dl/javascript-maps/ 下载,
用http://inloop.github.io/svg2android/ 网站将SVG资源转换成相应的Android代码,利用DOM解析SVG的代码,将属性pathData数据封装成JavaBean,最重要的是得到点击的Path。Path指令解析如下所示:
- M = moveto(M X,Y) :将画笔移动到指定的坐标位置,相当于 android Path 里的moveTo()
- L = lineto(L X,Y) :画直线到指定的坐标位置,相当于 android Path 里的lineTo()
- H = horizontal lineto(H X):画水平线到指定的X坐标位置
- V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
- C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
- S = smooth curveto(S X2,Y2,ENDX,ENDY) 同样三次贝塞尔曲线,更平滑
- Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
- T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射 同样二次贝塞尔曲线,更平滑
- A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线 ,相当于arcTo()
- Z = closepath():关闭路径(会自动绘制链接起点和终点)
重写onDraw方法,利用path绘制台湾地图,重写onTouchEvent方法,记录手指触摸位置,判断坐标是否在某一个市的范围内。
二、 代码实例
自定义TaiWanMapView
public class TaiWanMapView extends View {
private static final String TAG = TaiWanMapView.class.getName();
private List<TaiWanItem> taiwanItems = new ArrayList<TaiWanItem>();
//被点击的区域
private TaiWanItem selectItem;
//缩放1.3倍
private float scale = 1.3f;
private Context mContext;
private Paint mPaint;
private int[] colors = new int[]{0xFF239BD7,0xFF30A9E5,0xFF80CBF1,0xFFF0E68C};
GestureDetectorCompat gestureDetectorCompat;
public TaiWanMapView(Context context) {
super(context);
}
public TaiWanMapView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mContext = context;
mPaint = new Paint();
mPaint.setAntiAlias(true);
gestureDetectorCompat = new GestureDetectorCompat(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
Log.d(TAG,"onDown x:"+e.getX()+";y:"+e.getY());
handleTouch(e.getX(),e.getY());
return true;
}
});
thread.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//将画布缩放1.3倍
canvas.scale(scale,scale);
if(taiwanItems != null){
for(TaiWanItem item:taiwanItems){
if(item != selectItem){
item.draw(canvas,mPaint,false);
}
}
if(selectItem != null){
selectItem.draw(canvas,mPaint,true);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetectorCompat.onTouchEvent(event);
}
private void handleTouch(float x,float y){
TaiWanItem taiWanItem = null;
if(taiwanItems != null){
for(TaiWanItem item : taiwanItems){
if(item.isTouch((int)(x/scale),(int)(y/scale))){
taiWanItem = item;
break;
}
}
if(taiWanItem != null) {
selectItem = taiWanItem;
postInvalidate();
}
}
}
Thread thread = new Thread(){
@Override
public void run() {
InputStream inputStream = mContext.getResources().openRawResource(R.raw.taiwan);
//采用Dom解析器解析xml
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);
Element rootelement = doc.getDocumentElement();
NodeList items = rootelement.getElementsByTagName("path");
for(int i=0;i<items.getLength();i++){
Element element = (Element) items.item(i);
String pathData = element.getAttribute("android:pathData");
Path path = PathParser.createPathFromPathData(pathData);
TaiWanItem item = new TaiWanItem(path);
taiwanItems.add(item);
}
handler.sendEmptyMessage(1);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(taiwanItems == null || taiwanItems.size()==0)
return;
int colorNum = taiwanItems.size();
int color = Color.WHITE;
//赋予颜色
for(int i=0;i<colorNum;i++){
int flag = i % 4;
switch(flag){
case 1:
color = colors[0];
break;
case 2:
color = colors[1];
break;
case 3:
color = colors[2];
break;
default:
color = colors[3];
}
taiwanItems.get(i).setDrawColor(color);
}
postInvalidate();
}
};
}
将地图path封装成TaiWanItem
public class TaiWanItem {
private Path path;
private int drawColor;
public TaiWanItem(Path path) {
this.path = path;
}
/**
* 绘制地图path
* @param canvas
* @param paint
* @param isSelect
*/
public void draw(Canvas canvas, Paint paint,boolean isSelect){
if(isSelect){
//画阴影图层
paint.setStrokeWidth(2);
paint.setShadowLayer(8,0,0,0xffffff);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
canvas.drawPath(path,paint);
//画区域path
paint.clearShadowLayer();
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path,paint);
}else{
//画线条
paint.clearShadowLayer();
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xFFD0E8F4);
canvas.drawPath(path,paint);
//画区域
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path,paint);
}
}
/**
* 判断当前点击坐标是否在path范围内
* @param x
* @param y
* @return
*/
public boolean isTouch(int x,int y){
RectF rectF = new RectF();
path.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
//判断X,Y是否在region区域范围内
if(region.contains(x,y)) return true;
return false;
}
public void setDrawColor(int drawColor) {
this.drawColor = drawColor;
}
}
整个代码不算很复杂,关键就是在onDraw方法里面,利用系统提供的方法canvas.drawPath(Path,Paint )将path区域绘制在画布上。