Android自定义View--通过SVG实现中国地图,各省可点击交互

惯例先上效果图:
在这里插入图片描述

中国地图的SVG还是比较复杂的,一般SVG图也不是程序员来做的,所以我们直接下载SVG或者问美工要即可。

一、下载中国地图:

下载地址

下载下来是:china.svg 文件

文件内容如下:
在这里插入图片描述

每一给路径path就是一个省path,

二、放入Android工程res/raw/下面

三、自定义view代码如下:

大致思路 : 解析china.svg文件–>存入省份路径集合中–>画布上绘制–>交互事件处理。


public class ChinaMapView extends View {

    private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};//各省地图显示的颜色
    private Context context;//上下文
    private List<ProviceItem> 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<ProviceItem> 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);
    }
}

布局使用代码如下:

<com.goodboy.mile.View.ChinaMapView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

通过上面的思路我们还可以实现如下自定义控件:

Android 利用svg实现不规则 遥控器自定义控件,原理如上。如有需求留言,我会贴代码出来。
在这里插入图片描述

资源下载:csdn资源
需要共享分,如果没有共享分可以去github
上下载
github地址:https://github.com/rain86/MileAndroid
在如下图CustomView目录下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/yanwenyuan0304/article/details/94737656