Android實(shí)現(xiàn)四色填充地圖功能
先上效果圖:

明顯是一個(gè)自定義view,先解析svg資源(該資源不嚴(yán)謹(jǐn),請(qǐng)勿在正規(guī)),獲取每個(gè)省的path,再用四色算法設(shè)置每個(gè)省的顏色,先列舉主要方法解析svg文件
InputStream inputStream = context.getResources().openRawResource(R.raw.china);proviceItems = new ArrayList<>();try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = null;builder = factory.newDocumentBuilder();Document document = builder.parse(inputStream);Element rootElement = document.getDocumentElement();NodeList items = rootElement.getElementsByTagName("path");
items就是每個(gè)省份的邊框了,遍歷全部省份確定地圖的最左最右最上最下,從而確定地圖的真正寬高,然后再對(duì)比自定義View的寬度,確定畫圖的縮放比例,再定義自定義View的高度
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = MeasureSpec.getSize(widthMeasureSpec);if (totalRect != null && width != 0) {//獲取到地圖的矩形的寬度double mapWidth = totalRect.width();//獲取到比例值scale = (float) (width / mapWidth);//用寬度重新定義高度heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (totalRect.height() * scale), MeasureSpec.EXACTLY);}super.onMeasure(widthMeasureSpec,heightMeasureSpec);}
重寫onDraw方法,把每個(gè)省依次華進(jìn)去,如果有點(diǎn)擊事件,被點(diǎn)擊有變化的話,多數(shù)情況下都是要最后一個(gè)話
@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (proviceItems != null) {int tatalNum = proviceItems.size();canvas.save();canvas.scale(scale, scale);ProviceItem selsetProviceItem = null;// 先畫沒被選中的for (int i = 0; i < tatalNum; i++) {if (!proviceItems.get(i).isSelect()) {proviceItems.get(i).drawItem(canvas, paint);} else {selsetProviceItem = proviceItems.get(i);}}//被選中的最后畫,因?yàn)楸贿x中的有陰影if (selsetProviceItem != null) {selsetProviceItem.drawItem(canvas, paint);}}}
把每個(gè)省都畫到地圖上的方法
paint.setStrokeWidth(1);paint.setColor(drawColor);paint.setStyle(Paint.Style.FILL_AND_STROKE);canvas.drawPath(path, paint);if (isSelect) {//被選擇設(shè)置一下陰影paint.setShadowLayer(20, 0, 0, Color.WHITE);} else {//沒選中去掉陰影paint.clearShadowLayer();}canvas.drawPath(path, paint);
接下來就是關(guān)于顏色的選擇問題了,寫一個(gè)獲得顏色的工具類,主要參數(shù)和構(gòu)造方法
//存放顏色種類,并非真正的顏色private int[] colorTypes;//板塊接壤矩陣,1為接壤private int[][] isBorder;//準(zhǔn)備填充的顏色列表private int[] colors;//顏色多少種類private int TYPE_SIZE ;//總共有幾個(gè)板塊private int plateCount;public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{plateCount = isBorder.length;if (plateCount != isBorder[0].length) {//板塊相鄰關(guān)系必須是方陣,不能是矩陣throw new Exception("colors's length must be equal to isBorder's length!");}this.colors = colors;TYPE_SIZE = colors.length;this.isBorder = isBorder;}
思路就是從第一個(gè)省份開始慢慢嘗試填充顏色,嘗試方法就是從可選的顏色種類中,依次填充進(jìn)去,然后再判斷是否和已經(jīng)填充的身份,是否有接壤并且同個(gè)顏色的,如果有就換一個(gè)顏色,如果最后每個(gè)顏色都嘗試了還是不行就說明上一個(gè)板塊填充有誤,要回退到上個(gè)板塊,如果上板塊還是不行再回退,最后直到每個(gè)板塊都設(shè)置好顏色,顏色種類如果小于4可能會(huì)填充失敗。詳細(xì)見后面代碼,以下是自定義省份的been
public class ProviceItem {private int index;private Path path;//省份顏色private int drawColor;//是否被點(diǎn)擊private boolean isSelect;public ProviceItem(Path path) {this.path = path;}public void setDrawColor(int drawColor) {this.drawColor = drawColor;}public void setIndex(int index) {this.index = index;}public boolean isSelect() {return isSelect;}public void setSelect(boolean select) {isSelect = select;}public void drawItem(Canvas canvas, Paint paint) {paint.setStrokeWidth(1);paint.setColor(drawColor);paint.setStyle(Paint.Style.FILL_AND_STROKE);canvas.drawPath(path, paint);if (isSelect) {//被選擇設(shè)置一下陰影paint.setShadowLayer(20, 0, 0, Color.WHITE);} else {//沒選中去掉陰影paint.clearShadowLayer();}canvas.drawPath(path, paint);}public boolean isTouch(float x, float y) {//創(chuàng)建一個(gè)矩形RectF rectF = new RectF();//獲取到當(dāng)前省份的矩形邊界path.computeBounds(rectF, true);//創(chuàng)建一個(gè)區(qū)域?qū)ο?/span>Region region = new Region();//將path對(duì)象放入到Region區(qū)域?qū)ο笾?/span>region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));//返回是否這個(gè)區(qū)域包含傳進(jìn)來的坐標(biāo)boolean resule = region.contains((int) x, (int) y);//無(wú)法通過代碼確定兩個(gè)省份是否接壤所以只能獲取下標(biāo),人工構(gòu)造省份相鄰矩陣,如果// if (result) {// Log.d("ProviceItemIndex-----", index + "");// }return result;}}
自定義view的代碼
public class MapView extends View {private Paint paint;private Context context;//整個(gè)地圖所占用的矩形,在重新設(shè)配之前private RectF totalRect;private List<ProviceItem> proviceItems;//繪制地圖的顏色private int[] colorArray = new int[]{0xFF1383f2, 0xFFFFDC00, 0xFFFF3D33, 0xFF4ADE8C};//適配比例private float scale = 0;int[] colors;//中國(guó)省份接壤關(guān)系矩陣,劃分34個(gè)省份,自治區(qū),市和特別行政區(qū)等,但是多一個(gè)顏色表示國(guó)外的顏色項(xiàng)目中最后沒有用到int[][] isBorder = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1},{0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},};public MapView(Context context) {super(context);}public MapView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init(context);}public MapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}private void init(Context context) {this.context = context;paint = new Paint();paint.setAntiAlias(true);//開線程解析數(shù)據(jù)loadThread.start();}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = MeasureSpec.getSize(widthMeasureSpec);if (totalRect != null && width != 0) {//獲取到地圖的矩形的寬度double mapWidth = totalRect.width();//獲取到比例值scale = (float) (width / mapWidth);//用寬度重新定義高度heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (totalRect.height() * scale), MeasureSpec.EXACTLY);}super.onMeasure(widthMeasureSpec,heightMeasureSpec);}protected void onDraw(Canvas canvas) {//如果省份數(shù)據(jù)還沒加載出來,實(shí)際縮放比例沒定義出來啥都不用干if (proviceItems != null||scale==0) {super.onDraw(canvas);int tatalNum = proviceItems.size();canvas.save();canvas.scale(scale, scale);ProviceItem selsetProviceItem = null;// 先畫沒被選中的for (int i = 0; i < tatalNum; i++) {if (!proviceItems.get(i).isSelect()) {proviceItems.get(i).drawItem(canvas, paint);} else {selsetProviceItem = proviceItems.get(i);}}//被選中的最后畫,因?yàn)楸贿x中的有陰影if (selsetProviceItem != null) {selsetProviceItem.drawItem(canvas, paint);}}}private Thread loadThread = new Thread(new Runnable() {public void run() {InputStream inputStream = context.getResources().openRawResource(R.raw.china);proviceItems = new ArrayList<>();try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = null;builder = factory.newDocumentBuilder();Document document = builder.parse(inputStream);Element rootElement = document.getDocumentElement();NodeList items = rootElement.getElementsByTagName("path");//定義一個(gè)不可能存在屏幕上的很右邊的點(diǎn)Integer.MAX_VALUE作為最左邊,同理定義一個(gè)不能存在屏幕上的很左邊的點(diǎn)-1作為為最左邊//因?yàn)檠h(huán)每個(gè)省份的最左最右最上最下,左邊下標(biāo)只會(huì)越來越小float left = Integer.MAX_VALUE;float right = -1;float top = Integer.MAX_VALUE;float bottom = -1;for (int i = 0; i < items.getLength(); i++) {Element element = (Element) items.item(i);String pathData = element.getAttribute("android:pathData");Path path = PathParser.createPathFromPathData(pathData);ProviceItem proviceItem = new ProviceItem(path);//設(shè)置省份下標(biāo)//proviceItem.setIndex(i);proviceItems.add(proviceItem);RectF rectF = new RectF();path.computeBounds(rectF, true);left = Math.min(left, rectF.left);right = Math.max(right, rectF.right);top = Math.min(top, rectF.top);bottom = Math.max(bottom, rectF.bottom);}//創(chuàng)建整個(gè)地圖totalRect = new RectF(left, top, right, bottom);try {if (colors == null) {colors = new ColorFillUtil(isBorder, colorArray).getColors();int totalNumber = proviceItems.size();for (int i = 0; i < totalNumber; i++) {proviceItems.get(i).setDrawColor(colors[i]);}handler.sendEmptyMessage(0);}} catch (Exception e) {e.printStackTrace();}} catch (ParserConfigurationException e) {e.printStackTrace();} catch (SAXException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}});private Handler handler = new Handler(Looper.getMainLooper()) {public void handleMessage(@NonNull Message msg) {//返回主線程調(diào)用以下方法//重新測(cè)量,調(diào)用onMeasurerequestLayout();//重新繪圖,系統(tǒng)調(diào)用onDrawinvalidate();}};public boolean onTouchEvent(MotionEvent event) {//將當(dāng)前手指觸摸到位置傳過去 判斷當(dāng)前點(diǎn)擊的區(qū)域handlerTouch(event.getX(), event.getY());return super.onTouchEvent(event);}/*** 判斷區(qū)域** @param x* @param y*/private void handlerTouch(float x, float y) {//判空if (proviceItems == null || proviceItems.size() == 0) {return;}for (ProviceItem proviceItem : proviceItems) {//入股點(diǎn)擊的是這個(gè)省份的范圍之內(nèi) 就把當(dāng)前省份的封裝對(duì)象繪制的方法 傳一個(gè)trueproviceItem.setSelect(proviceItem.isTouch(x / scale, y / scale));}postInvalidate();}
顏色選擇工具類
/*** 板塊顏色填充工具*/public class ColorFillUtil {//存放顏色種類,并非真正的顏色private int[] colorTypes;//板塊接壤矩陣,1為接壤private int[][] isBorder;//準(zhǔn)備填充的顏色列表private int[] colors;//顏色多少種類private int TYPE_SIZE ;//總共有幾個(gè)板塊private int plateCount;public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{plateCount = isBorder.length;if (plateCount != isBorder[0].length) {//板塊相鄰關(guān)系必須是方陣,不能是矩陣throw new Exception("colors's length must be equal to isBorder's length!");}this.colors = colors;TYPE_SIZE = colors.length;this.isBorder = isBorder;}/*** 獲取最后的結(jié)果* @return*/public int[] getColors() {colorTypes = new int[plateCount];int index = 0;int colorType = 0;while (index < plateCount) {if (setColor(index, colorType)) {//設(shè)置顏色種類暫時(shí)成功,接著下一個(gè),直到全部顏色設(shè)置完成,設(shè)置顏色種類從0開始嘗試index++;colorType = 0;} else {//找不到合適的顏色要回退,上一個(gè)板塊修改顏色index--;colorType = colorTypes[index] + 1;if (index == 0)//無(wú)法求解,可能是是顏色種類太少return null;}}return getRealColors();}/*** 返回真正的顏色列表* @return*/private int[] getRealColors() {int[] result = new int[plateCount];for (int i = 0; i < plateCount; i++) {result[i] = colors[colorTypes[i]];}return result;}/*** 嘗試填充顏色 填充成功返回true* @param index 準(zhǔn)備填充的板塊下標(biāo)* @param colorType 準(zhǔn)備填充的顏色種類* @return*/private boolean setColor(int index, int colorType) {if (colorType >= TYPE_SIZE) return false;while (colorType < TYPE_SIZE) {//是否可以設(shè)置顏色種類boolean canSet = true;//循環(huán)判斷準(zhǔn)備填充的顏色與之前的顏色是否沖突for (int i = 0; i < index; i++) {//isBorder[i][index] == 1 表示之前已經(jīng)填充的第i個(gè)板塊和準(zhǔn)備填充的板塊是接壤的//colorType == colorTypes[i] 同時(shí)準(zhǔn)備填充的顏色種類又是一樣的,則準(zhǔn)備填充的顏色要改變,再重新嘗試填充if (isBorder[i][index] == 1 && colorType == colorTypes[i]) {++colorType;canSet = false;break;}}if (canSet) {colorTypes[index] = colorType;return true;}}//找不到合適的顏色,要回退return false;}}
部分資源文件
https://pan.baidu.com/s/1Tgq84epnaFhmiEBotGeBgw
評(píng)論
圖片
表情
