概念
什麼是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.csdn.net/download/xifei66/13107152