Android 绘制中国地图及热点省份分布

本文出自: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

你可能感兴趣的:(TCL,雏鹰飞翔计划,·,Android,篇,Android,Github,Android,自定义View,Android,自定义View)