開發中遇到畫不規則的圖像的時候,我們往往通過自定義View,然後自己在onDraw去繪製可能會很麻煩。這時候我們就可以使用SVG圖,可以讓UI將需要實現的效果設計好導出爲SVG圖。
SVG圖的特性
- svg可被非常多的工具讀取和修改
- svg與JPEG和GIF比起來,尺寸更小,可壓縮性更強
- svg是可伸縮的
- svg圖可以在任何分辨率下被高質量的打印
- svg可在圖像質量不下降的情況下被放大
- svg圖像中的文本是可自定義的,可以融入代碼片段
- svg是純粹的xml
svg語法
M = moveto(M X, Y):將畫筆移動到指定的座標位置
L = lineto(L X, Y):畫直線到指定的座標位置
H = horizontal lineto(H X):畫水平線到指定的X座標位置
V = verical 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):二次貝塞爾曲線
Z = closepath():關閉路徑
例子:利用SVG圖繪製中國地圖
public class ChinaMap extends View {
private Paint mPaint;
private Context mContext;
private int[] colorArray = new int[]{
0xFF239BD7,
0xFF30A9E5,
0xFF80CBF1,
0xff9B30FF,
0xffe52302,
0xff20fed0,
0xffF08080,
0xffEE6AA7,
0xff9AFF9A
};
private List<ProvinceItem> itemList;
private ProvinceItem select;
private RectF totalRect;
private float scale = 1.0f;
public ChinaMap(Context context) {
this(context, null);
}
public ChinaMap(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ChinaMap(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
itemList = new ArrayList<>();
new Thread(runnable).start();
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
InputStream inputStream = mContext.getResources().openRawResource(R.raw.china);
// dom解析
//取得DocumentBuilderFactory實例
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
// 從DocumentBuilderFactory對象中中獲取DocumentBuilder實例
builder = builderFactory.newDocumentBuilder();
// 解析輸入流獲取Document
Document document = builder.parse(inputStream);
// 解析出根節點
Element rootElement = document.getDocumentElement();
// 解析出所有的path節點
NodeList nodeList = 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 < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
String pathData = element.getAttribute("android:pathData");
Path path = PathParser.createPathFromPathData(pathData);
ProvinceItem provinceItem = new ProvinceItem(path);
provinceItem.setDrawColor(colorArray[i % colorArray.length]);
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(provinceItem);
}
itemList = list;
// 因爲left,top最終獲取的是最小的,right,bottom最終獲取的是最大的。
totalRect = new RectF(left, top, right, bottom);
// 刷新界面
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> {
requestLayout();
postInvalidate();
});
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
}
};
@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 || itemList.size() == 0) return;
canvas.save();
// 對畫布進行縮放
canvas.scale(scale, scale);
for (ProvinceItem provinceItem : itemList) {
if (provinceItem != select) {
provinceItem.drawItem(canvas, mPaint, false);
} else {
select.drawItem(canvas, mPaint, true);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (itemList == null) return false;
ProvinceItem selectItem = null;
for (ProvinceItem provinceItem : itemList) {
if (provinceItem.isTouch(event.getX() / scale, event.getY() / scale)) {
selectItem = provinceItem;
}
}
if (selectItem != null) {
select = selectItem;
postInvalidate();
}
return super.onTouchEvent(event);
}
}
public class ProvinceItem {
private Path path;
/**
* 繪製顏色
*/
private int drawColor;
public ProvinceItem(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.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path, paint);
// 繪製邊界
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
int strokeColor = 0xFFD0E8F4;
paint.setColor(strokeColor);
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);
paint.setStrokeWidth(2);
canvas.drawPath(path, paint);
}
}
public boolean isTouch(float x, float y) {
RectF rectF = new RectF();
path.computeBounds(rectF, true);
Region region = new 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);
}
}