自定义View之SVG矢量图实现不规则图像及点击事件

先上效果图,利用SVG中国地图实现不规则图形,点击各省的不规则图像,让省份颜色变红。

原理解析

svg矢量图是一种体积小,放大后都不会模糊的图片格式,适用于纯色或者简单的图案,svg矢量图其实也可以看成类似xml的文件,使用文本编辑器就可以打开,svg都有一个path,类似于java中的path,我们可以利用DocumentBuilderFactory对svg进行解析,从而拿到svg的path路径,然后重新绘制,但是解析是一个耗时的事情,我们开启一个子线程进行解析。

代码实现

首先是定义一个Map类继承自View,在构造方法中对画笔等进行初始化,然后开启一个线程解析svg

    public Map(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }


    private void init() {
        provices = new ArrayList();
        paint = new Paint();
        paint.setAntiAlias(true);
        provices = new ArrayList<>();
        new LoadThread().start();//读取数据
    }

然后我们定义一个省份的JavaBean,每个省份都是一个对象,每个省份包括自己的path路径和绘制颜色,完了我们在创建一个绘制path的方法,最后关于点击,我们创建一个方法public boolean isTouch(float x, float y),传入坐标点,判断坐标是否在该省份的path范围内。


public class ProviceBean {
    private Path mPath;
    private int drawColor;

    public ProviceBean(Path path) {
        mPath = path;
    }

    public void setDrawColor(int drawColor) {
        this.drawColor = drawColor;
    }

    void drawItem(Canvas canvas, Paint paint, boolean isSelect) {
        if (isSelect) {
//            绘制内部的颜色
            paint.clearShadowLayer();
            paint.setStrokeWidth(1);
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(Color.RED);
            canvas.drawPath(mPath, paint);
//            绘制边界
            paint.setStyle(Paint.Style.STROKE);
            int strokeColor = 0xFFD0E8F4;
            paint.setColor(strokeColor);
            canvas.drawPath(mPath, paint);
        } else {
//            绘制内部的颜色
            paint.setStrokeWidth(2);
            paint.setColor(Color.BLACK);
            paint.setStyle(Paint.Style.FILL);
            paint.setShadowLayer(8, 0, 0, 0xffffff);
            canvas.drawPath(mPath, paint);
//            绘制边界
            paint.clearShadowLayer();
            paint.setColor(drawColor);
            paint.setStyle(Paint.Style.FILL);
            paint.setStrokeWidth(2);
            canvas.drawPath(mPath, paint);
        }

    }

    public boolean isTouch(float x, float y) {
        RectF rectF = new RectF();
        mPath.computeBounds(rectF, true);
        Region region = new Region();
        region.setPath(mPath, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
        return region.contains((int) x, (int) y);
    }


}

完了定义一个Map类继承自View ,在构造函数对paint进行初始化,并且开启一个线程解析svg矢量图,因为解析是一个耗时的过程,单独使用一个线程。

    public Map(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        provices = new ArrayList();
        paint = new Paint();
        paint.setAntiAlias(true);
        provices = new ArrayList<>();
        new LoadThread().start();//读取数据
    }


    private class LoadThread extends Thread {
        private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1};

        @Override
        public void run() {
            super.run();

            final InputStream inputStream = mContext.getResources().openRawResource(R.raw.map_china);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  //取得DocumentBuilderFactory实例
            DocumentBuilder builder = null; //从factory获取DocumentBuilder实例
            try {
                builder = factory.newDocumentBuilder();
                Document doc = builder.parse(inputStream);   //解析输入流 得到Document实例
                Element rootElement = doc.getDocumentElement();
                NodeList items = rootElement.getElementsByTagName("path");
                float left = -1;
                float right = -1;
                float top = -1;
                float bottom = -1;
                List<ProviceBean> list = new ArrayList<>();
                for (int i = 0; i < items.getLength(); i++) {
                    Element element = (Element) items.item(i);
                    String pathData = element.getAttribute("d");
                    Path path = PathParser.createPathFromPathData(pathData);
                    ProviceBean proviceItem = new ProviceBean(path);
                    proviceItem.setDrawColor(colorArray[i % 3]);
                    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);
                }
                provices = 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();
            }
        }
    }

