本文出自:http://blog.csdn.net/dt235201314/article/details/78190492
一丶效果图
二丶需求功能点技术点
1.上一篇说到被砍掉的需求:中国地图省份热点,这一篇提供方案。
2.Path绘制中国地图
3.SVG
Android Canvas Path基础:http://www.gcssloop.com/customview/Path_Basic/
三丶看代码
自定义ChinaMapView
/** * Created by Shuxin on 2016/8/1. */ public class ChinaMapView extends View implements View.OnTouchListener { private static final int DEFAULT_COLOR = Color.rgb(0x22, 0x22, 0x22); private static final int DEFAULT_SELECTD_COLOR = Color.rgb(0x00, 0xff, 0xff); private static String[] svgPaths = new String[]{ //此处为svgpaths点数值,代码过长(略) }; public enum Area { BeiJing("BeiJing", 0), TianJin("TianJin", 1), ShangHai("ShangHai", 2), ChongQing("ChongQing", 3), HeBei("HeBei", 4), ShanXi("ShanXi", 5), LiaoNing("LiaoNing", 6), HeiLongJiang("HeiLongJiang", 7), JiLin("JiLin", 8), JiangSu("JiangSu", 9), ZheJiang("ZheJiang", 10), AnHui("AnHui", 11), FuJian("FuJian", 12), JiangXi("JiangXi", 13), ShanDong("ShanDong", 14), HeNan("HeNan", 15), HuBei("HuBei", 16), HuNan("HuNan", 17), GuangDong("GuangDong", 18), HaiNan("HaiNan", 19), SiChuan("SiChuan", 20), GuiZhou("GuiZhou", 21), YunNan("YunNan", 22), ShaanXi("ShaanXi", 23), GanSu("GanSu", 24), QingHai("QingHai", 25), NeiMengGu("NeiMengGu", 26), GuangXi("GuangXi", 27), XiZang("XiZang", 28), NingXia("NingXia", 29), XinJiang("XinJiang", 30), AoMen("AoMen", 31), XiangGang("XiangGang", 32), TaiWan("TaiWan", 33); private int value; private String name; private Area(String pName, int pValue) { this.name = pName; this.value = pValue; } public static Area valueOf(int value) { // 手写的从int到enum的转换函数 switch (value) { case 0: return BeiJing; case 1: return TianJin; case 2: return ShangHai; case 3: return ChongQing; case 4: return HeBei; case 5: return ShanXi; case 6: return LiaoNing; case 7: return HeiLongJiang; case 8: return JiLin; case 9: return JiangSu; case 10: return ZheJiang; case 11: return AnHui; case 12: return FuJian; case 13: return JiangXi; case 14: return ShanDong; case 15: return HeNan; case 16: return HuBei; case 17: return HuNan; case 18: return GuangDong; case 19: return HaiNan; case 20: return SiChuan; case 21: return GuiZhou; case 22: return YunNan; case 23: return ShaanXi; case 24: return GanSu; case 25: return QingHai; case 26: return NeiMengGu; case 27: return GuangXi; case 28: return XiZang; case 29: return NingXia; case 30: return XinJiang; case 31: return AoMen; case 32: return XiangGang; case 33: return TaiWan; default: return null; } } } public interface OnProvinceSelectedListener { public void onprovinceSelected(Area pArea); } private OnProvinceSelectedListener xOnProvinceSelectedListener; public void setOnProvinceSelectedListener(OnProvinceSelectedListener pOnProvinceSelectedListener) { this.xOnProvinceSelectedListener = pOnProvinceSelectedListener; } private Path[] xPaths = new Path[34]; private Paint[] xPaints = new Paint[34]; private Paint touchPaint; private int selected = -1; private Matrix xMatrix = new Matrix(); private float translateX, translateY; private int viewHeight, viewWidth; private float minScale = 1; private float maxScale = 6; private float scale; private float defaultScale = 1; private int selectdColor = -1; private int mapColor = -1; public void setPaintColor(Area pArea, int color, boolean isFull) { Paint p = xPaints[pArea.value]; p.setColor(color); if (isFull) { p.setStyle(Paint.Style.FILL); } invalidate(); } public void setPaintColor(int index, int color, boolean isFull) { Paint p = xPaints[index]; p.setColor(color); if (isFull) { p.setStyle(Paint.Style.FILL); } invalidate(); } public void setSelectdColor(int pSelectdColor) { this.selectdColor = pSelectdColor; invalidate(); } public void setMapColor(int pMapColor) { mapColor = pMapColor; invalidate(); } public void selectAProvince(Area pArea) { if (selected == pArea.value) { return; } selected = pArea.value; if (this.xOnProvinceSelectedListener != null) this.xOnProvinceSelectedListener.onprovinceSelected(pArea); invalidate(); } public void up() { translateY += 10; invalidate(); } public void down() { translateY -= 10; invalidate(); } public void left() { translateX += 10; invalidate(); } public void right() { translateX -= 10; invalidate(); } public void restScale() { this.scale = defaultScale; xMatrix.setScale(scale, scale); invalidate(); ; } public void restPosition() { translateX = 0; translateY = 0; invalidate(); } public void zoomIn() { scale += 0.3; invalidate(); } public void zoomOut() { scale -= 0.3; invalidate(); } private void initPaths() { try { SvgPathToAndroidPath lParser = new SvgPathToAndroidPath(); for (int i = 0; i < svgPaths.length; i++) { String svgPath = svgPaths[i]; Path path = lParser.parser(svgPath); xPaths[i] = path; } } catch (Exception e) { e.printStackTrace(); } } private void initPaints() { for (int i = 0; i < xPaints.length; i++) { Paint xPaint = new Paint(Paint.ANTI_ALIAS_FLAG); xPaint.setColor(DEFAULT_COLOR); xPaint.setStrokeWidth(1); xPaint.setStyle(Paint.Style.STROKE); xPaints[i] = xPaint; } touchPaint = new Paint(Paint.ANTI_ALIAS_FLAG); touchPaint.setStyle(Paint.Style.FILL); touchPaint.setColor(DEFAULT_SELECTD_COLOR); touchPaint.setStrokeWidth(1); setOnTouchListener(this); } private PointF[] mPointFs = new PointF[4]; private float height = 0; private float width = 0; private int padding = 8; /** * 计算地图边界 * 1.黑龙江是中国最东,最北的省份 * 2.新疆是中国最西的省份 * 3.海南是中国最南的省份 * * 地图边界为 * 0点 1点 * 0,0------------------heilongjiang.right,0 * | | * | | * 0,hainan.bottom------heilongjiang.right,hainan.bottom * 3点 2点 * 地图宽度--->heilongjiang.right * 地图高度--->hainan.bottom */ private void computeBounds() { RectF hljRF = new RectF(); xPaths[Area.HeiLongJiang.value].computeBounds(hljRF, true); RectF hnRF = new RectF(); xPaths[Area.HaiNan.value].computeBounds(hnRF, true); mPointFs[0] = new PointF(0, 0); mPointFs[1] = new PointF(hljRF.right, 0); mPointFs[2] = new PointF(hljRF.right, hnRF.bottom); mPointFs[3] = new PointF(0, hnRF.bottom); width = hljRF.right + 2 * padding; height = hnRF.bottom + 2 * padding; } public ChinaMapView(Context context) { super(context); initPaths(); computeBounds(); initPaints(); } public ChinaMapView(Context context, AttributeSet attrs) { super(context, attrs); initPaths(); computeBounds(); initPaints(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int speSize = MeasureSpec.getSize(widthMeasureSpec); minScale = defaultScale = scale = speSize / width; setMeasuredDimension(speSize, (int) (speSize * height / width)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); scale = scale > maxScale ? maxScale : scale < minScale ? minScale : scale; xMatrix.setScale(scale, scale); canvas.concat(xMatrix); canvas.translate(translateX + padding, translateY + padding); drawBaseMap(canvas); drawSelectedMap(canvas); } private void drawBaseMap(Canvas pCanvas) { for (int i = 0; i < xPaths.length; i++) { if (mapColor != -1 && xPaints[i].getColor() == DEFAULT_COLOR) { xPaints[i].setColor(mapColor); } pCanvas.drawPath(xPaths[i], xPaints[i]); } } private void drawSelectedMap(Canvas pCanvas) { if (selected >= 0) { if (selectdColor != -1) { touchPaint.setColor(selectdColor); } pCanvas.drawPath(xPaths[selected], touchPaint); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; } private long startOnTouchTime = 0; @Override public boolean onTouch(View pView, MotionEvent pMotionEvent) { switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mode = NONE; if (pMotionEvent.getPointerCount() == 1) { startOnTouchTime = System.currentTimeMillis(); mode = NONE; startPoint.set(pMotionEvent.getX(), pMotionEvent.getY()); } break; case MotionEvent.ACTION_POINTER_DOWN: onPointerDown(pMotionEvent); break; case MotionEvent.ACTION_MOVE: onTouchMove(pMotionEvent); break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: mode = NONE; if (pMotionEvent.getPointerCount() == 1) { long timeCount = System.currentTimeMillis() - startOnTouchTime; if (timeCount < 300 && Math.abs(pMotionEvent.getX() - startPoint.x) < 5f && Math.abs(pMotionEvent.getY() - startPoint.y) < 5f) { try { for (int i = 0; i < xPaths.length; i++) { RectF r = new RectF(); xPaths[i].computeBounds(r, true); Region re = new Region(); re.setPath(xPaths[i], new Region((int) r.left, (int) r.top, (int) r.right, (int) r.bottom)); if (re.contains((int) (pMotionEvent.getX() / scale - translateX - padding), (int) (pMotionEvent.getY() / scale - translateY - padding))) { if (i == selected) { return true; } selected = i; if (this.xOnProvinceSelectedListener != null) this.xOnProvinceSelectedListener.onprovinceSelected(Area.valueOf(selected)); invalidate(); return true; } } } catch (Exception e) { e.printStackTrace(); } } } break; default: break; } return true; } /** * 模式 NONE:无. MOVE:移动. ZOOM:缩放 */ private static final int NONE = 0; private static final int MOVE = 1; private static final int ZOOM = 2; private int mode = NONE; // 默认模式 private double startDis = 0d; private PointF startPoint = new PointF(); /** * 多触点 * * @param event */ private void onPointerDown(MotionEvent event) { if (event.getPointerCount() == 2) { mode = ZOOM; startDis = getDistance(event); } } private void onTouchMove(MotionEvent event) { if (mode == ZOOM && event.getPointerCount() == 2) { double endDis = getDistance(event);//结束距离 if (endDis > 10f) { float tmpScale = (float) (endDis / startDis);//放大倍数 if (tmpScale == 1) { return; } else { scale = tmpScale; invalidate(); } } } else { long timeCount = System.currentTimeMillis() - startOnTouchTime; if (timeCount > 300 && Math.abs(event.getX() - startPoint.x) > 10f && Math.abs(event.getY() - startPoint.y) > 10f) { translateX = event.getX() - startPoint.x; translateY = event.getY() - startPoint.y; invalidate(); } } } /** * @param event * @return 获取两手指之间的距离 */ private double getDistance(MotionEvent event) { double x = event.getX(0) - event.getX(1); double y = event.getY(0) - event.getY(1); return Math.sqrt(x * x + y * y); } /** * 计算两点之间中心点的距离 * * @param event * @return */ private static PointF mid(MotionEvent event) { float midx = event.getX(1) + event.getX(0); float midy = event.getY(1) - event.getY(0); return new PointF(midx / 2, midy / 2); } }这里提供了上下左右移动,缩放,平移等方法,以及省份点击事件监听,颜色设置
SVG
SvgPathToAndroidPath.Java
/** * Created by Shuxin on 2016/8/3. */ public class SvgPathToAndroidPath { private int svgPathLenght = 0; private String svgPath = null; private int mIndex; private List.xmlcmdPositions = new ArrayList<>(); /** * M x,y * L x,y * H x * V y * C x1,y1,x2,y2,x,y * Q x1,y1,x,y * S x2,y2,x,y * T x,y * */ public Path parser(String svgPath) { this.svgPath = svgPath; svgPathLenght = svgPath.length(); mIndex = 0; Path lPath = new Path(); lPath.setFillType(Path.FillType.WINDING); //记录最后一个操作点 PointF lastPoint = new PointF(); findCommand(); for (int i = 0; i < cmdPositions.size(); i++) { Integer index = cmdPositions.get(i); switch (svgPath.charAt(index)) { case 'm': case 'M': { String ps[] = findPoints(i); lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1])); lPath.moveTo(lastPoint.x, lastPoint.y); } break; case 'l': case 'L': { String ps[] = findPoints(i); lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1])); lPath.lineTo(lastPoint.x, lastPoint.y); } break; case 'h': case 'H': {//基于上个坐标在水平方向上划线,因此y轴不变 String ps[] = findPoints(i); lastPoint.set(Float.parseFloat(ps[0]), lastPoint.y); lPath.lineTo(lastPoint.x, lastPoint.y); } break; case 'v': case 'V': {//基于上个坐标在水平方向上划线,因此x轴不变 String ps[] = findPoints(i); lastPoint.set(lastPoint.x, Float.parseFloat(ps[0])); lPath.lineTo(lastPoint.x, lastPoint.y); } break; case 'c': case 'C': {//3次贝塞尔曲线 String ps[] = findPoints(i); lastPoint.set(Float.parseFloat(ps[4]), Float.parseFloat(ps[5])); lPath.cubicTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]), Float.parseFloat(ps[4]), Float.parseFloat(ps[5])); } break; case 's': case 'S': {//一般S会跟在C或是S命令后面使用,用前一个点做起始控制点 String ps[] = findPoints(i); lPath.cubicTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3])); lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3])); } break; case 'q': case 'Q': {//二次贝塞尔曲线 String ps[] = findPoints(i); lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3])); lPath.quadTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3])); } break; case 't': case 'T': {//T命令会跟在Q后面使用,用Q的结束点做起始点 String ps[] = findPoints(i); lPath.quadTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1])); lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1])); } break; case 'a': case 'A':{//画弧 } break; case 'z': case 'Z': {//结束 lPath.close(); } break; } } return lPath; } private String[] findPoints(int cmdIndexInPosition) { int cmdIndex = cmdPositions.get(cmdIndexInPosition); String pointString = svgPath.substring(cmdIndex + 1, cmdPositions.get(cmdIndexInPosition + 1)); return pointString.split(","); } private void findCommand() { cmdPositions.clear(); while (mIndex < svgPathLenght) { char c = svgPath.charAt(mIndex); if ('A' <= c && c <= 'Z') { cmdPositions.add(mIndex); }else if ('a' <= c && c <= 'z') { cmdPositions.add(mIndex); } ++mIndex; } } }
android:id="@+id/vp"
android:background="#FFFF6F"
android:layout_width="match_parent"
android:layout_marginBottom="40dp"
android:layout_marginTop="20dp"
android:layout_height="250dp"/>
java代码颜色属性设置
lView = (ChinaMapView)findViewById(R.id.vp); lView.setOnProvinceSelectedListener(new ChinaMapView.OnProvinceSelectedListener() { @Override public void onprovinceSelected(ChinaMapView.Area pArea) { Toast.makeText(MapViewActivity.this,"您选择了-->"+pArea.name(),Toast.LENGTH_SHORT).show(); } }); lView.setMapColor(Color.BLUE); lView.setPaintColor(ChinaMapView.Area.HeBei, Color.rgb(0xfa,0x74,0x01),true); lView.setPaintColor(ChinaMapView.Area.GuangDong, Color.rgb(0xd2,0x00,0x7f),true); lView.setPaintColor(ChinaMapView.Area.BeiJing, Color.rgb(0x00,0x6f,0xbf),true); lView.setPaintColor(ChinaMapView.Area.SiChuan, Color.rgb(0x00,0x9c,0x85),true); lView.setPaintColor(ChinaMapView.Area.AnHui, Color.rgb(0x8f,0xc4,0x1e),true);四丶参考类容
github代码:https://github.com/xchengx/ChinaMap
五丶实际意义
最初的需求源于将大屏展示(电视)的中国地图热度分布放在手机上,源于手机屏幕小不太适合,砍掉的需求。
如果可以放大缩小,以不同颜色区分热度,点击进入显示详情数据,还是有一定的用户体验。
六丶跪求关注下载源码,200粉小目标
github开源代码分享,原文链接见上文
源码下载记得顺便Star哦~
下载链接:https://github.com/JinBoy23520/CoderToDeveloperByTCLer