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

明顯是一個自定義view,先解析svg資源(該資源不嚴謹,請勿在正規(guī)),獲取每個省的path,再用四色算法設置每個省的顏色,先列舉主要方法解析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就是每個省份的邊框了,遍歷全部省份確定地圖的最左最右最上最下,從而確定地圖的真正寬高,然后再對比自定義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方法,把每個省依次華進去,如果有點擊事件,被點擊有變化的話,多數(shù)情況下都是要最后一個話
@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);}}//被選中的最后畫,因為被選中的有陰影if (selsetProviceItem != null) {selsetProviceItem.drawItem(canvas, paint);}}}
把每個省都畫到地圖上的方法
paint.setStrokeWidth(1);paint.setColor(drawColor);paint.setStyle(Paint.Style.FILL_AND_STROKE);canvas.drawPath(path, paint);if (isSelect) {//被選擇設置一下陰影paint.setShadowLayer(20, 0, 0, Color.WHITE);} else {//沒選中去掉陰影paint.clearShadowLayer();}canvas.drawPath(path, paint);
接下來就是關于顏色的選擇問題了,寫一個獲得顏色的工具類,主要參數(shù)和構造方法
//存放顏色種類,并非真正的顏色private int[] colorTypes;//板塊接壤矩陣,1為接壤private int[][] isBorder;//準備填充的顏色列表private int[] colors;//顏色多少種類private int TYPE_SIZE ;//總共有幾個板塊private int plateCount;public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{plateCount = isBorder.length;if (plateCount != isBorder[0].length) {//板塊相鄰關系必須是方陣,不能是矩陣throw new Exception("colors's length must be equal to isBorder's length!");}this.colors = colors;TYPE_SIZE = colors.length;this.isBorder = isBorder;}
思路就是從第一個省份開始慢慢嘗試填充顏色,嘗試方法就是從可選的顏色種類中,依次填充進去,然后再判斷是否和已經(jīng)填充的身份,是否有接壤并且同個顏色的,如果有就換一個顏色,如果最后每個顏色都嘗試了還是不行就說明上一個板塊填充有誤,要回退到上個板塊,如果上板塊還是不行再回退,最后直到每個板塊都設置好顏色,顏色種類如果小于4可能會填充失敗。詳細見后面代碼,以下是自定義省份的been
public class ProviceItem {private int index;private Path path;//省份顏色private int drawColor;//是否被點擊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) {//被選擇設置一下陰影paint.setShadowLayer(20, 0, 0, Color.WHITE);} else {//沒選中去掉陰影paint.clearShadowLayer();}canvas.drawPath(path, paint);}public boolean isTouch(float x, float y) {//創(chuàng)建一個矩形RectF rectF = new RectF();//獲取到當前省份的矩形邊界path.computeBounds(rectF, true);//創(chuàng)建一個區(qū)域對象Region region = new Region();//將path對象放入到Region區(qū)域對象中region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));//返回是否這個區(qū)域包含傳進來的坐標boolean resule = region.contains((int) x, (int) y);//無法通過代碼確定兩個省份是否接壤所以只能獲取下標,人工構造省份相鄰矩陣,如果// if (result) {// Log.d("ProviceItemIndex-----", index + "");// }return result;}}
自定義view的代碼
public class MapView extends View {private Paint paint;private Context context;//整個地圖所占用的矩形,在重新設配之前private RectF totalRect;private List<ProviceItem> proviceItems;//繪制地圖的顏色private int[] colorArray = new int[]{0xFF1383f2, 0xFFFFDC00, 0xFFFF3D33, 0xFF4ADE8C};//適配比例private float scale = 0;int[] colors;//中國省份接壤關系矩陣,劃分34個省份,自治區(qū),市和特別行政區(qū)等,但是多一個顏色表示國外的顏色項目中最后沒有用到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ù)還沒加載出來,實際縮放比例沒定義出來啥都不用干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);}}//被選中的最后畫,因為被選中的有陰影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");//定義一個不可能存在屏幕上的很右邊的點Integer.MAX_VALUE作為最左邊,同理定義一個不能存在屏幕上的很左邊的點-1作為為最左邊//因為循環(huán)每個省份的最左最右最上最下,左邊下標只會越來越小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);//設置省份下標//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)建整個地圖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) {//返回主線程調用以下方法//重新測量,調用onMeasurerequestLayout();//重新繪圖,系統(tǒng)調用onDrawinvalidate();}};public boolean onTouchEvent(MotionEvent event) {//將當前手指觸摸到位置傳過去 判斷當前點擊的區(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) {//入股點擊的是這個省份的范圍之內 就把當前省份的封裝對象繪制的方法 傳一個trueproviceItem.setSelect(proviceItem.isTouch(x / scale, y / scale));}postInvalidate();}
顏色選擇工具類
/*** 板塊顏色填充工具*/public class ColorFillUtil {//存放顏色種類,并非真正的顏色private int[] colorTypes;//板塊接壤矩陣,1為接壤private int[][] isBorder;//準備填充的顏色列表private int[] colors;//顏色多少種類private int TYPE_SIZE ;//總共有幾個板塊private int plateCount;public ColorFillUtil(int[][] isBorder, int[] colors) throws Exception{plateCount = isBorder.length;if (plateCount != isBorder[0].length) {//板塊相鄰關系必須是方陣,不能是矩陣throw new Exception("colors's length must be equal to isBorder's length!");}this.colors = colors;TYPE_SIZE = colors.length;this.isBorder = isBorder;}/*** 獲取最后的結果* @return*/public int[] getColors() {colorTypes = new int[plateCount];int index = 0;int colorType = 0;while (index < plateCount) {if (setColor(index, colorType)) {//設置顏色種類暫時成功,接著下一個,直到全部顏色設置完成,設置顏色種類從0開始嘗試index++;colorType = 0;} else {//找不到合適的顏色要回退,上一個板塊修改顏色index--;colorType = colorTypes[index] + 1;if (index == 0)//無法求解,可能是是顏色種類太少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 準備填充的板塊下標* @param colorType 準備填充的顏色種類* @return*/private boolean setColor(int index, int colorType) {if (colorType >= TYPE_SIZE) return false;while (colorType < TYPE_SIZE) {//是否可以設置顏色種類boolean canSet = true;//循環(huán)判斷準備填充的顏色與之前的顏色是否沖突for (int i = 0; i < index; i++) {//isBorder[i][index] == 1 表示之前已經(jīng)填充的第i個板塊和準備填充的板塊是接壤的//colorType == colorTypes[i] 同時準備填充的顏色種類又是一樣的,則準備填充的顏色要改變,再重新嘗試填充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
評論
圖片
表情
