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

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

一丶效果图


二丶需求功能点技术点

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

2.Path绘制中国地图

3.SVG <path> 转 Android Canvas Path

Android Canvas Path基础:http://www.gcssloop.com/customview/Path_Basic/

三丶看代码

自定义ChinaMapView

扫描二维码关注公众号,回复: 11614922 查看本文章
/**
 * 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.海南是中国最南的省份
     * <p/>
     * 地图边界为
     * 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 <path> 转 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<Integer> 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

<com.example.jinboy.codertodeveloperbytcler.java_demo.appdemo.ui.view.ChinaMapView
    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

猜你喜欢

转载自blog.csdn.net/DT235201314/article/details/78190492