Android实现四色填充地图功能

龙旋

共 17446字,需浏览 35分钟

 ·

2022-07-07 22:14

先上效果图:



明显是一个自定义view,先解析svg资源(该资源不严谨,请勿在正规),获取每个省的path,再用四色算法设置每个省的颜色,先列举主要方法解析svg文件

 InputStream inputStream = context.getResources().openRawResource(R.raw.china);            proviceItems = new ArrayList<>();            try {                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();                DocumentBuilder builder = null;                builder = factory.newDocumentBuilder();                Document document = builder.parse(inputStream);                Element rootElement = document.getDocumentElement();                NodeList items = rootElement.getElementsByTagName("path");


items就是每个省份的边框了,遍历全部省份确定地图的最左最右最上最下,从而确定地图的真正宽高,然后再对比自定义View的宽度,确定画图的缩放比例,再定义自定义View的高度

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int width = MeasureSpec.getSize(widthMeasureSpec);        if (totalRect != null && width != 0) {            //获取到地图的矩形的宽度            double mapWidth = totalRect.width();            //获取到比例值            scale = (float) (width / mapWidth);            //用宽度重新定义高度            heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (totalRect.height() * scale), MeasureSpec.EXACTLY);        }        super.onMeasure(widthMeasureSpec,heightMeasureSpec);    }

重写onDraw方法,把每个省依次华进去,如果有点击事件,被点击有变化的话,多数情况下都是要最后一个话

   @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (proviceItems != null) {            int tatalNum = proviceItems.size();            canvas.save();            canvas.scale(scale, scale);            ProviceItem selsetProviceItem = null;            // 先画没被选中的            for (int i = 0; i < tatalNum; i++) {                if (!proviceItems.get(i).isSelect()) {                    proviceItems.get(i).drawItem(canvas, paint);                } else {                    selsetProviceItem = proviceItems.get(i);                }            }            //被选中的最后画,因为被选中的有阴影            if (selsetProviceItem != null) {                selsetProviceItem.drawItem(canvas, paint);            }        }    }

把每个省都画到地图上的方法

 paint.setStrokeWidth(1);        paint.setColor(drawColor);        paint.setStyle(Paint.Style.FILL_AND_STROKE);        canvas.drawPath(path, paint);        if (isSelect) {            //被选择设置一下阴影            paint.setShadowLayer(20, 0, 0, Color.WHITE);        } else {            //没选中去掉阴影            paint.clearShadowLayer();        }        canvas.drawPath(path, paint);

接下来就是关于颜色的选择问题了,写一个获得颜色的工具类,主要参数和构造方法

    //存放颜色种类,并非真正的颜色    private int[] colorTypes;    //板块接壤矩阵,1为接壤    private int[][] isBorder;    //准备填充的颜色列表    private int[] colors;    //颜色多少种类    private int TYPE_SIZE ;    //总共有几个板块    private int plateCount;
public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{ plateCount = isBorder.length; if (plateCount != isBorder[0].length) {//板块相邻关系必须是方阵,不能是矩阵 throw new Exception("colors's length must be equal to isBorder's length!"); } this.colors = colors; TYPE_SIZE = colors.length; this.isBorder = isBorder; }


思路就是从第一个省份开始慢慢尝试填充颜色,尝试方法就是从可选的颜色种类中,依次填充进去,然后再判断是否和已经填充的身份,是否有接壤并且同个颜色的,如果有就换一个颜色,如果最后每个颜色都尝试了还是不行就说明上一个板块填充有误,要回退到上个板块,如果上板块还是不行再回退,最后直到每个板块都设置好颜色,颜色种类如果小于4可能会填充失败。详细见后面代码,以下是自定义省份的been

public class ProviceItem {    private int index;    private Path path;    //省份颜色    private int drawColor;    //是否被点击    private boolean isSelect;
public ProviceItem(Path path) { this.path = path; }
public void setDrawColor(int drawColor) { this.drawColor = drawColor; }

public void setIndex(int index) { this.index = index; }
public boolean isSelect() { return isSelect; }
public void setSelect(boolean select) { isSelect = select; }
public void drawItem(Canvas canvas, Paint paint) { paint.setStrokeWidth(1); paint.setColor(drawColor); paint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawPath(path, paint); if (isSelect) { //被选择设置一下阴影 paint.setShadowLayer(20, 0, 0, Color.WHITE); } else { //没选中去掉阴影 paint.clearShadowLayer(); } canvas.drawPath(path, paint); }
public boolean isTouch(float x, float y) { //创建一个矩形 RectF rectF = new RectF(); //获取到当前省份的矩形边界 path.computeBounds(rectF, true); //创建一个区域对象 Region region = new Region(); //将path对象放入到Region区域对象中 region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom)); //返回是否这个区域包含传进来的坐标 boolean resule = region.contains((int) x, (int) y); //无法通过代码确定两个省份是否接壤所以只能获取下标,人工构造省份相邻矩阵,如果 // if (result) { // Log.d("ProviceItemIndex-----", index + ""); // } return result; }
}


自定义view的代码

public class MapView extends View {    private Paint paint;    private Context context;    //整个地图所占用的矩形,在重新设配之前    private RectF totalRect;    private List<ProviceItem> proviceItems;    //绘制地图的颜色    private int[] colorArray = new int[]{0xFF1383f2, 0xFFFFDC00, 0xFFFF3D33, 0xFF4ADE8C};    //适配比例    private float scale = 0;    int[] colors;    //中国省份接壤关系矩阵,划分34个省份,自治区,市和特别行政区等,但是多一个颜色表示国外的颜色项目中最后没有用到    int[][] isBorder = {            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},            {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},            {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},            {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1},            {0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},

};
public MapView(Context context) { super(context); }
public MapView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); }

public MapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
private void init(Context context) { this.context = context; paint = new Paint(); paint.setAntiAlias(true); //开线程解析数据 loadThread.start(); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); if (totalRect != null && width != 0) { //获取到地图的矩形的宽度 double mapWidth = totalRect.width(); //获取到比例值 scale = (float) (width / mapWidth); //用宽度重新定义高度 heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (totalRect.height() * scale), MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
@Override protected void onDraw(Canvas canvas) { //如果省份数据还没加载出来,实际缩放比例没定义出来啥都不用干 if (proviceItems != null||scale==0) { super.onDraw(canvas); int tatalNum = proviceItems.size(); canvas.save(); canvas.scale(scale, scale); ProviceItem selsetProviceItem = null; // 先画没被选中的 for (int i = 0; i < tatalNum; i++) { if (!proviceItems.get(i).isSelect()) { proviceItems.get(i).drawItem(canvas, paint); } else { selsetProviceItem = proviceItems.get(i); } } //被选中的最后画,因为被选中的有阴影 if (selsetProviceItem != null) { selsetProviceItem.drawItem(canvas, paint); } } }
private Thread loadThread = new Thread(new Runnable() { @Override public void run() { InputStream inputStream = context.getResources().openRawResource(R.raw.china); proviceItems = new ArrayList<>(); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null; builder = factory.newDocumentBuilder(); Document document = builder.parse(inputStream); Element rootElement = document.getDocumentElement(); NodeList items = rootElement.getElementsByTagName("path"); //定义一个不可能存在屏幕上的很右边的点Integer.MAX_VALUE作为最左边,同理定义一个不能存在屏幕上的很左边的点-1作为为最左边 //因为循环每个省份的最左最右最上最下,左边下标只会越来越小 float left = Integer.MAX_VALUE; float right = -1; float top = Integer.MAX_VALUE; float bottom = -1; 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); ProviceItem proviceItem = new ProviceItem(path); //设置省份下标 //proviceItem.setIndex(i); proviceItems.add(proviceItem); RectF rectF = new RectF(); path.computeBounds(rectF, true); left = Math.min(left, rectF.left); right = Math.max(right, rectF.right); top = Math.min(top, rectF.top); bottom = Math.max(bottom, rectF.bottom); } //创建整个地图 totalRect = new RectF(left, top, right, bottom); try { if (colors == null) { colors = new ColorFillUtil(isBorder, colorArray).getColors(); int totalNumber = proviceItems.size(); for (int i = 0; i < totalNumber; i++) { proviceItems.get(i).setDrawColor(colors[i]); } handler.sendEmptyMessage(0); } } catch (Exception e) { e.printStackTrace(); }
} catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } });

private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { //返回主线程调用以下方法 //重新测量,调用onMeasure requestLayout(); //重新绘图,系统调用onDraw invalidate(); } };
@Override public boolean onTouchEvent(MotionEvent event) { //将当前手指触摸到位置传过去 判断当前点击的区域 handlerTouch(event.getX(), event.getY()); return super.onTouchEvent(event); }
/** * 判断区域 * * @param x * @param y */ private void handlerTouch(float x, float y) { //判空 if (proviceItems == null || proviceItems.size() == 0) { return; } for (ProviceItem proviceItem : proviceItems) { //入股点击的是这个省份的范围之内 就把当前省份的封装对象绘制的方法 传一个true proviceItem.setSelect(proviceItem.isTouch(x / scale, y / scale)); } postInvalidate(); }


颜色选择工具类

/** * 板块颜色填充工具 */public class ColorFillUtil {    //存放颜色种类,并非真正的颜色    private int[] colorTypes;    //板块接壤矩阵,1为接壤    private int[][] isBorder;    //准备填充的颜色列表    private int[] colors;    //颜色多少种类    private int TYPE_SIZE ;    //总共有几个板块    private int plateCount;
public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{ plateCount = isBorder.length; if (plateCount != isBorder[0].length) {//板块相邻关系必须是方阵,不能是矩阵 throw new Exception("colors's length must be equal to isBorder's length!"); } this.colors = colors; TYPE_SIZE = colors.length; this.isBorder = isBorder; }

/** * 获取最后的结果 * @return */ public int[] getColors() {
colorTypes = new int[plateCount]; int index = 0; int colorType = 0; while (index < plateCount) {
if (setColor(index, colorType)) { //设置颜色种类暂时成功,接着下一个,直到全部颜色设置完成,设置颜色种类从0开始尝试 index++; colorType = 0; } else { //找不到合适的颜色要回退,上一个板块修改颜色 index--; colorType = colorTypes[index] + 1; if (index == 0) //无法求解,可能是是颜色种类太少 return null; }
} return getRealColors(); }
/** * 返回真正的颜色列表 * @return */ private int[] getRealColors() { int[] result = new int[plateCount]; for (int i = 0; i < plateCount; i++) { result[i] = colors[colorTypes[i]]; } return result; }
/** * 尝试填充颜色 填充成功返回true * @param index 准备填充的板块下标 * @param colorType 准备填充的颜色种类 * @return */ private boolean setColor(int index, int colorType) { if (colorType >= TYPE_SIZE) return false; while (colorType < TYPE_SIZE) { //是否可以设置颜色种类 boolean canSet = true; //循环判断准备填充的颜色与之前的颜色是否冲突 for (int i = 0; i < index; i++) { //isBorder[i][index] == 1 表示之前已经填充的第i个板块和准备填充的板块是接壤的 //colorType == colorTypes[i] 同时准备填充的颜色种类又是一样的,则准备填充的颜色要改变,再重新尝试填充 if (isBorder[i][index] == 1 && colorType == colorTypes[i]) {
++colorType; canSet = false; break; } } if (canSet) { colorTypes[index] = colorType; return true; } } //找不到合适的颜色,要回退 return false; }}


部分资源文件
https://pan.baidu.com/s/1Tgq84epnaFhmiEBotGeBgw

浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报