1、SVG概念:
SVG是一种图像文件格式,类似PNG,JPG。只不过PNG这种图片需要图像引擎加载,SVG则是由画布来加载,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形,可让你设计无损失、高分辨率的Web图形页面,用户可以直接使用代码来描绘图像;
2、SVG图像在Android中的使用
app图标:sdk23以后,app的图标都是由svg图像来表示
自定义控件:如不规则控件、复杂的交互控件、子控件重叠判断、图标等,都可以使用SVG图像实现
复杂动画:如根据用户滑动手势动态显示动画,路径动画等
3、实现中国地图的绘制,并且能正常点击省份
效果展示:
中国地图svg图像下载地址:http://www.amcharts.com/download/
点击第二个download按钮即可下载所有国家svg图像;
此下载比较费劲,我分享一个网盘地址:
链接 https://pan.baidu.com/s/1zbtejuTYhSL2ino8soOgRw 提取码:mung
下载成功后将文件导入工程res/raw/china.svg
自定义view代码:
package com.xxx.uidemo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.graphics.PathParser;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class ChinaMapView extends View {
private int[] colorArrays = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFF00};
private List<ProvinceItem> provinceItems = new ArrayList<>();// 所有省份
private Paint paint;
private ProvinceItem selectItem;// 点击选中的省份
private RectF totalRectF;// 地图矩形
private float scale = 1.0f;// 画布缩放系数
public ChinaMapView(Context context) {
this(context, null);
}
public ChinaMapView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChinaMapView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
parseSVG(context);
}
private void parseSVG(final Context context) {
final InputStream inputStream = context.getResources().openRawResource(R.raw.china);
new Thread(new Runnable() {
@Override
public void run() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
Document parse = documentBuilder.parse(inputStream);
Element documentElement = parse.getDocumentElement();
NodeList g = documentElement.getElementsByTagName("path");
float left = -1;
float right = -1;
float top = -1;
float bottom = -1;
ArrayList<ProvinceItem> list = new ArrayList<>();
for (int i = 0; i < g.getLength(); i++) {
Element element = (Element) g.item(i);
String pathData = element.getAttribute("d");
@SuppressLint("RestrictedApi") Path path = PathParser.createPathFromPathData(pathData);
ProvinceItem provinceItem = new ProvinceItem(path);
provinceItem.setColor(colorArrays[i % 4]);
// 这里循环遍历每个省份path的边界,并求得最左边、右边、顶部、底部path的边界
RectF rectF = new RectF();
path.computeBounds(rectF, true);
left = left == -1 ? rectF.left : Math.min(left, rectF.left);
right = right == -1 ? rectF.right : Math.max(right, rectF.right);
top = top == -1 ? rectF.top : Math.min(top, rectF.top);
bottom = bottom == -1 ? rectF.bottom : Math.max(bottom, rectF.bottom);
list.add(provinceItem);
}
provinceItems = list;// 避免priviceItems集合并发操作,先使用临时集合,然后再重新赋值
totalRectF = new RectF(left, top, right, bottom);//保存实际地图大小
// 通知刷新界面
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
requestLayout();
invalidate();
}
});
//postInvalidate();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void init() {
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取画布原始大小
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
// 计算缩放系数
if (totalRectF != null) {
float width = totalRectF.width();
scale = measuredWidth / width;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (provinceItems != null && provinceItems.size() > 0) {
canvas.save();
//按照缩放系数将画布进行缩放
canvas.scale(scale, scale);
for (ProvinceItem provinceItem : provinceItems) {
if (provinceItem == selectItem) {
provinceItem.DrawItem(canvas, paint, true);
} else {
provinceItem.DrawItem(canvas, paint, false);
}
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
handleTouch(event.getX() / scale, event.getY() / scale);// 点击触摸时也需要将事件位置进行缩放,不然点击事件会受影响
return super.onTouchEvent(event);
}
private void handleTouch(float x, float y) {
if (provinceItems == null) {
return;
}
ProvinceItem select = null;
for (ProvinceItem provinceItem : provinceItems) {
if (provinceItem.isTouch(x, y)) {
select = provinceItem;
}
}
if (select != null) {
selectItem = select;
postInvalidate();
}
}
}
package com.xxx.uidemo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
/**
* 省份
*/
public class ProvinceItem {
private Path path;
private int color;
public ProvinceItem(Path path) {
this.path = path;
}
public void setColor(int color) {
this.color = color;
}
/**
* 绘制自己
*
* @param canvas
* @param paint
* @param isSelect
*/
public void DrawItem(Canvas canvas, Paint paint, boolean isSelect) {
if (isSelect) {
// 绘制内部的颜色
paint.clearShadowLayer();
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
canvas.drawPath(path, paint);
// 绘制边界
paint.setColor(0xFFD0E8F4);
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, paint);
} else {
// 绘制内部的颜色
paint.clearShadowLayer();
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setShadowLayer(8, 0, 0, 0xFFFFFF);
canvas.drawPath(path, paint);
// 绘制边界
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
canvas.drawPath(path, paint);
}
}
/**
* 按下坐标坐标是否在path轨迹和path对应的矩形轨迹的交集中
*
* @param x
* @param y
* @return
*/
public boolean isTouch(float x, float y) {
RectF rectF = new RectF();
path.computeBounds(rectF, true);//计算path轨迹对应的矩形界限
Region region = new Region();
// 计算path对应的轨迹和path轨迹对应的矩形界限的范围交集
region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
return region.contains((int) x, (int) y);// 坐标值是否包含在此范围中
}
}
使用布局加载地图
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xxx.uidemo.ChinaMapView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
里面有详细备注,基本思路也就是先解析svg文件,然后依据解析结果画出地图;
关键步骤:
1、解析svg;
2、绘制自己
3、判断点击坐标是否属于此path轨迹范围内;
4、解析过程中获取地图实际大小;
5、计算画布和地图 缩放比并按比例缩放画布;