SVG矢量圖打造不規則自定義控件,可點擊的中國地圖

1、SVG概念:

SVG是一種圖像文件格式,類似PNG,JPG。只不過PNG這種圖片需要圖像引擎加載,SVG則是由畫布來加載,它的英文全稱為Scalable Vector Graphics,意思為可縮放的矢量圖形,可讓你設計無損失、高分辨率的Web圖形頁面,用戶可以直接使用代碼來描繪圖像;


2、SVG圖像在Android中的使用

app圖標:sdk23以后,app的圖標都是由svg圖像來表示

自定義控件:如不規則控件、復雜的交互控件、子控件重疊判斷、圖標等,都可以使用SVG圖像實現

復雜動畫:如根據用戶滑動手勢動態顯示動畫,路徑動畫等

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]);

? ? ? ? ? ? ? ? ? ? ? ? // 這里循環遍歷每個省份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集合并發操作,先使用臨時集合,然后再重新賦值

? ? ? ? ? ? ? ? ? ? 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、計算畫布和地圖 縮放比并按比例縮放畫布;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容