美文网首页
Android 我们的矢量地图,放大不失真

Android 我们的矢量地图,放大不失真

作者: as_pixar | 来源:发表于2020-04-28 23:00 被阅读0次

地图的轮廓看起来是不规则的,不规则轮廓就是地图的路线。路线在手,说走就走。我们一起来写一个矢量地图。

先来了解矢量图形,XML 文件中定义为一组点、一组线条和一组曲线及其相关颜色信息。

  • 矢量图形 svg 用 XML语言来编写,文件是纯粹的 XML
  • SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  • SVG 是W3C的标准
  • 可在图像质量不下降的情况下被放大

使用矢量图形的主要优势在于图片可缩放。可以在不降低显示质量的情况下缩放图片。也就是说,可以针对不同的屏幕密度调整同一文件的大小,而不会降低图片质量。不仅能缩减 APK 文件大小,还能减少开发者维护工作。您还可以对动画使用矢量图片,使用多个 XML 文件,而不是多张图片。

Android 5.0(API 级别 21)是第一个使用矢量图像的。使用兼容看可以支持更低的版本。

下载svg 地图网站

绘制地图的轮廓

我们是怎么绘制地图轮廓的呢?

新建了一个属性文件 attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--    是否仅仅绘制轮廓-->
    <attr name="isOnlyProfile" format="boolean" />

    <declare-styleable name="MapView">
        <attr name="isOnlyProfile" />
    </declare-styleable>

</resources>

我们在布局main布局文件中 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.wwj.ourmap.MapView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:isOnlyProfile="true" />

</RelativeLayout>

在MapView.java文件中,获取是否仅仅显示轮廓的属性值

 /**
     * 是否仅仅绘制轮廓
     */
    private boolean isOnlyProfile;

    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);
    }


    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView);
        isOnlyProfile = typedArray.getBoolean(R.styleable.MapView_isOnlyProfile, false);


        typedArray.recycle();

        this.mContext = context;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        loadPathData();
    }

我们接下来获取控件的宽度,我们以屏幕宽度为主

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        map 的宽度  和高度
        if (mMapRect != null) {
            double mapWidth = mMapRect.width();

            //控件宽度
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);

            int finalWidth = Math.min(width, height);

            //计算控件宽度和地图宽度的比例  比如
            //以屏幕宽度为主
            mScale = (float) (finalWidth / mapWidth);
            Log.d("tag", "-------scale=" + mScale);
        }
    }

这部分代码很简单

我们的地图路线是怎么生成的呢?

地图路线是美工或者UI设计师已经画好了的,是一个china.svg 文件,用一个xml文件描述,比如我们画一个圆圈,用svg怎么做呢?
svg 菜鸟教程
有很多的学习教程

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg> 

加载地图路线数据

/**
     * 解析svg 矢量图数据
     */
    private void loadPathData() {
        //获取矢量图输入流
        InputStream inputStream = mContext.getResources().openRawResource(R.raw.china);
        List<ProvinceItem> list = new ArrayList<>();
        try {
            //文档施工队工厂
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //文档施工队的某位师傅
            DocumentBuilder builder = factory.newDocumentBuilder();
            //w3c 的Document
            Document document = builder.parse(inputStream);

            //文档中的根元素
            Element rootElement = document.getDocumentElement();

            //获取名称为path 的节点列表
            NodeList items = rootElement.getElementsByTagName("path");
//                中国地图的  矩形
            float left = -1;
            float right = -1;
            float top = -1;
            float bottom = -1;
            RectF rect = new RectF();
            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);

                //要绘制的地图颜色,随机生成要绘制的地图颜色
                int color;
                int flag = i % 4;
                switch (flag) {
                    case 1:
                        color = ContextCompat.getColor(mContext, R.color.deep_sky_blue);
                        break;
                    case 2:
                        color = ContextCompat.getColor(mContext, R.color.cerulean_blue);
                        break;
                    case 3:
                        color = ContextCompat.getColor(mContext, R.color.light_sky_blue);
                        break;
                    default:
                        color = ContextCompat.getColor(mContext, R.color.cyan);
                        break;
                }
                // 一条省份路线数据
                ProvinceItem provinceItem = new ProvinceItem(path, color, mContext);
                list.add(provinceItem);

                //获取宽高
                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);
                mMapRect.set(left, top, right, bottom);
            }
            mProvinceItemList = list;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

获取矢量图输入流,创建了一个文档施工队工厂,创建一个文档施工队师傅解析矢量图输入流,接着获取文档根元素,获取根元素中的path节点列表,通过android:pathData获取每个节点值。这个值就是一个省的路线。我们通过PathParser类解析为一个android的Path对象 Path path = PathParser.createPathFromPathData(pathData); 有了路线,说走就走,接下来我们看看怎么绘制地图轮廓。 PathParse类在android.util下,我们通过Everything搜索某个类。

@Override
    protected void onDraw(Canvas canvas) {
        if (mProvinceItemList != null) {
            canvas.save();
            canvas.scale(mScale, mScale);
            for (ProvinceItem provinceItem : mProvinceItemList) {
                if (provinceItem != this.mSelectProvinceItem) {
                    provinceItem.drawItem(canvas, mPaint, false,isOnlyProfile);
                } else {
                    provinceItem.drawItem(canvas, mPaint, true,isOnlyProfile);
                }
            }
        }
    }

