android自定义View,区域热力地图(具备每个省份的点击接口)
需求:需要一个热力地图,全面的显示中国个省份的数据对比
功能:完整的中国地图(可缩放,平移,点击)
数据颜色区域条(各省颜色按数据所在区间而定)
各省份颜色可设置
各省份具备点击事件接口(点击该省份,黑线描出该省边框)
项目地址:https://github.com/AndroidCloud/-china-map-for-android-
最终实现效果:
技术路线(简要技术思路,具体实现详见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;//地图的省份集合 //get和set省略
public class Province { private String name;//省份名称 private List<Path> listpath;//该省份的Path集合 private int linecolor;//绘制该省份的边框颜色 private int color;//该省份的颜色 private List<Lasso> pathLasso;//Path集合对应的Lasso集合 //get和set省略
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;//对应色块的数值 //get和set省略
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]) ); }