目錄
目錄
效果展示
實(shí)現(xiàn)原理
該效果的實(shí)現(xiàn)原理是通過(guò)讀取svg圖像的路徑并且轉(zhuǎn)換為Path,然后繪制實(shí)現(xiàn)的。
實(shí)現(xiàn)步驟
1.獲取適用于Android的SVG
下載地址:https://www.amcharts.com/dl/javascript-maps/ ,這里包含世界各個(gè)國(guó)家的SVG圖像
上圖所指的是中國(guó)的
然后通過(guò)轉(zhuǎn)換工具轉(zhuǎn)換為Android適用的:
用該網(wǎng)站進(jìn)行在線轉(zhuǎn)換http://inloop.github.io/svg2android/
2.自定義View中加載這個(gè)SVG
其中ProvinceItem是用于存放Path的自定義實(shí)體類
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = getContext().getResources().openRawResource(R.raw.chinahigh);
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(inputStream);
Element root = document.getDocumentElement();//獲取根節(jié)點(diǎn)
NodeList nodeList = root.getElementsByTagName("path");
provinceItems.clear();
float left = -1,top = -1 ,right = -1 ,bottom = -1;//算出當(dāng)前繪制的地圖的邊界,用于縮放地圖,防止展示在界面上過(guò)大顯示不全或過(guò)小
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);//path節(jié)點(diǎn)
//解析pathData屬性
ProvinceItem provinceItem = new ProvinceItem();
//獲取各個(gè)省的Path
Path provincePath = PathParser.createPathFromPathData(element.getAttribute("android:pathData"));
provinceItem.setPath(provincePath);
provinceItems.add(provinceItem);
//獲取Path的邊界頂點(diǎn)值
RectF rectF = new RectF();
provincePath.computeBounds(rectF,true);
left = left == -1 ? rectF.left : Math.min(rectF.left,left);//取出最小的left
right = right == -1 ? rectF.right : Math.max(rectF.right,right);//取出最大的right
bottom = bottom == -1 ? rectF.bottom : Math.max(rectF.bottom,bottom);//取出最大的bottom
top = top == -1 ? rectF.top : Math.min(rectF.top,top);//取出最小的top
}
mRectFViewRegion = new RectF(left, top, right, bottom);
//請(qǐng)求重新布局,用于縮放和實(shí)現(xiàn)自適應(yīng)高度
handler.sendEmptyMessage(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
3.加載完后要重新調(diào)整大小以實(shí)現(xiàn)自適應(yīng)高度,并根據(jù)邊界值算出地圖的寬度與控件寬度的比值
該步驟主要是為了實(shí)現(xiàn)高度的自適應(yīng)和防止地圖路徑過(guò)大顯示不全或過(guò)小不夠填充控件。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(mRectFViewRegion != null){
mWidthScale = MeasureSpec.getSize(widthMeasureSpec) / mRectFViewRegion.width();
}
//獲取自適應(yīng)的高度
int defaultHeight = mRectFViewRegion == null ? 1 : (int) mRectFViewRegion.height();
setMeasuredDimension(getDefaultSize(MeasureSpec.getSize(widthMeasureSpec),widthMeasureSpec)
,getHeightSize(defaultHeight,heightMeasureSpec));
}
/**
* 獲取高度值
* @param size
* @param measureSpec
* @return
*/
private int getHeightSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
result = size;
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
4.繪制地圖
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.scale(mWidthScale,1);
drawMapPath(canvas);
}
/**
* 繪制地圖
* @param canvas
*/
private void drawMapPath(Canvas canvas) {
for (ProvinceItem provinceItem:provinceItems){
if(selectProvinceItem != null && selectProvinceItem.equals(provinceItem)){
//被選中的加上陰影
mPaint.setShadowLayer(3,2,2,Color.BLACK);
}else {
mPaint.clearShadowLayer();
}
canvas.drawPath(provinceItem.getPath(),mPaint);
}
}
5.設(shè)置點(diǎn)擊響應(yīng)
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_UP:
//當(dāng)手指抬起時(shí)表示點(diǎn)擊完成
onClickProvince(event.getX() / mWidthScale,event.getY());
break;
}
return true;
}
/**
* 點(diǎn)擊完成讓點(diǎn)擊的省高亮顯示
* @param x
* @param y
*/
private void onClickProvince(float x, float y) {
double ratio = -1;//點(diǎn)擊的點(diǎn)到矩形中心點(diǎn)的距離與矩形對(duì)角線長(zhǎng)度的比值(為了處理點(diǎn)擊不準(zhǔn)確的問(wèn)題),比值大的是點(diǎn)擊的
for (ProvinceItem provinceItem:provinceItems){
RectF rectF = new RectF();
provinceItem.getPath().computeBounds(rectF,true);
if(rectF.contains(x, y)){
float rectFCenterX = (rectF.right - rectF.left) / 2;
float rectFCenterY = (rectF.bottom - rectF.top) / 2;
//根據(jù)公式算的點(diǎn)擊的點(diǎn)到中心點(diǎn)的距離
double tempRatio = Math.abs(Math.sqrt(Math.pow((x - rectFCenterX), 2) + Math.pow((y - rectFCenterY), 2)))
/ Math.abs(Math.sqrt(Math.pow((rectF.width()+rectF.height()),2)));
if(tempRatio > ratio){
ratio = tempRatio;
selectProvinceItem = provinceItem;
}
Log.e("點(diǎn)擊","查找的" + ratio);
}
}
//重新繪制
postInvalidate();
}
整體步驟比較少,連貫的邏輯請(qǐng)查看本案例的源碼。
第三方實(shí)現(xiàn)
無(wú)意中發(fā)現(xiàn)了一個(gè)比較好的別人的實(shí)現(xiàn),是如下效果(可以放大和滑動(dòng),點(diǎn)擊響應(yīng)的準(zhǔn)確率也比較高):
項(xiàng)目地址:https://github.com/NoEndToLF/ChinaMapView
案例源碼
https://gitee.com/itfitness/SvgMap
思考總結(jié)
我們也可以根據(jù)SVG實(shí)現(xiàn)其他不規(guī)則的自定義View呢,而且還簡(jiǎn)單方便。