本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(9)

什么?!!你从来没用SVG打造过不规则的自定义View?

发布于2021-03-07 21:59     阅读(1586)     评论(0)     点赞(23)     收藏(3)


SVG概念

SVG是一种图像文件格式,类似于PNG JPG。只不过PNG需要图像引擎加载,SVG则由画布来加载 它的英文全称为Scalable
Vector Graphics,意思为可缩放的矢量图形。可以设计无损失、高分辨率的Web图形页面。用户可以直接用代码来描绘图像。

SVG特性

  • SVG可被非常 多的工具读取和修改(比如记事本)
  • SVG与JPEG和GIF图像 比起来尺寸更小,且可缩放性更强
  • SVG是可伸缩的
  • SVG图像可在 任何分辨率下被高质量地打印
  • SVG可在图像质量不下降地情况下被放大
  • SVG图像中的文本 是可以自定义的可以融入代码片段
  • SVG文件是纯粹的XML

SVG在安卓中能做什么

  1. APP图标:在SDK23后,APP的图标是由SVG来表示
  2. 自定义控件:不规则的控件,复杂的交互,子控件重叠判断,图表等都
    可以用SVG来做
  3. 复杂动画:如根据用户滑动动态显示动画,路径动画

是不是很抽象?没耐心看下去,那就点击看看这篇博客,就知道它干嘛用了。

Android中使用SVG矢量图打造多边形图形框架

附张图吧
在这里插入图片描述

标准SVG预览

<svg width= 580 height=400"
xmIns= http://www.w3.org/2000/svg>
<title>L ayerl</title>
<line stroke-linecap= undefind stroke linejoin= undefind
ld=svg_1y2=119.4375x2=''412.53211"yl="119.4375"x1='77.5"
Stroke-width= 1.5 stroke- #000" fill= none" />
</svg>

SVG语法介绍

  • M=moveto(M X,Y):将画笔移动到指定位置
  • L=lineto(L X,Y): 画直线到指定位置
  • H=horizontal lineto(H X): 画水平线到指定的X坐标位置
  • V=vertical lineto(VY):画垂直线到指定的Y坐标位置
  • Q=quadratic Belzier curve(Q X,ENDX,ENDY):二次贝塞尔曲线
  • C=curveto(Q X1,Y1,X2,Y2,ENDX,ENDY):三次贝塞尔曲线
  • S=smooth curveto(S X1,Y1,ENDX,ENDY):平滑过渡
  • Z=closepath(): 闭合路径

SVG图下载地址

https://www.amcharts.com/svg-maps/

在这里插入图片描述
这个网站里面的资源好像挺丰富的,以后有需要可以上来参考看看。

巴铁!奔跑的小恐龙?(示例开始)

看网上有很多我们大中国的地图示例了,我就下个巴铁的地图,试试能不能给咱巴铁弄一份。

先按步骤,下载巴基斯坦的svg地图矢量图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将下载完成的svg图片放入项目的raw文件夹下

在这里插入图片描述
点开svg图片,我们就能看到预览效果
在这里插入图片描述
再来看看svg文件中的代码

<?xml version="1.0" encoding="utf-8"?>
<!-- (c) ammap.com | SVG map of Pakistan - High -->
<svg xmlns="http://www.w3.org/2000/svg"
	xmlns:amcharts="http://amcharts.com/ammap"
	xmlns:xlink="http://www.w3.org/1999/xlink"
	viewBox="0 0 800 700"
	version="1.1">
	<defs>
		<style type="text/css">
			.land
			{
				fill: #CCCCCC;
				fill-opacity: 1;
				stroke:white;
				stroke-opacity: 1;
				stroke-width:0.5;
			}
		</style>

		<amcharts:ammap projection="mercator" leftLongitude="60.896610" topLatitude="37.095717" rightLongitude="77.843840" bottomLatitude="23.705520"></amcharts:ammap>

		<!-- All areas are listed in the line below. You can use this list in your script. -->
		<!--{id:"PK-BA"},{id:"PK-GB"},{id:"PK-IS"},{id:"PK-JK"},{id:"PK-KP"},{id:"PK-PB"},{id:"PK-SD"},{id:"PK-TA"}-->

	</defs>
	<g>
		<!--各省的path-->
		<path />
		<path />
		<path />
		...
	</g>
</svg>

来个中国红?改属性:fill: #ff0000;
在这里插入图片描述
奔跑的小恐龙?

如何能转成Android项目能使用的VectorDrawable xml文件呢,步骤如下:

