一 需求
绘制中国地图,按省区分,点一个省,有选中效果
二 效果图
三 原理分析
- 省份是不规则的,不同的绘图方式不行,得用svg绘制
- 利用path绘制每一个省份
- 利用RegionAPI判断触摸是否在某个省份(Path)中
四 具体实现
世界地图SVG格式:
https://www.amcharts.com/dl/javascript-maps/
-
第一步 下载含有中国地图的 SVG
-
第二步 用http://inloop.github.io/svg2android/ 网站 将svg资源转换成相应的 Android代码
-
第三步 利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path
-
第四步 重写OnDraw方法 利用Path绘制中国地图
-
第五步 重写OnTouchEvent方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上
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():关闭路径(会自动绘制链接起点和终点)android:pathData="M541.02,336.29L541.71,336.09 L542.54,337.38L543.77,338.27 L543.53,338.58L545.92,338.99.8,350.12L561.12,349.61L562.97,349.6L563.89,349.94 L563.48,350.21L563.6,351.15L562.98,351.84L562.99,353.94L562.28,353.68L562.06,3 53.97L561.87,355.49L561.13,355.88L561.38,356.41L560.77,357.72L561.33,357.73 。。。。。 L562.06,359L563.49,358.5L563.75,357.85L564.17,358.09L564.64 ,361.19L565.52,361.68L564.51,362.21L564.67,363.38L565.17,363.21L565.35,364.41 L566.19,364.53L566.23,365.29L567.26,365L568.99,365.25L569.63,364.91 L539.3,337.63L539.84,336.78L540.31,336.88z" />
//指令详情可以 参考 http://www.w3school.com.cn/svg/svg_intro.asp
//SVG —> 动画
地图资源可以在 https://www.amcharts.com/dl/javascript-maps/ 下载
里面包含世界各个国家的SVG地图 各个省份地图
五 核心代码
SvgChinaView
/**
* Created by Ray on 2019-7-22.
*/
public class SvgChinaView extends View {
private Context context;
private List<ProvinceItem> list = new ArrayList<>();
private static int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, Color.RED};
private ProvinceItem selectItem;
private float scale = 1.3f;
private Paint paint;
private GestureDetectorCompat gestureDetectorCompat;
public SvgChinaView(Context context) {
this(context, null);
}
public SvgChinaView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SvgChinaView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
this.context = context;
this.paint = new Paint();
paint.setAntiAlias(true);
loadThread.start();
gestureDetectorCompat = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
handlerTouch(e.getX(),e.getY());
return true;
}
});
}
private void handlerTouch(float x, float y) {
if(list != null){
ProvinceItem temp = null;
for(ProvinceItem item:list){
if(item.isTouch((int)(x/scale), (int)(y/scale))){
temp = item;
break;
}
}
if(temp != null){
selectItem = temp;
postInvalidate();
}
}
}
Thread loadThread = new Thread() {
@Override
public void run() {
InputStream inputStream = context.getResources().openRawResource(R.raw.china);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream);
Element element = document.getDocumentElement();
NodeList items = element.getElementsByTagName("path");
for (int i = 0; i < items.getLength(); i++) {
Element element1 = (Element) items.item(i);
String pathData = element1.getAttribute("android:pathData");
@SuppressLint("RestrictedApi") Path path = PathParser.createPathFromPathData(pathData);
ProvinceItem item = new ProvinceItem(path);
list.add(item);
handler.sendEmptyMessage(1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (list != null) {
int totalNumber = list.size();
for (int i = 0; i < totalNumber; i++) {
int color = Color.WHITE;
int flag = i % 4;
switch (flag) {
case 1:
color = colorArray[0];
break;
case 2:
color = colorArray[1];
break;
case 3:
color = colorArray[2];
break;
default:
color = 0x00FFF0EF;
break;
}
list.get(i).setDrawColor(color);
}
postInvalidate();
}
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetectorCompat.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(list != null){
canvas.save();
canvas.scale(scale, scale);
for(ProvinceItem item:list){
if(item != selectItem){
item.draw(canvas,paint , false);
}
}
if(selectItem != null){
selectItem.draw(canvas,paint ,true );
}
}
}
}
ProvinceItem
public class ProvinceItem {
private Path path;
private int drawColor;
public ProvinceItem(Path path) {
this.path = path;
}
public void draw(Canvas canvas, Paint paint, boolean isSelect) {
if (isSelect) {
paint.setStrokeWidth(2);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
paint.setShadowLayer(8, 0, 0,0xFFFFFFFF);
canvas.drawPath(path, paint);
paint.clearShadowLayer();
paint.setColor(drawColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
canvas.drawPath(path, paint);
} else {
paint.clearShadowLayer();
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xffD0E8F4);
canvas.drawPath(path, paint);
}
}
public boolean isTouch(int x, int y) {
RectF rect = new RectF();
path.computeBounds(rect, true);
Region region = new Region();
region.setPath(path, new Region((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom));
boolean result = region.contains(x, y);
Log.i("ProvinceItem", "isTouch x="+ result );
return result;
}
public int getDrawColor() {
return drawColor;
}
public void setDrawColor(int drawColor) {
this.drawColor = drawColor;
}
}