为了让整个地图的宽度铺满屏幕的宽度,我们在这里重写onMeasure方法,获得一个屏幕宽度与svg图计算宽度的比例系数,然后在onDraw方法中对画布进行缩放使其正好铺满屏幕的宽度

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //        获取到当前控件宽高值
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (totalRect != null) {
            scale = width / totalRect.width();
        }

        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.scale(scale, scale);
        for (ProviceBean provice : provices) {
            provice.drawItem(canvas, paint, select == provice ? true : false);
        }
    }

最后就是点击事件的处理了,这里比较简单,因为我们对画布做了缩放,因此在点击这里也应该对坐标做相应的缩放。

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        for (ProviceBean provice : provices) {
            boolean isTouch = provice.isTouch(event.getX() / scale, event.getY() / scale);
            if (isTouch) {
                select = provice;
                invalidate();
                break;
            }

        }

        return super.onTouchEvent(event);
    }

Map的完整代码


public class Map extends View {
    private List<ProviceBean> provices;
    private Paint paint;
    private ProviceBean select;
    private RectF totalRect;
    private float scale = 1.0f;//缩放比例,让地图在屏幕全屏显示
    private Context mContext;

    public Map(Context context) {
        this(context, null);
    }

    public Map(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }


    public Map(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        provices = new ArrayList();
        paint = new Paint();
        paint.setAntiAlias(true);
        provices = new ArrayList<>();
        new LoadThread().start();//读取数据
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //        获取到当前控件宽高值
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (totalRect != null) {
            scale = width / totalRect.width();
        }

        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.scale(scale, scale);
        for (ProviceBean provice : provices) {
            provice.drawItem(canvas, paint, select == provice ? true : false);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        for (ProviceBean provice : provices) {
            boolean isTouch = provice.isTouch(event.getX() / scale, event.getY() / scale);
            if (isTouch) {
                select = provice;
                invalidate();
                break;
            }
        }

        return super.onTouchEvent(event);
    }

    private class LoadThread extends Thread {
        private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1};

        @Override
        public void run() {
            super.run();
            final InputStream inputStream = mContext.getResources().openRawResource(R.raw.map_china);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  //取得DocumentBuilderFactory实例
            DocumentBuilder builder = null; //从factory获取DocumentBuilder实例
            try {
                builder = factory.newDocumentBuilder();
                Document doc = builder.parse(inputStream);   //解析输入流 得到Document实例
                Element rootElement = doc.getDocumentElement();
                NodeList items = rootElement.getElementsByTagName("path");
                float left = -1;
                float right = -1;
                float top = -1;
                float bottom = -1;
                List<ProviceBean> list = new ArrayList<>();
                for (int i = 0; i < items.getLength(); i++) {
                    Element element = (Element) items.item(i);
                    String pathData = element.getAttribute("d");
                    Path path = PathParser.createPathFromPathData(pathData);
                    ProviceBean proviceItem = new ProviceBean(path);
                    proviceItem.setDrawColor(colorArray[i % 3]);
                    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);
                }
                provices = 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();
            }
        }
    }
}

Demo:https://github.com/987570437/SVGDemo

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue中使用SVG矢量图,你可以按照以下步骤进行配置: 1. 首先,确保你的项目中有一个存放SVG图标的文件夹,比如`src/icons`。在这个文件夹中,你可以存放所有的SVG图标文件。 2. 在你的组件中,使用`<svg-icon>`组件来引用SVG图标。可以通过设置`icon-class`属性来指定SVG图标的名称,通过设置`className`属性来修改SVG图标的样式,比如大小和颜色。例如,`<svg-icon icon-class="tree" className="需要修改的样式" />`。 3. 配置`vue.config.js`文件,添以下代码片段到`chainWebpack`方法中: ```javascript chainWebpack(config) { config.module .rule('svg') .exclude.add(resolve('src/icons')) // 注意svg的存储地址 .end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/icons')) // 注意svg的存储地址 .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() } ``` 这段代码的作用是配置Webpack,使其能够正确地载和处理SVG图标。其中,`exclude`和`include`方法用于指定SVG图标文件的存放地址,`svg-sprite-loader`用于载和处理SVG图标文件,并通过`symbolId`选项设置图标的ID。 通过以上配置,你就可以在Vue项目中使用SVG矢量图了。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [vue中如何使用svg](https://blog.csdn.net/weixin_47909202/article/details/123015710)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值