惯例先上效果图:
中国地图的SVG还是比较复杂的,一般SVG图也不是程序员来做的,所以我们直接下载SVG或者问美工要即可。
下载地址
下载下来是:china.svg 文件
每一给路径path就是一个省path,
大致思路 : 解析china.svg文件–>存入省份路径集合中–>画布上绘制–>交互事件处理。
public class ChinaMapView extends View {
private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};//各省地图显示的颜色
private Context context;//上下文
private List itemList;//各省地图列表 各省地图颜色 与路径
private Paint paint; //初始化画笔
private ProviceItem select; //选中的省份
private RectF totalRect;//中国地图的矩形范围
private float scale = 1.0f;//中国地图的缩放比例
public ChinaMapView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ChinaMapView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(Context context) {
this.context = context;
paint = new Paint();
paint.setAntiAlias(true);
itemList = new ArrayList<>();
loadThread.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取当前控件的高度 让地图宽高适配当前控件
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (totalRect != null) {
double mapWidth = totalRect.width();
scale = (float) (width / mapWidth); //获取控件高度为了让地图能缩放到和控件宽高适配
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
//加载中国地图的路径相对比较耗时,这里开启子线程来加载
private Thread loadThread = new Thread() {
@Override
public void run() {
final InputStream inputStream = context.getResources().openRawResource(R.raw.china);//读取地图svg
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //获取DocumentBuilderFactory实例
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);//解析svg的输入流
Element rootElement = doc.getDocumentElement();
NodeList items = rootElement.getElementsByTagName("path");
//获取地图的整个上下左右位置,
float left = -1;
float right = -1;
float top = -1;
float bottom = -1;
List list = new ArrayList<>();
for (int i = 0; i < items.getLength(); i++) {
Element element = (Element) items.item(i);
String pathData = element.getAttribute("android:pathData");
@SuppressLint("RestrictedApi")
Path path = PathParser.createPathFromPathData(pathData);
ProviceItem proviceItem = new ProviceItem(path);//设置路径
proviceItem.setDrawColor(colorArray[i % 4]);//设置颜色
//取每个省的上下左右 最后拿出最小或者最大的来充当 总地图的上下左右
RectF rect = new RectF();
path.computeBounds(rect, true);
left = left == -1 ? rect.left : Math.min(left, rect.left);
right = right == -1 ? rect.right : Math.max(right, rect.right);
top = top == -1 ? rect.top : Math.min(top, rect.top);
bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
list.add(proviceItem);
}
itemList = list;
totalRect = new RectF(left, top, right, bottom);//设置地图的上下左右位置
//加载完以后刷新界面
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
requestLayout();
invalidate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
@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 (itemList == null){
return;
}
ProviceItem selectItem = null;
for (ProviceItem proviceItem : itemList){
if (proviceItem.isTouch(x,y)){
selectItem = proviceItem;
}
}
if (selectItem != null){
select = selectItem;
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (itemList != null){
canvas.save();
canvas.scale(scale,scale);//把画布缩放匹配到本控件的宽高
for (ProviceItem proviceItem : itemList){
if (proviceItem != select){
proviceItem.drawItem(canvas,paint,false);
}else {
proviceItem.drawItem(canvas,paint,true);
}
}
}
}
}
省份封装代码如下:
public class ProviceItem {
private Path path;
/**
* 绘制颜色
* */
private int drawColor;
public void setDrawColor(int drawColor){
this.drawColor = drawColor;
}
public ProviceItem(Path path) {
this.path = path;
}
public void drawItem(Canvas canvas, Paint paint,boolean isSelect){
if (isSelect){
//绘制内部颜色
paint.clearShadowLayer();
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xffff0000);
canvas.drawPath(path,paint);
//绘制边界
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xFFD0E8F4);
canvas.drawPath(path,paint);
}else {
paint.setStrokeWidth(2);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
paint.setShadowLayer(8,0,0,0xffffff);
canvas.drawPath(path,paint);
//绘制边界
paint.clearShadowLayer();
paint.setColor(drawColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
canvas.drawPath(path,paint);
}
}
public boolean isTouch(float x,float y){//注意注意这块是来判断点击位置的 主要知识点Region
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));
return region.contains((int)x,(int)y);
}
}
布局使用代码如下:
Android 利用svg实现不规则 遥控器自定义控件,原理如上。如有需求留言,我会贴代码出来。
资源下载:csdn资源
需要共享分,如果没有共享分可以去github
上下载
github地址:https://github.com/rain86/MileAndroid
在如下图CustomView目录下