首先需要在svg文件中加入viewBox属性(参考上面的svg文件代码),然后按下图操作:在这里插入图片描述
选中svg文件,然后确定
在这里插入图片描述
便能在目标目录中看到生成好的文件
在这里插入图片描述
点击查看代码

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="800dp"
    android:height="700dp"
    android:viewportWidth="800"
    android:viewportHeight="700">
    <!--各省份的path的属性-->
    <path
      android:pathData="balabala"
      android:strokeWidth="0.5"
      android:fillColor="#ff0000"
      android:strokeColor="#ffffff"/>
    <path/>
	...
</vector>

这个时候你就可以指定各省的属性了。

自定义View展示SVG

这个示例是给巴铁的各省添加上了点击事件,点击的时候展示相应省份的名字。

看看效果

在这里插入图片描述
附上主要代码

定义ProvinceItem对应各个省份

public class ProvinceItem {
    private Path path;
    private String name;
    private int drawColor;//板块颜色
    private PointF clickPoint;//显示省份信息

    public ProvinceItem(Path path) {
        this.path = path;
    }

    public Path getPath() {
        return path;
    }

    public void setPath(Path path) {
        this.path = path;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getDrawColor() {
        return drawColor;
    }

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

    public PointF getClickPoint() {
        return clickPoint;
    }

    public void setClickPoint(PointF clickPoint) {
        this.clickPoint = clickPoint;
    }

    void drawItem(Canvas canvas, Paint paint, boolean isSelect) {
        if (isSelect) {
            //绘制内部颜色
            paint.clearShadowLayer();
            paint.setStrokeWidth(1);
            paint.setColor(drawColor);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawPath(path, paint);
            //绘制边界
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(0xff0e8ef4);
            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);
            canvas.drawPath(path, paint);
        }
    }

    /**
     * 判断点击区域是否在当前的省份
     *
     * @param x
     * @param y
     * @return
     */
    public boolean isTouch(float x, float y) {
        //获取path矩形区域
        RectF recrF = new RectF();
        path.computeBounds(recrF, true);
        Region region = new Region();
        //给定路径
        region.setPath(path, new Region((int) recrF.left, (int) recrF.top, (int) recrF.right, (int) recrF.bottom));
        return region.contains((int) x, (int) y);
    }
}

自定义View来展示地图:

