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

来源:互联网 发布:天刀淘宝代购怎么赚钱 编辑:程序博客网 时间:2024/06/03 13:45

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

/** * 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