android自定义View,区域热力地图(具备每个省份的点击接口)

需求:需要一个热力地图,全面的显示中国个省份的数据对比


功能:完整的中国地图(可缩放,平移,点击)

           数据颜色区域条(各省颜色按数据所在区间而定)

           各省份颜色可设置

           各省份具备点击事件接口(点击该省份,黑线描出该省边框)


项目地址:https://github.com/AndroidCloud/-china-map-for-android-


最终实现效果:

                                                            android自定义View,区域热力地图(具备每个省份的点击接口)

技术路线(简要技术思路,具体实现详见GitHub的Demo):

热力图View技术:

                           1,解析出assest目录下的SVG文件(典型的XML解析),解析出该地图所有的Path(详见GitHub的                                   Demo中util包中的SVG解析类),该Demo中使用的模拟数据在util包中ColorChangeHelp类中

                                 

                    2,将解析出的Path封装到MyMap和Province对象中

           public class MyMap {
           private float Max_x;//地图最大横坐标
            private float Min_x;//地图最小横坐标
            private float Max_y;//地图最大纵坐标
            private float Min_y;//地图最小纵坐标
            private List<Province> provinceslist;//地图的省份集合
            //getset省略

           public class Province {
           private String name;//省份名称
            private List<Path> listpath;//该省份的Path集合
            private int linecolor;//绘制该省份的边框颜色
            private int color;//该省份的颜色
            private List<Lasso> pathLasso;//Path集合对应的Lasso集合
            //getset省略

           public class Lasso {
           // polygon coordinates
           private float[] mPolyX, mPolyY;
           // number of size in polygon
           private int mPolySize;
           //判断一个点是否在一个不规则的Path            public boolean contains(float x, float y) {
             boolean result = false;
            for (int i = 0, j = mPolySize - 1; i < mPolySize; j = i++) {
            if ((mPolyY[i] < y && mPolyY[j] >= y)
                    || (mPolyY[j] < y && mPolyY[i] >= y)) {
                if (mPolyX[i] + (y - mPolyY[i]) / (mPolyY[j] - mPolyY[i])
                        * (mPolyX[j] - mPolyX[i]) < x) {
                    result = !result;
                }
            }
            }
           return result;
            }

                              3,画View(首先进行大小适配),第一次绘制时,按照取到的该地图的最大横坐标和最大纵坐标先将                                 path中的所有点进行重置,重置规则为最大横坐标按照View的宽度计算出比例,然后View的高度使                             用纵坐标的最大值按照该比例缩放进行设置,然后其他所有的点按照该比例缩放。

                                         

           protected void onDraw(Canvas canvas) {
           //第一次绘制,重置所有点的坐标,使得map适应屏幕大小
            if (isFirst){
           Width=getWidth();
           Height=getHeight();
           //首先重置所有点的坐标,使得map适应屏幕大小
            if (map!=null){
            map_scale=Width/map.getMax_x();
           }
           scalePoints(canvas,map_scale);
           isFirst=false;
           }else{
           //非第一次绘制,重绘缩放和平移后的View
           canvas.concat(myMatrix);
           canvas.translate(OnMoveX, OnMoveY);
           drawMap(canvas);
           }
           super.onDraw(canvas);
           }

           //设置View的大小
           @Override
           protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           super.onMeasure(widthMeasureSpec, heightMeasureSpec);
           int width=MeasureSpec.getSize(widthMeasureSpec);
           setMeasuredDimension(width, (int) map.getMax_y());
           }

                                4,处理该View的事件,包括:

                         点击事件(点击坐标运算和定位,进行重绘,被点击的省份外框黑色,其他省份外框还是默 认灰色)

                         双指缩放事件(对canvas重设缩放后的Matrix)

                          滑动平移事件(对canvas进行平移操作)

                                        

           public boolean onTouchEvent(MotionEvent event) {
           switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                onTouchDown(event);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                // 多点触摸
                onPointerDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                 onTouchMove(event);
                break;
            case MotionEvent.ACTION_UP:
               // 必须是单指移动
                if (mode==MOVE){
                offX = (event.getX() - downX)/getScale();//X轴移动距离
                offY = (event.getY() - downY)/getScale();//y轴移动距离
                 if (!criticalflag)  {
                UpMoveX=UpMoveX+offX;
                UpMoveY=UpMoveY+offY;}
                }
                mode = NONE;
                if (Math.abs(event.getX()-downX)<10&&Math.abs(event.getY()-downY)<10                      ){
                    RectF rectF=getMatrixRectF();
                    //判断点的是哪个省,然后相应的省变颜色
                    changeProvinceColor(event,rectF);
                }
                break;
            // 多点松开
             case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                break;
                }
                return true;
                }

                                    5,点击事件接口

                                           

           public interface onProvinceClickLisener{
           public void onChose(String provincename);
           }  

           //判断点的是哪个省,然后相应的省变颜色
           private void changeProvinceColor(MotionEvent event,RectF rectF) {
           for (Province province:map.getProvinceslist()){
           //province.setColor(Color.BLUE);
           province.setLinecolor(Color.GRAY);
            }
           for (Province p:map.getProvinceslist()){
           for (Lasso lasso:p.getPathLasso()){
             //当前点击的坐标进行换算到缩放和平移后的坐标,然后判断点击的Path在哪个省份
             PointF pf=new PointF(event.getX() / getScale() - rectF.left / getScale(             )-OnMoveX
                    ,event.getY() / getScale() - rectF.top/getScale()-OnMoveY);
            if (lasso.contains(pf.x,pf.y)){
                provincename=p.getName();
                //p.setColor(Color.RED);
                p.setLinecolor(Color.BLACK);
                invalidate();
                //暴露到Activity中的接口,把省的名字传过去
                onProvinceClickLisener.onChose(provincename);
                break;
            }
            }
            }
            }

                                   6,Activity中使用

           <com.example.vmmet.mymapview.view.MyMapView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_marginLeft="5dp"
           android:layout_marginRight="5dp"
           android:layout_marginBottom="3dp"
           android:layout_marginTop="3dp"
           android:id="@+id/view"
           />
           //在OnCreate中初始化
           //初始化map
           initMap();
           //初始化map各省份颜色
           ColorChangeHelp.changeMapColors(myMap,ColorChangeHelp.nameStrings[0]);
           mapview.chingeMapColors();
           mapview.setOnChoseProvince(new MyMapView.onProvinceClickLisener() {
          @Override
           public void onChose(String provincename) {
           //地图点击省份回调接口,listview滚动到相应省份位置
            for (int i = 0; i < list.size(); i++) {
            if (list.get(i).contains(provincename)) {
                adapter.setPosition(i);
                province_listview.setSelection(i);
                break;
            }
            }
            }
            });


           private void initMap() {
           //拿到SVG文件,解析成对象
            myMap = new SvgUtil(this).getProvinces();
           //设置缩放最大和最小倍数
            mapview.setMaxScale(3);
           mapview.setMinScale(1);
           //传数据
            mapview.setMap(myMap);
          


渐变色条View技术

                       1,封装MycolorArea对象

            public class MycolorArea {
             private int color;//色块颜色
              private String text;//对应色块的数值
              //getset省略

                         2,绘制ColorView

             public class ColorView extends View{
             //部分代码省略
              @Override
             protected void onDraw(Canvas canvas) {
              if (list==null)return;
              if (list.size()>0){
             //根据色块集合,平均绘制
               int width_average=getWidth()/list.size();
              for (int i=0;i<list.size();i++){
                colorPaint.setColor(list.get(i).getColor());
                canvas.drawRect(i * width_average, 0, (i + 1) * width_average, getHe                ight() / 3, colorPaint);
                colorPaint.setColor(Color.BLACK);
                colorPaint.setTextSize(getHeight()/3);
                canvas.drawText(list.get(i).getText(),width_average/2+i * width_aver                age,getHeight()/3*5/2,colorPaint);
             }
             }
             super.onDraw(canvas);
             }

                         3,Activity中使用

             <com.example.vmmet.mymapview.view.ColorView
              android:layout_width="match_parent"
              android:layout_height="33dp"
              android:id="@+id/colorView"/>

             //设置颜色渐变条
             setColorView();
             private void setColorView() {
             colorView_hashmap = new HashMap<>();
             for (int i = 0; i < ColorChangeHelp.nameStrings.length; i++) {
              String colors[] = ColorChangeHelp.colorStrings[i].split(",");
              String texts[] = ColorChangeHelp.textStrings[i].split(",");
              List<MycolorArea> list = new ArrayList<>();
             for (int j = 0; j < colors.length; j++) {
              MycolorArea c = new MycolorArea();
              c.setColor(Color.parseColor(colors[j]));
              c.setText(texts[j]);
              list.add(c);
             }
             colorView_hashmap.put(ColorChangeHelp.nameStrings[i], list);
             }
             colorView.setList(colorView_hashmap.get(ColorChangeHelp.nameStrings[0])             );
             }


      做开发,需要脚踏实地,日积月累,愿你我共勉