SVG 矢量图的加载
一、什么是 SVG
SVG 是可缩放矢量图形,用户可以用代码来直接描绘图像。区别于 JPG 和 PNG 的需要用引擎来加载的图片,它直接用画布绘制,所以是无损失的。
二、SVG 的优点
- SVG文件时纯粹的XML 可以用很多工具打开编辑
2. SVG比其他的图片都要更小
3. SVG是一种可伸缩的图片
4. SVG 图像可以在任何分辨率下轻松打印
5. SVG 可以自定义
三、它能做什么
1.APP的图表
2.自定义的复杂动画控件
3.复杂的动画
四、如何获得 SVG
使用 PhotoShop 就可以将图片转换成 SVG 的图片。
五、SVG 常用标识
PathData:图片数据边界
六、如何使用 SVG
一个 SVG 的图片效果如下

SVG 图片实际上是xml的格式,所以加载一个 SVG 可以使用 DocumentBuilderFactory 对象,关注它的Path节点,这个节点点的pathData元素代表着要绘制的路径。使用 Android 原生的 DocumentBuilderFactory 类可以解析 xml。
final InputStream inputStream = context.getResources().openRawResource(R.raw.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();
// 获取所有 path 节点的内容
NodeList items = rootElement.getElementsByTagName("path")
for (int i = 0; i < items.getLength(); i++) {
Element element = (Element) items.item(i);
// 获取其中 pathData 元素的内容
String pathData = element.getAttribute("android:pathData");
........
}
Android中提供了 PathParser 对象来解析 pathData 数据,将这个数据转换成 android 的 Path 对象
Path path = PathParser.createPathFromPathData(pathData);
拿到Path对象以后就可直接通过canvas的drawPath方法来直接绘制
canvas.drawPath(path,paint);
七、如何给svg矢量图添加点击事件
1.首先需要判断点击事件是否作用在 Path 的区域
2.如果在这个区域内容我们在onTouchEvent里面可以触发事件。
android 提供了 Region 对象来描述一个 Path 的范围,并且提供了 contains 函数判断某一个点是否在这个区域内
public boolean isContains(float x, float y, Path path){
RectF rect = new RectF();
path.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path,new Region(rect.left,rect.top,rect.right,rect.bottom));
return region.contains((int)x,(int)y);
}
public boolean onTouchEvent(MotionEvent event){
if (isContains(event.getX(),event.getY())){
mListeners.onClickListener();
}
}
八、适配 SVG 的自定义 View
SVG 图片可能不能适应画布的大小,所以需要针对画布进行适配,适配的方案是计算 SVG 和 自定义 View 的计算缩放比,在加载 SVG 图片并解析 Path 的时候就计算出这个 SVG 图片的宽度,因为 SVG 可能分成多个 Path。可以在加载的时候就求出每个Path对应的范围,找到 left 和 top 的最小值,right 和 bottom 最小值
int left = -1;
int right = -1;
int top = -1;
int bottom = -1;
for (int i = 0; i < items.getLength(); i++) {
......
Path path = PathParser.createPathFromPathData(pathData);
ProviceItem proviceItem = new ProviceItem(path);
proviceItem.setDrawColor(colorArray[i % 4]);
RectF rect = new RectF();
path.computeBounds(rect, true);
// 求左上右下的极值 然后和View 做比较进行适配
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);
}
totalRect = new RectF(left, top, right, bottom);
在 onMeasure 的时候计算出缩放比例
@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));
}
在绘制的时候直接作用在整个画布上
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (itemList != null) {
canvas.save();
// 对整个画布缩放
canvas.scale(scale, scale);
......
}
}
同时onTouch事件的位置也要进行缩放处理
public boolean onTouchEvent(MotionEvent event){
if (isContains(event.getX()/scale,event.getY()/scale)){
mListeners.onClickListener();
}
}