Android UI SVG使用
1.
SVG是什麼?
- 可縮放矢量圖形(Scalable Vector Graphics)
- SVG于2003 年1月14日成為 W3C 推薦标準
2.
SVG特性
- SVG可被很多工具讀取和使用
- SVG跟PNG,JPG,GIF圖像比起來,尺寸更小,可壓縮性更強
- SVG是可伸縮的
- SVG适配性好,任意放大縮小不失真
3.
SVG使用場景
- Android SDK23 APP的圖示都是由SVG來制作的
- 不規則控件,複雜的互動,子控件重疊判斷,圖示等都可以用SVG來做
- 複雜路徑動畫等
4.
SVG使用
自己制作SVG:https://editor.method.ac/ (線上SVG編輯器)
SVG檔案下載下傳:https://www.amcharts.com/download/ (比如中國地圖等)
5.
SVG常用方法
接下來利用SVG畫一個中國地圖,并且實作各個省份有可點選事件
6.
涉及到知識點
- 自定義View (MapView.java)
public class MapView extends View {
private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};
private Context context;
private List<ProvinceItem> itemList;
private Paint paint;
private ProvinceItem select;
private RectF totalRect;
private float scale = 1.0f;
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);
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) {
double mapWidth = totalRect.width();
// SVG縮放适配
scale = (float) (width / mapWidth);
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
private Thread loadThread = new Thread() {
@Override
public void run() {
final InputStream inputStream = context.getResources().openRawResource(R.raw.china);
// 取得DocumentBuilderFactory執行個體
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//從factory擷取DocumentBuilder執行個體
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);
// 解析輸入流 得到Document執行個體
Element rootElement = doc.getDocumentElement();
NodeList items = rootElement.getElementsByTagName("path");
float left = -1;
float right = -1;
float top = -1;
float bottom = -1;
List<ProvinceItem> list = new ArrayList<>();
// 解析所有省份SVG資料,然後将省份所對應字元串轉換為Path繪制資料
for (int i = 0; i < items.getLength(); i++) {
// Dom解析xml
Element element = (Element) items.item(i);
String pathData = element.getAttribute("android:pathData");
@SuppressLint("RestrictedApi")
Path path = PathParser.createPathFromPathData(pathData);
ProvinceItem proviceItem = new ProvinceItem(path);
proviceItem.setDrawColor(colorArray[i % 4]);
// 通過計算地圖所占最大矩形區域(左,上,右,下位置,來計算縮放scale)
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(proviceItem);
}
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 (Exception e) {
e.printStackTrace();
}
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
// 适配點選事件區域X,Y
handleTouch(event.getX() / scale, event.getY() / scale);
return super.onTouchEvent(event);
}
private void handleTouch(float x, float y) {
if (itemList == null) {
return;
}
ProvinceItem selectItem = null;
for (ProvinceItem proviceItem : itemList) {
if (proviceItem.isTouch(x, y)) {
selectItem = proviceItem;
}
}
if (selectItem != null) {
select = selectItem;
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (itemList != null) {
canvas.save();
canvas.scale(scale, scale);
for (ProvinceItem proviceItem : itemList) {
if (proviceItem != select) {
proviceItem.drawItem(canvas, paint, false);
} else {
select.drawItem(canvas, paint, true);
}
}
}
}
}
- 封裝每個省份資訊對象
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.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path, paint);
// 繪制省份邊界
paint.setStyle(Paint.Style.STROKE);
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);
}
}
// 判斷手指點選區域所在省份
// Android Region 區域點選
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);
}
}
- 最後需要一個 中國地圖SVG,可從上面提供網站下載下傳
7.
參考
- SVG—最簡單的SVG動畫
- SVG矢量動畫機制
- geftimov/android-pathview
- android中svg介紹及使用[彙總]
- Android實作炫酷SVG動畫效果
- SVGA 是一種跨平台的開源動畫格式