概念
什么是svg
即Scalable Vector Graphics 可伸缩矢量图形
SVG的W3C的解释: http://www.w3school.com.cn/svg/svg_intro.asp
什么是矢量图像,什么是位图图像?
1、矢量图像:SVG是W3C 推出的一种开放标准的文本式矢量图形描述语言,他是基于XML的、专门为网络而设计的图像格式,
SVG是一种采用XML来描述二维图形的语言,所以它可以直接打开xml文件来修改和编辑。
2、位图图像:位图图像的存储单位是图像上每一点的像素值,因而文件会比较大,像GIF、JPEG、PNG等都是位图图像格式。
Vector
在Android中指的是Vector Drawable,也就是Android中的矢量图,
可以说Vector就是Android中的SVG实现(并不是支持全部的SVG语法,现已支持的完全足够用了)
补充:Vector图像刚发布的时候,是只支持Android 5.0+的,自从AppCompat 23.2之后,Vector可以使用于Android 2.1以上的所有系统,
只需要引用com.android.support:appcompat-v7:23.2.0以上的版本就可以了。(所谓的兼容也是个坑爹的兼容,即低版本非真实使用SVG,而是生成PNG图片)
1) Vector 语法简介
通过使用它的Path标签,几乎可以实现SVG中的其它所有标签,虽然可能会复杂一点,
但这些东西都是可以通过工具来完成的,所以,不用担心写起来会很复杂。
(1)Path指令解析如下所示:
M = moveto(M X,Y) :将画笔移动到指定的坐标位置,相当于 android Path 里的moveTo()
L = lineto(L X,Y) :画直线到指定的坐标位置,相当于 android Path 里的lineTo()
H = horizontal lineto(H X):画水平线到指定的X坐标位置
V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
S = smooth curveto(S X2,Y2,ENDX,ENDY) 同样三次贝塞尔曲线,更平滑
Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射 同样二次贝塞尔曲线,更平滑
A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线 ,相当于arcTo()
Z = closepath():关闭路径(会自动绘制链接起点和终点)
注意,’M’处理时,只是移动了画笔, 没有画任何东西。
注意:1.关于这些语法,开发者不需要全部精通,而是能够看懂即可,这些path标签及数据生成都可以交给工具来实现。
(一般美工来帮你搞定!PS、Illustrator等等都支持导出SVG图片)
2.程序员:没必要去学习使用这些设计工具,开发者可以利用一些工具,自己转换一些比较基础的图像,
如:http://inloop.github.io/svg2android/
3.还可以使用SVG的编辑器来进行SVG图像的编写,例如:http://editor.method.ac/
(绝配!可以先用http://editor.method.ac/ 生成SVG图片,然后用http://inloop.github.io/svg2android/ 生成 VectorDrawable xml代码)
4.使用AndroidStudio插件完成SVG添加(Vector Asset Studio)
详细:http://www.jianshu.com/p/d6c39f2dd5e7
AS会自动生成兼容性图片(高版本会生成xxx.xml的SVG图片;低版本会自动生成xxx.png图片)
5.有些网站可以找到SVG资源
SVG下载地址: http://www.shejidaren.com/8000-flat-icons.html
http://www.flaticon.com/
http://www.iconfont.cn/plus --- 阿里巴巴
图片转成SVG https://vectormagic.com/
总结如下:
SVG的demo实现
类似下面的效果:
实现思路:
第一步 下载含有中国地图的 SVG
第二步 用http://inloop.github.io/svg2android/ 网站 将svg资源转换成相应的 Android代码
第三步 利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path
第四步 重写OnDraw方法 利用Path绘制中国地图
第五步 重写OnTouchEvent方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上。代码如下:
package com.xifei.svgmapdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import androidx.core.graphics.PathParser;
public class MapView extends View {
//上下文
private Context context;
//画笔
private Paint paint;
//适配比例
private float scale = 1.0f;
//矩形对象
private RectF totalRect;
//定义一个装载所有省份的集合
List<ProviceItem> itemList;
//绘制地图的颜色
private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};
//是否XML已经解析完毕
private boolean isEnd = false;
//当前选中的省份
private ProviceItem select;
public MapView(Context context) {
super(context);
}
public MapView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
this.context = context;
paint = new Paint();
paint.setAntiAlias(true);
loadThread.start();
}
public MapView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private Thread loadThread = new Thread(new Runnable() {
@Override
public void run() {
//定义一个输入流对象去加载xml文件
InputStream inputStream = context.getResources().openRawResource(R.raw.china);
//定义一个装载所有省份的集合
List<ProviceItem> list = new ArrayList<>();
try {
//获取到解析器的工厂类
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
//获取到SVG图形的XML对象
Document parse = builder.parse(inputStream);
//获取都节点目录
Element documentElement = parse.getDocumentElement();
//通过Element获取到所有path节点的集合
NodeList items = documentElement.getElementsByTagName("path");
//定义四个点
float left= -1;
float bottom= -1;
float top= -1;
float right = -1;
//遍历所有的path节点
for(int x=0;x<items.getLength();x++){
//获取到每一个path节点
Element element = (Element) items.item(x);
String pathData = element.getAttribute("android:pathData");
Path path = PathParser.createPathFromPathData(pathData);
ProviceItem proviceItem = new ProviceItem(path);
list.add(proviceItem);
//初始化每个省份的矩形
RectF rect = new RectF();
//获取到每个省份的边界
path.computeBounds(rect,true);
//获取到left最小的值
left = left == -1?rect.left: Math.min(left,rect.left);
//获取right最大值
right = right==-1?rect.right: Math.max(right,rect.right);
//遍历取出每个path中的top取所有的最小值
top = top == -1 ? rect.top : Math.min(top, rect.top);
//遍历取出每个path中的bottom取所有的最大值
bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
}
//封装地图的矩形
totalRect = new RectF(left,top,right,bottom);
itemList = list;
handler.sendEmptyMessage(1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
/**
* 设置省份的颜色
*/
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
if(itemList == null || itemList.size()<=0){
return;
}
int size = itemList.size();
for(int x=0;x<size;x++){
int color = Color.WHITE;
int flag = x % 4;
switch (flag){
case 1:
color = colorArray[0];
break;
case 2:
color = colorArray[1];
break;
case 3:
color = colorArray[2];
break;
default:
color = Color.CYAN;
break;
}
itemList.get(x).setDrawColor(color);
}
isEnd = true;
measure(getMeasuredWidth(),getMeasuredHeight());
//调用绘制
postInvalidate();
}
};
@Override
protected void onDraw(Canvas canvas) {
if(!isEnd){
return;
}
if(itemList !=null && itemList.size()>0){
canvas.save();
canvas.scale(scale,scale);
for (ProviceItem proviceItem : itemList) {
if(select == proviceItem){
proviceItem.drawItem(canvas,paint,true);
}else{
proviceItem.drawItem(canvas,paint,false);
}
}
}
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取到当前控件宽高值
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if(!isEnd){
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY));
return;
}
if(totalRect !=null){
//获取到地图的矩形的宽度
double mapWidth = totalRect.width();
//获取到比例值
scale = (float) (width/mapWidth);
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY));
}
@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(itemList ==null || itemList.size() ==0){
return;
}
//定义一个空的被选中的省份
ProviceItem selectItem =null;
for (ProviceItem proviceItem : itemList) {
//入股点击的是这个省份的范围之内 就把当前省份的封装对象绘制的方法 传一个true
if(proviceItem.isTouch(x/scale,y/scale)){
selectItem = proviceItem;
}
}
if(selectItem !=null){
select = selectItem;
postInvalidate();
}
}
}
JavaBean代码如下:
package com.xifei.svgmapdemo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
/**
* 省份的封装类
*/
public class ProviceItem {
//path对象
private Path path;
//绘制的颜色
private int drawColor;
public ProviceItem(Path path) {
this.path = path;
}
public void setDrawColor(int drawColor){
this.drawColor = drawColor;
}
void drawItem(Canvas canvas, Paint paint, boolean isSelect){
if(isSelect){
//选中的时候
paint.clearShadowLayer();
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
paint.setShadowLayer(0,0,0,0xffffff);
canvas.drawPath(path,paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawPath(path,paint);
}else{
//未选中的时候
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path,paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
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));
//返回是否这个区域包含传进来的坐标
return region.contains((int)x,(int)y);
}
}
demo下载地址 : https://download.youkuaiyun.com/download/xifei66/13107152