本文出自:http://blog.csdn.net/dt235201314/article/details/78190492

一丶效果图

Android 绘制中国地图及热点省份分布_第1张图片

二丶需求功能点技术点

1.上一篇说到被砍掉的需求:中国地图省份热点,这一篇提供方案。

2.Path绘制中国地图

3.SVG 转 Android Canvas Path

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 转 Android Canvas Path

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 cmdPositions = 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;        }    }}
.xml

    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

更多相关文章

  1. Android中的人脸检测的示例代码(静态和动态)
  2. 谷歌Android为何关闭源代码?
  3. android boot 代码流程 1
  4. Android 混淆代码有关问题总结
  5. Android 开发常用代码
  6. Android程序实现全屏代码
  7. [原]Android有用代码片断(六)

随机推荐

  1. Linux系统Android(安卓)NDK编译环境搭建
  2. 一步一步学android OpenGL ES2.0编程(1)
  3. Android(安卓)char数据类型乱码��解决方法
  4. Android初学笔记(记录自己的学习过程,有不
  5. RxJava2 使用详解一之基础教程
  6. Fragment中使用listview
  7. android虚拟按键的实现
  8. Android网络框架-OkHttp使用
  9. Android读书笔记2-AndroidManifest.xml解
  10. android调用ITelephony类,AIDL实现电话,联