1静汤、SVG概念:
SVG是一種圖像文件格式,類似PNG居凶,JPG虫给。只不過PNG這種圖片需要圖像引擎加載,SVG則是由畫布來加載侠碧,它的英文全稱為Scalable Vector Graphics抹估,意思為可縮放的矢量圖形,可讓你設計無損失弄兜、高分辨率的Web圖形頁面药蜻,用戶可以直接使用代碼來描繪圖像;
2替饿、SVG圖像在Android中的使用
app圖標:sdk23以后语泽,app的圖標都是由svg圖像來表示
自定義控件:如不規(guī)則控件、復雜的交互控件视卢、子控件重疊判斷踱卵、圖標等,都可以使用SVG圖像實現
復雜動畫:如根據用戶滑動手勢動態(tài)顯示動畫据过,路徑動畫等
3惋砂、實現中國地圖的繪制,并且能正常點擊省份
效果展示:
中國地圖svg圖像下載地址:http://www.amcharts.com/download/
點擊第二個download按鈕即可下載所有國家svg圖像绳锅;
此下載比較費勁西饵,我分享一個網盤地址:
鏈接 https://pan.baidu.com/s/1zbtejuTYhSL2ino8soOgRw? 提取碼:mung
下載成功后將文件導入工程res/raw/china.svg
自定義view代碼:
package com.xxx.uidemo;
import android.annotation.SuppressLint;
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.support.v4.graphics.PathParser;
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;
public class ChinaMapView extends View {
? ? private int[] colorArrays = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFF00};
? ? private List<ProvinceItem> provinceItems = new ArrayList<>();// 所有省份
? ? private Paint paint;
? ? private ProvinceItem selectItem;// 點擊選中的省份
? ? private RectF totalRectF;// 地圖矩形
? ? private float scale = 1.0f;// 畫布縮放系數
? ? public ChinaMapView(Context context) {
? ? ? ? this(context, null);
? ? }
? ? public ChinaMapView(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public ChinaMapView(Context context, AttributeSet attrs, int defStyleAttr) {
? ? ? ? super(context, attrs, defStyleAttr);
? ? ? ? init();
? ? ? ? parseSVG(context);
? ? }
? ? private void parseSVG(final Context context) {
? ? ? ? final InputStream inputStream = context.getResources().openRawResource(R.raw.china);
? ? ? ? new Thread(new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
? ? ? ? ? ? ? ? ? ? DocumentBuilder documentBuilder = factory.newDocumentBuilder();
? ? ? ? ? ? ? ? ? ? Document parse = documentBuilder.parse(inputStream);
? ? ? ? ? ? ? ? ? ? Element documentElement = parse.getDocumentElement();
? ? ? ? ? ? ? ? ? ? NodeList g = documentElement.getElementsByTagName("path");
? ? ? ? ? ? ? ? ? ? float left = -1;
? ? ? ? ? ? ? ? ? ? float right = -1;
? ? ? ? ? ? ? ? ? ? float top = -1;
? ? ? ? ? ? ? ? ? ? float bottom = -1;
? ? ? ? ? ? ? ? ? ? ArrayList<ProvinceItem> list = new ArrayList<>();
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < g.getLength(); i++) {
? ? ? ? ? ? ? ? ? ? ? ? Element element = (Element) g.item(i);
? ? ? ? ? ? ? ? ? ? ? ? String pathData = element.getAttribute("d");
? ? ? ? ? ? ? ? ? ? ? ? @SuppressLint("RestrictedApi") Path path = PathParser.createPathFromPathData(pathData);
? ? ? ? ? ? ? ? ? ? ? ? ProvinceItem provinceItem = new ProvinceItem(path);
? ? ? ? ? ? ? ? ? ? ? ? provinceItem.setColor(colorArrays[i % 4]);
? ? ? ? ? ? ? ? ? ? ? ? // 這里循環(huán)遍歷每個省份path的邊界,并求得最左邊榨呆、右邊、頂部庸队、底部path的邊界
? ? ? ? ? ? ? ? ? ? ? ? RectF rectF = new RectF();
? ? ? ? ? ? ? ? ? ? ? ? path.computeBounds(rectF, true);
? ? ? ? ? ? ? ? ? ? ? ? left = left == -1 ? rectF.left : Math.min(left, rectF.left);
? ? ? ? ? ? ? ? ? ? ? ? right = right == -1 ? rectF.right : Math.max(right, rectF.right);
? ? ? ? ? ? ? ? ? ? ? ? top = top == -1 ? rectF.top : Math.min(top, rectF.top);
? ? ? ? ? ? ? ? ? ? ? ? bottom = bottom == -1 ? rectF.bottom : Math.max(bottom, rectF.bottom);
? ? ? ? ? ? ? ? ? ? ? ? list.add(provinceItem);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? provinceItems = list;// 避免priviceItems集合并發(fā)操作积蜻,先使用臨時集合,然后再重新賦值
? ? ? ? ? ? ? ? ? ? totalRectF = new RectF(left, top, right, bottom);//保存實際地圖大小
? ? ? ? ? ? ? ? ? ? // 通知刷新界面
? ? ? ? ? ? ? ? ? ? Handler handler = new Handler(Looper.getMainLooper());
? ? ? ? ? ? ? ? ? ? handler.post(new Runnable() {
? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? requestLayout();
? ? ? ? ? ? ? ? ? ? ? ? ? ? invalidate();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? ? ? //postInvalidate();
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }).start();
? ? }
? ? private void init() {
? ? ? ? paint = new Paint();
? ? ? ? paint.setColor(Color.BLACK);
? ? ? ? paint.setAntiAlias(true);
? ? }
? ? @Override
? ? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? ? ? super.onMeasure(widthMeasureSpec, heightMeasureSpec);
? ? ? ? // 獲取畫布原始大小
? ? ? ? int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
? ? ? ? int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
? ? ? ? // 計算縮放系數
? ? ? ? if (totalRectF != null) {
? ? ? ? ? ? float width = totalRectF.width();
? ? ? ? ? ? scale = measuredWidth / width;
? ? ? ? }
? ? }
? ? @Override
? ? protected void onDraw(Canvas canvas) {
? ? ? ? super.onDraw(canvas);
? ? ? ? if (provinceItems != null && provinceItems.size() > 0) {
? ? ? ? ? ? canvas.save();
? ? ? ? ? ? //按照縮放系數將畫布進行縮放
? ? ? ? ? ? canvas.scale(scale, scale);
? ? ? ? ? ? for (ProvinceItem provinceItem : provinceItems) {
? ? ? ? ? ? ? ? if (provinceItem == selectItem) {
? ? ? ? ? ? ? ? ? ? provinceItem.DrawItem(canvas, paint, true);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? provinceItem.DrawItem(canvas, paint, false);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? @Override
? ? public boolean onTouchEvent(MotionEvent event) {
? ? ? ? handleTouch(event.getX() / scale, event.getY() / scale);// 點擊觸摸時也需要將事件位置進行縮放彻消,不然點擊事件會受影響
? ? ? ? return super.onTouchEvent(event);
? ? }
? ? private void handleTouch(float x, float y) {
? ? ? ? if (provinceItems == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? ProvinceItem select = null;
? ? ? ? for (ProvinceItem provinceItem : provinceItems) {
? ? ? ? ? ? if (provinceItem.isTouch(x, y)) {
? ? ? ? ? ? ? ? select = provinceItem;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if (select != null) {
? ? ? ? ? ? selectItem = select;
? ? ? ? ? ? postInvalidate();
? ? ? ? }
? ? }
}
package com.xxx.uidemo;
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 ProvinceItem {
? ? private Path path;
? ? private int color;
? ? public ProvinceItem(Path path) {
? ? ? ? this.path = path;
? ? }
? ? public void setColor(int color) {
? ? ? ? this.color = color;
? ? }
? ? /**
? ? * 繪制自己
? ? *
? ? * @param canvas
? ? * @param paint
? ? * @param isSelect
? ? */
? ? public void DrawItem(Canvas canvas, Paint paint, boolean isSelect) {
? ? ? ? if (isSelect) {
? ? ? ? ? ? // 繪制內部的顏色
? ? ? ? ? ? paint.clearShadowLayer();
? ? ? ? ? ? paint.setStrokeWidth(1);
? ? ? ? ? ? paint.setStyle(Paint.Style.FILL);
? ? ? ? ? ? paint.setColor(color);
? ? ? ? ? ? canvas.drawPath(path, paint);
? ? ? ? ? ? // 繪制邊界
? ? ? ? ? ? paint.setColor(0xFFD0E8F4);
? ? ? ? ? ? paint.setStyle(Paint.Style.STROKE);
? ? ? ? ? ? canvas.drawPath(path, paint);
? ? ? ? } else {
? ? ? ? ? ? // 繪制內部的顏色
? ? ? ? ? ? paint.clearShadowLayer();
? ? ? ? ? ? paint.setStrokeWidth(2);
? ? ? ? ? ? paint.setStyle(Paint.Style.FILL);
? ? ? ? ? ? paint.setColor(Color.BLACK);
? ? ? ? ? ? paint.setShadowLayer(8, 0, 0, 0xFFFFFF);
? ? ? ? ? ? canvas.drawPath(path, paint);
? ? ? ? ? ? // 繪制邊界
? ? ? ? ? ? paint.setColor(color);
? ? ? ? ? ? paint.setStyle(Paint.Style.FILL);
? ? ? ? ? ? paint.setStrokeWidth(2);
? ? ? ? ? ? canvas.drawPath(path, paint);
? ? ? ? }
? ? }
? ? /**
? ? * 按下坐標坐標是否在path軌跡和path對應的矩形軌跡的交集中
? ? *
? ? * @param x
? ? * @param y
? ? * @return
? ? */
? ? public boolean isTouch(float x, float y) {
? ? ? ? RectF rectF = new RectF();
? ? ? ? path.computeBounds(rectF, true);//計算path軌跡對應的矩形界限
? ? ? ? Region region = new Region();
? ? ? ? // 計算path對應的軌跡和path軌跡對應的矩形界限的范圍交集
? ? ? ? region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
? ? ? ? return region.contains((int) x, (int) y);// 坐標值是否包含在此范圍中
? ? }
}
使用布局加載地圖
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:app="http://schemas.android.com/apk/res-auto"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? <com.xxx.uidemo.ChinaMapView
? ? ? ? android:layout_width="match_parent"
? ? ? ? android:layout_height="match_parent" />
</RelativeLayout>
里面有詳細備注竿拆,基本思路也就是先解析svg文件,然后依據解析結果畫出地圖宾尚;
關鍵步驟:
1丙笋、解析svg谢澈;
2、繪制自己
3御板、判斷點擊坐標是否屬于此path軌跡范圍內锥忿;
4、解析過程中獲取地圖實際大械±摺敬鬓;
5、計算畫布和地圖 縮放比并按比例縮放畫布笙各;