/**
     * 绘制某一个省的地图路线
     *
     * @param canvas
     * @param paint
     * @param isSelect      是否选中
     * @param isOnlyProfile 是否仅仅绘制轮廓
     */
    void drawItem(Canvas canvas, Paint paint, boolean isSelect, boolean isOnlyProfile) {
        if (isSelect) {
            //清除阴影层
            paint.clearShadowLayer();

            //设置边界
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(2);
            paint.setColor(Color.MAGENTA);
            paint.setShadowLayer(6, 0, 0, Color.BLUE);
//            radius:模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,则阴影消失不见
//            dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移
//            dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移
//            color: 绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)

            canvas.drawPath(mPath, paint);

            if (isOnlyProfile) {
                return;
            }

            //选中时,绘制描边效果
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(mDrawColor);
            paint.setStrokeWidth(1);
            canvas.drawPath(mPath, paint);
        } else {
            //设置边界
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(1);
            paint.setColor(Color.BLACK);
            paint.setShadowLayer(8, 0, 0, Color.WHITE);
            canvas.drawPath(mPath, paint);

            if (isOnlyProfile) {
                return;
            }
            //后面是填充
            paint.clearShadowLayer();
            paint.setColor(mDrawColor);
            paint.setStyle(Paint.Style.FILL);
            paint.setStrokeWidth(2);
            canvas.drawPath(mPath, paint);
        }
    }

onDraw方法中通过遍历每一个省份,绘制每一个省的路线图。isOnlyProfile 如果为真仅仅绘制轮廓,绘制轮廓主要就是canvas.drawPath()方法,第一个参数传递的是要绘制路线,第二个是画笔。就这样我们就绘制了地图轮廓图。如果仅仅绘制轮廓为假,绘制五彩斑斓的地图。

我们点击地图,会有选中效果,这部分是怎么做的呢?

判断点击哪个省份

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        handleTouch(event.getX(), event.getY());
        return super.onTouchEvent(event);
    }

    /**
     * 点击某个省份进行绘制
     *
     * @param x 点击x坐标
     * @param y 点击y坐标
     */
    private void handleTouch(float x, float y) {
        if (mProvinceItemList == null) {
            return;
        }
        for (ProvinceItem provinceItem : mProvinceItemList) {
            if (provinceItem.isTouch(x / mScale, y / mScale)) {
                mSelectProvinceItem = provinceItem;
                postInvalidate();
                break;
            }
        }
    }


/**
     * true 包含点击的x y 左边
     *
     * @param x 坐标
     * @param y 坐标
     * @return
     */
    public boolean isTouch(float x, float y) {
        RectF rectF = new RectF();
        /**
         * 获取路径的举行
         */
        mPath.computeBounds(rectF, true);
//        rectF   矩形  包含了Path
        Region region = new Region();

        //设置路径范围
        region.setPath(mPath, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
        //x y 是否在这个坐标中
        return region.contains((int) x, (int) y);
    }

在onTouchEvent方法中获取手指点击屏幕的x,y坐标,判断是在哪个省份中。 region.setPath是取路线和矩形的较劲,最后判断是否在这个区域中。

下一节我们来聊聊svg矢量动画。

项目下载地址

相关文章

  • Android 我们的矢量地图,放大不失真

    地图的轮廓看起来是不规则的,不规则轮廓就是地图的路线。路线在手,说走就走。我们一起来写一个矢量地图。 先来了解矢量...

  • 矢量图

    由于正常的图片在放大过程中会产生失真,所有我们在开发项目时,常常采用矢量图, 这种图片在放大缩小之后不会产生失真,...

  • 浅谈SVG及矢量图在Android的应用

    1. 图像基础 图像分为矢量图和栅格图两种,这两张格式最直观的区别是矢量图可以无限放大而不失真,而矢量图放大或缩小...

  • 图片放大一百倍不失真,这个神器我一定要推荐给你

    大家都知道我们的图片可以分为两种类型:位图和矢量图。两者最大的区别的矢量图无论放大多少倍都不会失真,而位图放大看就...

  • 第四天 矢量形状与路径

    (一) 矢量形状与位图 矢量图:由点,线,形,以数学矢量方式来记录的图形特点:放大不失真位图:是由像素来构成的图像...

  • 图形图像基础

    一、 位图与矢量图 定义位图:优点:利于显示色彩层次丰富的写实图像。缺点:文件较大,放大和缩小图像会失真。矢量图:...

  • svg 介绍

    简介 svg 是『矢量图』,相比于『位图』,矢量图的特点是不会随着图片放大而失真,能做到这一点,主要是由于矢量图存...

  • Android矢量图VectorDrawable

    一、前言 我们通常使用的图片有.jpeg.gif.png(三格图) 和.svg(矢量图)放大不失真,存储也方便。 ...

  • 五、Android SVG动画

    SVG的全称是Scalable Vector Graphics(可缩放矢量图形),它不会因为图像放大而失真,且占用...

  • PS基础干货总结

    矢量: 1、矢量——放大缩小不会模糊失真 按住command键,拖动点改变锚点位置,拖动线可以变成平行四边形(同时...

网友评论

      本文标题:Android 我们的矢量地图,放大不失真

      本文链接:https://www.haomeiwen.com/subject/gawbwhtx.html