無圖無真相递礼,先上圖
這個(gè)效果看上去很高大上楼吃,通過常規(guī)手段來實(shí)現(xiàn)的難度很大友雳,使用SVG技術(shù)來實(shí)現(xiàn)非常合適。
下面分析一下實(shí)現(xiàn)思路:
1)不規(guī)則的區(qū)域需要使用SVG Path
2)地圖控件肯定是Canvas來繪制的自定義控件
3)點(diǎn)擊變色使用Android原生的觸摸事件即可解決
地圖的SVG矢量圖可以通過網(wǎng)絡(luò)找到資源署辉,再通過處理使之變成Android系統(tǒng)可以使用的矢量圖(本質(zhì)上是把SVG的標(biāo)簽進(jìn)行簡(jiǎn)化)栅受,下面以臺(tái)灣地圖為例:fillColor定義Path填充的顏色将硝,strokeColor定義Path邊界的顏色,strokeWidth定義Path邊界的寬度屏镊,pathData定義Path實(shí)際的路徑依疼,在本文中也就是地圖的路徑,其中一個(gè)城市的Path路徑如下:
path
android:fillColor="#CCCCCC"
android:strokeColor="#ffffff"
android:strokeWidth="0.5"
android:pathData="M573.31,330.07L570.81,329.19L570.81,329.19L569.19,331.78L567.43,336.57L566.91,338.87L567.08,339.84L565.09,340.51L563.01,342.21L561.38,344.46L560.72,346.75L560.25,349.77L559.08,351.52L559.03,351.58L565.55,354.4L577.39,358.16L581.64,360.42L584.68,363.43L586.13,366.31L585.37,369.2L583.52,370.26L579.45,370.95L574.88,372.83L572.17,376.22L570.4,380.67L568.44,383.05L571.93,391.75L574.96,395.25L579.68,398.76L589.24,403.27L592.25,405.9L592.87,410.03L592.59,413.09L591.97,415.79L591.2,418.26L591.97,420.54L592.72,423.64L591.75,427.3L591.03,431.27L594.24,432.05L600.28,430.39L605.63,430.05L609.36,430.42L619.23,427.92L623.16,431.05L624.19,434.52L626.8,437.05L630.36,443.18L630.86,447.01L631.47,446.58L639.77,445.8L641.99,446.4L645.4,438.18L656.39,427.05L659.34,422.48L658.89,419.04L659.95,415.32L661.77,410.22L658.01,408.53L652.3,405.28L649.14,403.02L644.78,402.27L639.92,399.36L635.91,396.26L635.46,392.85L639.09,387.74L640.56,381.67L639.32,375.96L639.04,372.02L636.36,369.95L632.94,368.26L630.89,364.68L628.22,361.93L625.21,362.18L622.21,361.61L619.74,359.17L616.47,356.79L611.32,355.15L608.61,352.24L609.5,347.63L606.68,344.37L601.31,342.61L598.09,341.08L594.7,340.1L590.77,338.57L587.64,331.75L584.01,329.31L573.31,330.07z" />
臺(tái)灣地圖的SVG文件是一個(gè)XML文件而芥,把他放到res/raw/taiwanhigh.xml律罢,在MainActivity中可以通過getResources().openRawResource(R.raw.taiwanhigh)獲取到該XML文件,進(jìn)行XML解析并通過工具類PathParser拿到里面的PathData棍丐,將路徑數(shù)據(jù)作為一個(gè)成員變量賦值給自定義的地圖控件TaiWan误辑。通過for循環(huán)遍歷整個(gè)XML把所有城市的數(shù)據(jù)獲取到。通過定義一個(gè)City類來城市的PathData歌逢。我們需要的不僅僅是Path數(shù)據(jù)巾钉,更需要一整塊PathData所圍繞封閉的Region(區(qū)域),才能知道手指是否點(diǎn)在這個(gè)城市上面秘案,這里要new一個(gè)Region砰苍,并使用path_svg.computeBounds(rectF, true)計(jì)算出PathData所占的區(qū)域潦匈。
private TaiWan taiWan;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
taiWan = (TaiWan) findViewById(R.id.taiwan);
ParseSVG();
}
private void ParseSVG() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
List<City> cities = new ArrayList<>();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = getResources().openRawResource(R.raw.taiwanhigh);
Document document = builder.parse(is);
NodeList svgNodeList = document.getElementsByTagName("path");
for (int i = 0; i < svgNodeList.getLength(); i++) {
Element element = (Element) svgNodeList.item(i);
String path = element.getAttribute("android:pathData");
City city = new City(this);
Path path_svg = PathParser.createPathFromPathData(path);
city.setPath(path_svg);
RectF rectF = new RectF();
path_svg.computeBounds(rectF, true);
Region region = new Region();
region.setPath(path_svg, new Region((int) (rectF.left), (int) (rectF.top), (int) (rectF.right), (int) (rectF.bottom)));
city.setRegion(region);
cities.add(city);
}
taiWan.setCities(cities);
} catch (Exception e) {
e.printStackTrace();
}
}
顯而易見地是,我們自定義了Taiwan作為地圖控件赚导,并在Taiwan里有一個(gè)集合cities历等,cities里存放了所有城市City的PathData,在onDraw中只要繪制出PathData辟癌,就能夠顯示出整個(gè)臺(tái)灣地圖。為了使圖片好看一點(diǎn)荐捻,我把畫布在X黍少,Y坐標(biāo)上都擴(kuò)大了1.2倍。 接下來只要處理好手指點(diǎn)擊在地圖上的變色效果就好了处面。在Touch事件里面厂置,獲取觸摸點(diǎn)event.getX()和event.getY(),遍歷所有的City并使用region.contains((int) (x / 1.2f), (int) (y / 1.2f))判斷該點(diǎn)是否處在Region中魂角,如果在則表示手指按在該城市上面昵济,在City中定義了布爾值isTouch,當(dāng)手指按下時(shí)為True野揪,其他狀態(tài)為False访忿。之所以在 x 和 y都要 除以1.2f 是由于之前我把畫布在X,Y坐標(biāo)上都擴(kuò)大了1.2倍斯稳,現(xiàn)在需要調(diào)整坐標(biāo)系來要正確計(jì)算Region海铆。最后別忘記調(diào)用invalidate,通知系統(tǒng)重繪挣惰。
public class TaiWan extends View {
List<City> cities;
public TaiWan(Context context) {
super(context);
}
public TaiWan(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (null != cities && cities.size() > 0) {
canvas.scale(1.2f, 1.2f);
for (City city : cities) {
city.draw(canvas);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
for (City city : cities) {
Region region = city.getRegion();
boolean isContain = region.contains((int) (x / 1.2f), (int) (y / 1.2f));
if (isContain) {
city.setTouch(true);
} else {
city.setTouch(false);
}
}
invalidate();
}
return true;
}
public List<City> getCities() {
return cities;
}
public void setCities(List<City> cities) {
this.cities = cities;
}
}
City的代碼如下卧斟,isTouch為True,按下時(shí)繪制實(shí)心Path并附加陰影憎茂,isTouch為False時(shí)繪制空心區(qū)域珍语,so easy!
public class City {
private Context context;
private Path path;
private Paint paint;
private boolean isTouch;
private Region region;
public City(Context context) {
this.context = context;
paint = new Paint();
paint.setStrokeWidth(3);
paint.setAntiAlias(true);
}
public boolean isTouch() {
return isTouch;
}
public void setTouch(boolean touch) {
isTouch = touch;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public void draw(Canvas canvas) {
if (isTouch()) {
int color = getRanColor();
paint.setColor(color);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setShadowLayer(8, 3, 3, Color.DKGRAY);
} else {
paint.setColor(this.context.getResources().getColor(R.color.color1));
paint.setStyle(Paint.Style.STROKE);
}
canvas.drawPath(path, paint);
}
private int getRanColor() {
int[] colors = {this.context.getResources().getColor(R.color.color2),
this.context.getResources().getColor(R.color.color3), this.context.getResources().getColor(R.color.color4)};
return colors[(int) (Math.random() * 3)];
}
public Region getRegion() {
return region;
}
public void setRegion(Region region) {
this.region = region;
}
}
完整代碼可見 : https://github.com/pengzee/SVG_TW