SVG地图绘制和点击效果实现
在一个小项目中用到了地图的展示,在此做个笔记,先上一个效果图
上图用到的SVG资源是从Free SVG Maps上下载的
然后将下载好的SVG资源转成XML文件格式,转化工具地址
解析好的XML文件
在res目录下,创建raw文件夹
将解析好的xml数据复制进去
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="-1dp"
android:height="-1dp"
android:viewportWidth="-1"
android:viewportHeight="-1">
//因为整个数据太长,仅展示部分
<path
android:name="CN-11"
android:fillColor="#CCCCCC"
android:strokeColor="#ffffff"
android:strokeWidth="0.5"
android:pathData="M546.46,257.82L546.2,258.07L545.19,257.69L545.32,258.18L543.93,257.5L543.54,257.65L542.41,258.89L541.38,259.38L542.02,260.56L540.61,260.4L539.44,259.53L539.28,258.7L538.66,258.32L537.92,258.56L536.13,258.13L535.82,258.52L535.34,258.01L535.11,258.57L534.23,258.84L533.87,259.42L533.2,259.42L532.34,257.98L530.81,258.02L529.74,257.1L529.84,255.69L529,255.13L530.84,254.47L530.2,254L530.15,252.87L528.99,252.2L529.29,251L531.38,249.47L533.13,249.19L533.53,248.49L534.28,249.13L534.78,247.81L535.79,247.12L535.17,245.59L534.67,245.52L533.4,244.1L533.46,243.33L532.94,243.15L533.23,242.5L535.15,241.24L536.1,241.87L537.59,241.28L539.29,238.42L540,238.94L541.47,238.45L542.04,238.74L542.1,238.37L540.49,236.54L540.53,236.05L540.92,235.88L541.32,236.49L542.23,236.64L541.97,235.28L543.78,235.25L543.95,234.15L544.4,233.9L545.04,234.33L544.79,235.26L545.4,236.52L546.36,237.49L547.05,237.66L548.92,239.98L550.77,239.84L552.86,240.63L554.07,240.2L555.26,240.44L555.25,240.92L554.58,240.66L554.36,240.98L554.53,241.89L552.78,241.9L551.97,242.41L552.11,242.94L551.46,243.23L551.9,244.24L551.74,245.25L553.34,247.62L553.79,247.69L553.79,247.69L553.79,248.62L551.45,250.07L551.45,250.07L549.71,250.41L548.96,250.95L548.26,250.65L546.19,250.97L545.91,251.98L546.21,253.02L547.91,253.88L548.31,254.99L547.73,255.44L547.66,256.41L547.66,256.41L547.72,256.71L547.07,256.96z" />
<path
android:name="CN-50"
android:fillColor="#CCCCCC"
android:strokeColor="#ffffff"
android:strokeWidth="0.5"
android:pathData="M441.77,372.89L443.66,374.38L444.63,374.41L444.78,375L446.44,376.09L448.29,376.53L450.21,378.04L450.34,378.61L451.33,378.94L451.14,379.42L451.5,380.2L455.19,379.99L455.19,379.99L457.11,380.46L457.36,381.73L457.05,382.61L458.18,382.41L458.99,382.98L460.2,383.16L459.82,383.71L462.29,384.97L462.95,388.58L462.01,389.34L462.06,390.23L462.62,390.98L461.5,393.66L460.76,393.21L460.54,392.23L460.08,392.64L459.2,392.38L457.68,392.95L456.24,394.2L456.03,394.91L455.29,395.18L455.18,395.81L454.67,395.53L453.63,396.3L452.22,398.17L452.27,397.14L451.71,396.13L449.61,397.59L448.96,397.13L449.33,395.92L448.36,395.81L447.35,396.31L445.63,398.13L444.48,398.09L443.95,396.8L443.22,396.85L443.39,397.48L442.46,398.45L441.7,398.03L440.59,398.26L440.29,399.7L442.47,401.76L442.33,403.29L441.57,404.6L441.89,404.52L442.02,405.51L441.8,407.26L440.72,407.05L440.14,407.32L440.05,408.02L440.26,407.75L440.53,408.06L440.75,409.2L441.64,409.55L442.02,409.18L441.92,408.45L442.7,407.43L443.53,407.5L443.8,409.82L444.32,409.83L444.8,410.48L445.17,410.11L445.21,410.67L445.95,410.43L446.37,410.79L446.2,411.24L446.63,411.22L446.13,413.01L447.06,413.79L446.76,415.14L447.54,415.06L448.73,414.13L449.23,414.58L449.22,415.57L449.56,415.99L449.27,416.9L450.8,418.06L450.8,418.06L450.75,418.48L451.9,419.15L450.84,421.56L450.91,422.98L451.7,423.65L451.06,424.21L451.25,424.58L450.18,425.23L450.38,425.53L451.53,425.27L451.9,425.65L451.32,426.94L451.32,426.94L450.3,427.46L449.8,428.24L449.51,429.71L448.92,430.59L449.02,431.36L448.2,431.08L447.79,431.79L446.84,431.04L445.95,431.23L445.46,430.62L444.83,431.27L444.38,430.29L444.98,429.63L445.02,427.91L444.28,426.98L443.36,427.48L443.93,428.36L443.49,428.9L443.73,429.24L443.07,429.41L442.52,428.97L442.87,428.1L442.47,426.47L442.96,426.35L443.25,424.97L442.5,424.63L441.21,425.13L439.53,424.46L440.11,422.81L439.57,421.95L439.75,421.38L439.21,420.24L439.1,418.62L438.69,418.67L438.23,419.37L437.7,418.81L437.13,419.08L436.07,418.6L435.39,419.47L434.6,419.32L433.29,420.37L432.56,418.96L432.75,417.82L432.18,417.14L431.24,417.77L430.78,417.31L430.07,417.41L429.74,416.67L428.75,417.32L428.32,416.89L427.78,417.16L427.94,418.15L427.34,418.35L427.54,419.25L427.24,419.68L428.15,420.59L427.49,421.88L426.94,421.98L426.72,422.44L425.6,423.08L425.27,422.23L424.2,421.39L423.47,421.7L422.77,421.44L422.34,421.91L422.44,422.98L421.99,423.08L421.3,422.45L420.51,423.08L420.33,423.54L421.12,424.35L420.36,425.64L419.74,425.19L419.79,426.02L419.14,426.46L419.43,427.12L419.15,427.56L418.85,427.62L418.63,427.06L418.32,427.23L417.52,426.8L417.16,427.12L418.25,424.66L417.29,424.25L417.46,423.84L415.93,422.16L415.58,422.31L415.69,422.91L416.55,424.48L415.87,425.76L416.27,426.41L415.04,425.91L414.72,426.56L414.72,426.56L414.16,426.17L414.22,425.02L413.82,424.91L413.15,422.8L413.23,422L412.7,421.31L410.57,421.13L410.54,420.52L409.78,420.07L408.73,421.22L408.47,420.82L407.47,420.67L406.76,419.15L406.94,418.8L406.42,418.33L406.68,417.91L406.1,415.66L405.58,416.18L405.37,415.87L403.09,415.63L403.17,415.2L402.71,415.23L402.6,414.85L402.81,413.97L402.14,413.7L402.19,413.33L401.4,413.4L401,412.03L401.29,411.09L401.63,411.29L402.57,409.98L403.29,410.06L403.4,409.44L403.91,409.66L404.47,409.24L405.03,407.77L406.52,407.46L406.32,406.43L406.71,404.97L406.05,404.76L404.12,402.85L405.61,402.33L405.17,401.36L406.53,401.64L406.35,400.95L406.69,400.13L407.78,399.06L408.01,399.66L408.59,399.41L411.76,400.73L412.98,402.66L413.65,402.41L413.78,401.94L415.38,401.74L415.6,401.07L417.08,400.92L418.06,401.45L417.75,402.13L419.42,404.94L421.64,404.87L422.26,404.15L422.84,404.83L423.5,404.51L425.51,402.07L426.13,400.58L427.14,399.4L427.44,398.15L429.03,396.06L427.94,394.53L429.05,393.11L429.96,392.94L430.66,393.51L431.68,392.45L432.26,393.49L433.62,393.59L434.99,392.14L434.43,391.87L434.53,391.04L435.94,390L435.38,389.14L436.34,387.67L435.56,387.52L435.57,387.1L437.53,385.87L437.2,385.24L437.9,384.75L438.09,383.93L437.7,383.47L439.38,383.25L440.68,381.44L441.3,381.59L441.27,381.26L442.02,380.82L441.68,379.97L441.93,379.55L441.51,379.13L440.95,379.18L441.03,378.8L439.67,378.05L438.57,376.47L439.02,376.07L439.84,376.19L439.42,375.68L439.74,375.29L439.59,374.88L440.88,374.99L440.46,373.95L439.95,373.76L439.99,373.24L440.39,372.99L441.36,373.22z" />
</vector>
直接上代码,自定义View
关于省份、国家等名称信息,网上找不到明确的样例
这里是通过SVG里的name信息,查找省份期刊号等进行识别对应省份,世界地图以各国家的英文缩写为name进行识别对应国家
public class MapView extends View {
private final Map<String, String> provinceId = new HashMap<String,String>(){
{
put("11", "北京"); put("12", "天津"); put("13", "河北");
put("14", "山西"); put("15", "内蒙古"); put("21", "辽宁");
put("22", "吉林"); put("23", "黑龙江"); put("31", "上海");
put("32", "江苏"); put("33", "浙江"); put("34", "安徽");
put("35", "福建"); put("36", "江西"); put("37", "山东");
put("41", "河南"); put("42", "湖北"); put("43", "湖南");
put("44", "广东"); put("45", "广西"); put("46", "海南");
put("50", "重庆"); put("51", "四川"); put("52", "贵州");
put("53", "云南"); put("54", "西藏"); put("61", "陕西");
put("62", "甘肃"); put("63", "青海"); put("64", "宁夏");
put("65", "新疆"); put("91", "澳门"); put("92","香港");
put("71","台湾");
}
};
private ExecutorService mThreadPool;
private Paint mPaint;
private int mMapResId = -1;
private List<PathItem> mItemList;
private RectF mMaxRect;
//现有确诊范围 1-9,10-99,100-999,1000-9999,10000
private int[] mColorArray = new int[] { Color.parseColor("#FFFFFF"),Color.parseColor("#cc6633"),Color.parseColor("#cc3300"), Color.parseColor("#cc0000"),Color.parseColor("#990000"),Color.parseColor("#660000") };
private PathItem mSelectItem;
private float mScale = 1f;
private Paint rectPaint;
public MapView(Context context) {
this(context, null);
}
public MapView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
mPaint = new Paint();
// 设置抗锯齿
mPaint.setAntiAlias(true);
// 初始化线程池
initThreadPool();
// 解析自定义属性
getMapResource(context, attrs, defStyleAttr);
rectPaint = new Paint();
rectPaint.setStyle(Paint.Style.FILL); // 设置样式
}
private void initThreadPool() {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r);
thread.setPriority(Thread.MAX_PRIORITY);
return thread;
}
};
mThreadPool = new ThreadPoolExecutor(1, 1, 10L, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(10), threadFactory,
new ThreadPoolExecutor.AbortPolicy());
}
private void getMapResource(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MapView, defStyleAttr, 0);
int resId = a.getResourceId(R.styleable.MapView_map, -1);
a.recycle();
setMapResId(resId);
}
/**
* 设置地图资源Id
*/
public void setMapResId(int resId) {
mMapResId = resId;
executeLoad();
}
private void executeLoad() {
if (mMapResId <= 0) {
return;
}
mThreadPool.execute(new Runnable() {
@Override
public void run() {
// 获取xml文件输入流
InputStream inputStream = getResources().openRawResource(mMapResId);
// 创建解析实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
// 解析输入流,得到Document实例
Document doc = builder.parse(inputStream);
// 获取根节点,即vector节点
Element rootElement = doc.getDocumentElement();
// 获取所有的path节点
NodeList items = rootElement.getElementsByTagName("path");
// 以下四个变量用来保存地图四个边界,用于确定缩放比例(适配屏幕)
float left = -1;
float right = -1;
float top = -1;
float bottom = -1;
// 解析path节点
List<PathItem> list = new ArrayList<>();
for (int i = 0; i < items.getLength(); ++i) {
Element element = (Element) items.item(i);
// 获取pathData内容
String pathData = element.getAttribute("android:pathData");
String name = element.getAttribute("android:name");
// 将pathData转换为path
Path path = PathParser.createPathFromPathData(pathData);
// 封装成PathItem对象
PathItem pathItem = new PathItem(path,getName(name));
pathItem.setDrawColor(mColorArray[getColor(name)]);
RectF rectF = new RectF();
// 计算当前path区域的矩形边界
path.computeBounds(rectF, true);
// 判断边界,最终获得的就是整个地图的最大矩形边界
left = left < 0 ? rectF.left : Math.min(left, rectF.left);
right = Math.max(right, rectF.right);
top = top < 0 ? rectF.top : Math.min(top, rectF.top);
bottom = Math.max(bottom, rectF.bottom);
list.add(pathItem);
}
// 解析完成,保存节点列表和最大边界
mItemList = list;
mMaxRect = new RectF(left, top, right, bottom);
// 通知重新布局和绘制
post(new Runnable() {
@Override
public void run() {
requestLayout();
invalidate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mThreadPool != null) {
// 释放线程池
mThreadPool.shutdown();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (mMaxRect != null) {
// 获取缩放比例
double mapWidth = mMaxRect.width();
mScale = (float) (width / mapWidth);
}
// 应用测量数据
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mItemList != null) {
// 使地图从画布左上角开始绘制(图片本身可能存在边距)
canvas.translate(-mMaxRect.left, -mMaxRect.top);
// 设置画布缩放,以(mMaxRect.left, mMaxRect.top)为基准进行缩放
// 因为当前该点对应屏幕左上(0, 0)点
canvas.scale(mScale, mScale, mMaxRect.left, mMaxRect.top);
// 绘制所有省份区域,并设置是否选中状态
for (PathItem pathItem : mItemList) {
pathItem.drawItem(canvas, mPaint, mSelectItem == pathItem );
}
//现有确诊范围 1-9,10-99,100-999,1000-9999,10000
rectPaint.setColor(mColorArray[1]);
canvas.drawRect(20,MyApplication.dp2px(180),30,MyApplication.dp2px(180)+10,rectPaint);
rectPaint.setColor(mColorArray[2]);
canvas.drawRect(20, MyApplication.dp2px(190),30,MyApplication.dp2px(190)+10,rectPaint);
rectPaint.setColor(mColorArray[3]);
canvas.drawRect(20, MyApplication.dp2px(200),30,MyApplication.dp2px(200)+10,rectPaint);
rectPaint.setColor(mColorArray[4]);
canvas.drawRect(20, MyApplication.dp2px(210),30,MyApplication.dp2px(210)+10,rectPaint);
rectPaint.setColor(mColorArray[5]);
canvas.drawRect(20, MyApplication.dp2px(220),30,MyApplication.dp2px(220)+10,rectPaint);
rectPaint.setColor(Color.BLACK);
rectPaint.setTextSize(20f);
canvas.drawText("1-9",35,MyApplication.dp2px(180)+10,rectPaint);
canvas.drawText("10-99",35,MyApplication.dp2px(190)+10,rectPaint);
canvas.drawText("100-999",35,MyApplication.dp2px(200)+10,rectPaint);
canvas.drawText("1000-9999",35,MyApplication.dp2px(210)+10,rectPaint);
canvas.drawText(">=10000",35,MyApplication.dp2px(220)+10,rectPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将事件分发给所有的区块,如果事件未被消费,则调用View的onTouchEvent,这里会默认范围false
if (handleTouch((int) (event.getX() / mScale + mMaxRect.left), (int) (event.getY() / mScale + mMaxRect.top), event)) {
return true;
}
return super.onTouchEvent(event);
}
/**
* 派发触摸事件
*/
private boolean handleTouch(int x, int y, MotionEvent event) {
if (mItemList == null) {
return false;
}
boolean isTouch = false;
PathItem selectItem = null;
for (PathItem pathItem : mItemList) {
// 依次派发事件
if (pathItem.isTouch(x, y, event)) {
// 选中省份区块
selectItem = pathItem;
isTouch = true;
break;
}
}
if (selectItem != null && selectItem != mSelectItem) {
mSelectItem = selectItem;
// 通知重绘
postInvalidate();
}
return isTouch;
}
private int getColor(String name){
int num = 0;
String[] arr = name.split("-");
//中国省份
if (arr.length == 2){
Children children = RetrofitUtil.getInstance().getProvinceDetail(provinceId.get(arr[1]));
if (children == null)
return 0;
num = children.getTotal().getConfirm() - children.getTotal().getDead() - children.getTotal().getHeal();
}
//国家
else {
AreaTree areaTree = RetrofitUtil.getInstance().getCountryDetail(countryId.get(name));
if (areaTree == null)
return 0;
num = areaTree.getTotal().getConfirm() - areaTree.getTotal().getDead() - areaTree.getTotal().getHeal();
}
if (num == 0)
return 0;
if (num > 0 && num < 10)
return 1;
if (num >= 10 && num < 100)
return 2;
if (num >= 100 && num < 1000)
return 3;
if (num >= 1000 && num < 10000)
return 4;
if (num >= 10000)
return 5;
return 0;
}
private String getName(String name){
String[] arr = name.split("-");
if (arr.length == 2){
Children children = RetrofitUtil.getInstance().getProvinceDetail(provinceId.get(arr[1]));
if (children != null)
return children.getName();
}
return "";
}
}
初始化View
<com.example.watcher.data.MapView
android:id="@+id/china_map"
android:layout_width="match_parent"
android:layout_height="300dp" />
viewBinding.chinaMap.setMapResId(R.raw.china_map);
快去试试吧!!!