public class MapView extends View {
    private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFF4087A3};
    private List<ProvinceItem> itemList;//所有省份集合
    private Paint paint;
    private ProvinceItem select;//当前选中的省份
    private RectF totalRect;//地图大小信息
    private float scale = 1.0f;

    private static final int NONE = 0;
    private static final int DRAG = 1;
    private static final int ZOOM = 2;
    private int mode = NONE;
    // 第一个按下的手指的点
    private PointF startPoint = new PointF();
    // 两个按下的手指的触摸点的中点
    private PointF midPoint = new PointF();
    // 初始的两个手指按下的触摸点的距离
    private float oriDis = 1f;
    private boolean actionClick = true;
    private float translateX;
    private float translateY;
    private boolean shouldShowText;//是否需要显示省份名

    public MapView(Context context) {
        super(context);
        init();
    }


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

    public MapView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();

    }

    private void init() {
        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) {
            float mapWidth = totalRect.width();
            scale = width / mapWidth;
        }
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        float currentScaleCount = 0;//当前缩放系数
        float currentTranslateX = 0;//当前x平移距离
        float currentTranslateY = 0;//当前y平移距离
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                //单点触控
                startPoint.set(event.getX(), event.getY());
                mode = DRAG;
                actionClick = true;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                //多点触控
                Log.e("多点触控", "多点触控");
                oriDis = distance(event);
                if (oriDis > 10) {
                    midPoint = midPoint(event);
                    mode = ZOOM;
                }
                actionClick = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //滑动
                Log.e("mode", mode + " ");
                if (mode == DRAG) {
                    //单指拖动
                    if (Math.abs(x - startPoint.x) > 10 || Math.abs(y - startPoint.y) > 10) {
                        currentTranslateX = translateX + x - startPoint.x;
                        currentTranslateY = translateY + y - startPoint.y;
                        translateX = currentTranslateX;
                        translateY = currentTranslateY;
                        startPoint.set(x, y);
                        actionClick = false;
                        invalidate();
                    }
                } else if (mode == ZOOM) {
                    //两指缩放
                    float newDist = distance(event);//当前两指距离
                    if (Math.abs(newDist - oriDis) > 10) {
                        float scaleInner = newDist / oriDis;
                        currentScaleCount = scale + (scaleInner - 1);
                        if (currentScaleCount < 1) {
                            scale = 1;
                        } else {
                            scale = currentScaleCount;
                        }
                        oriDis = newDist;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                //单点触控
                mode = NONE;
                if (actionClick) {
                    handleTouch(x / scale - translateX, y / scale - translateY);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //多点触控
                mode = NONE;
                break;
        }
        return true;
    }

    private void handleTouch(float x, float y) {
        shouldShowText = false;
        if (itemList == null) {
            return;
        }
        ProvinceItem selectItem = null;
        for (ProvinceItem provinceItem : itemList) {
            if (provinceItem.isTouch(x, y)) {
                selectItem = provinceItem;
                provinceItem.setClickPoint(new PointF(x, y));
                shouldShowText = true;
            }
        }
        if (selectItem != null) {
            select = selectItem;
            postInvalidate();
        }
    }

    private Thread loadThread = new Thread() {
        @Override
        public void run() {
            InputStream inputStream = getResources().openRawResource(R.raw.pakistan);
            //获取DocumentBuilderFactory
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //从DocumentBuilderFactory获取DocumentBuilder实例
            DocumentBuilder builder;
            try {
                builder = factory.newDocumentBuilder();
                //解析输入流 获取Document实例
                Document document = builder.parse(inputStream);
                Element rootElement = document.getDocumentElement();
                //先找到Path
                NodeList path = rootElement.getElementsByTagName("path");
                float left = -1;
                float right = -1;
                float top = -1;
                float bottom = -1;
                List<ProvinceItem> list = new ArrayList<>();
                for (int i = 0; i < path.getLength(); i++) {
                    Element element = (Element) path.item(i);
                    String pathData = element.getAttribute("d");
                    String name = element.getAttribute("title");
                    //将pathData转成Path
                    @SuppressLint("RestrictedApi") Path path1 = PathParser.createPathFromPathData(pathData);
                    ProvinceItem provinceItem = new ProvinceItem(path1);
                    provinceItem.setName(name);
                    provinceItem.setDrawColor(colorArray[i % 4]);
                    RectF rectF = new RectF();
                    path1.computeBounds(rectF, true);
                    left = left == -1 ? rectF.left : Math.min(left, rectF.left);
                    right = right == -1 ? rectF.right : Math.max(right, rectF.right);
                    top = top == -1 ? rectF.top : Math.min(top, rectF.top);
                    bottom = bottom == -1 ? rectF.bottom : Math.max(bottom, rectF.bottom);
                    list.add(provinceItem);
                }
                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 (ParserConfigurationException e) {
                e.printStackTrace();
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (itemList != null && itemList.size() > 0) {
            canvas.save();
            canvas.scale(scale, scale);
            canvas.translate(translateX, translateY);
            for (ProvinceItem provinceItem : itemList) {
                if (provinceItem != select) {
                    provinceItem.drawItem(canvas, paint, false);
                } else {
                    provinceItem.drawItem(canvas, paint, true);
                }
            }
            if (shouldShowText) {
                //绘制文本
                paint.setColor(Color.RED);
                paint.setStyle(Paint.Style.FILL);
                paint.setTextSize(40);
                canvas.drawText(select.getName(), select.getClickPoint().x, select.getClickPoint().y, paint);
                canvas.restore();

            }
        }
    }

    /**
     * 计算两个手指头之间的中心点的位置
     * x = (x1+x2)/2;
     * y = (y1+y2)/2;
     *
     * @param event 触摸事件
     * @return 返回中心点的坐标
     */
    private PointF midPoint(MotionEvent event) {
        float x = (event.getX(0) + event.getX(1)) / 2;
        float y = (event.getY(0) + event.getY(1)) / 2;
        return new PointF(x, y);
    }

    /**
     * 计算两个手指间的距离
     *
     * @param event 触摸事件
     * @return 放回两个手指之间的距离
     */
    private float distance(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);//两点间距离公式
    }
}

最后引用即可:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <pers.owen.svgmap.MapView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

本文完,Demo下载地址


About

UI系列文章一览




所属网站分类: 技术文章 > 博客

作者:前端霸主

链接:http://www.qianduanheidong.com/blog/article/33519/53950e604ffe8f218dfe/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

23 0
收藏该文
已收藏

评论内容:(最多支持255个字符)