1、提出问题

在必问上有一位同学提出了关于不规则项的绘制问题,具体可参照如下链接:https://biwen.csdn.net/question/3399

2、分析问题

■ 笔者的分析:
由于指定的图形为不规则的,同时需要根据数据对不规则项设置不同的颜色,滑动到不同的不规则项还需要改变背景色。其中的不规则,就需要开发者去绘制去自定义。

在做复杂的自定义动画的时候,我们的开发者会先定义Path来规划动画的路径。然后再在不同的节点去设置动画。本例中其实也可以参照这个思想去解决问题。矢量图标SVG其实就是定义多个PathData绘制图形的。

■ SVG
在解决问题之前,我们准备一下关于SVG的知识:
https://www.w3school.com.cn/svg/index.asp

由于我们需要解析路径数据PathData,先了解一下:

  • M = moveto(M X,Y) :将画笔移动到指定的坐标位置
  • L =lineto(L X,Y) :画直线到指定的坐标位置
  • H = horizontal lineto(H X):画水平线到指定的X坐标位置
  • V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
  • C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
  • S = smooth curveto(S X2,Y2,ENDX,ENDY)
  • Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
  • T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射
  • A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
  • Z = closepath():关闭路径

3、解决问题

3.1、数据准备

通过图形的SVG文件和分析数据,生成模拟数据供程序调用。

3.2、架构设计

下面对这个业务进行架构设计。

(1)全体绘制接口

该接口主要就是对不规则View的抽象,透过接口我们可以知道测量指定图形的宽和高,需要

  • 根据路径数据,测量宽和高
  • 初始数据和刷新数据
  • 绘制图像
  • 管理事件
public interface MapDrawer {// 刷新数据void setData(MapConfigEntity config, Map<String, Integer> data);// 测量宽高VectorManager.Viewport getViewport();// 绘制void onDraw(Canvas canvas, Paint paint);// 事件管理boolean onTouch(MapPointEntity dataPoint);
}
(2)单个绘制接口

单个不规则图形接口需要实现如下功能。

  • 绘制文字
  • 绘制区域
  • 根据路径数据,判断是否在区域内。
public interface MapItemDrawer {void drawText(Canvas canvas, Paint paint);void drawRegion(Canvas canvas, Paint paint);boolean checkIsSelected(float x, float y);Path getPath();
}
(3)样式定义

根据自己的需要实现MapStyleRuler的接口。

MapStyleRuler

  • MapNormalStyle(默认状态)
  • MapSelectStyle(选中状态)
public interface MapStyleRuler {MapTextSelector.MapTextStyle getTextStyle();MapShapeSelector.MapShapeStyle getShapeStyle();
}
3.3、功能实现

下面对功能实现做说明。

(1)模拟数据读取

从文件读取数据或者从服务端都是耗时操作,所以需要开启子线程去读取。
这里只是模拟获取数据,就简单的封装了AsyncTask,文件读取的时候可以直接使用。
服务端读取数据,可以用Retrofit+LiveData或者Retrofit+RxJava实现。

public class MapActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {// 启动获取数据线程。private void initData() {MapMockDataFactory factory = new MapMockDataFactory(this,new MapMockDataFactory.Callback() {@Overridepublic void onFinish(MapConfigEntity config, MapDataEntity data) {mConfig = config;mData = data;splitData();}});factory.execute();}
}// 封装AsyncTask,用于读取文件等耗时操作。
public class MapMockDataFactory extends AsyncTask<Void, Void, Void> {private WeakReference<Context> mContext;private MapConfigEntity mConfig;private MapDataEntity mData;private Callback mCallback;public interface Callback {void onFinish(MapConfigEntity config, MapDataEntity data);}public MapMockDataFactory(Context context, Callback callback) {this.mContext = new WeakReference<>(context.getApplicationContext());this.mCallback = callback;}@Overrideprotected Void doInBackground(Void... voids) {mConfig = getMockMapConfig(mContext.get());mData = getMockMapData(mContext.get());return null;}@Overrideprotected void onPostExecute(Void aVoid) {super.onPostExecute(aVoid);mCallback.onFinish(mConfig, mData);}private MapConfigEntity getMockMapConfig(final Context context) {MapConfigEntity entity = null;try {InputStream inputStream = context.getApplicationContext().getResources().openRawResource(R.raw.map_config);InputStreamReader reader = new InputStreamReader(inputStream);Gson gson = new Gson();entity = gson.fromJson(reader, MapConfigEntity.class);} catch (Exception e) {e.printStackTrace();}return entity;}private MapDataEntity getMockMapData(final Context context) {MapDataEntity entity = null;try {InputStream inputStream = context.getApplicationContext().getResources().openRawResource(R.raw.map_data);InputStreamReader reader = new InputStreamReader(inputStream);Gson gson = new Gson();entity = gson.fromJson(reader, MapDataEntity.class);} catch (Exception e) {e.printStackTrace();}return entity;}
}
(2)拆分数据
public class MapActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {private void splitData() {List<MapDataItemEntity> listData = mData.getMapData();for (MapDataItemEntity it : listData) {mAddData.put(it.getName(), Integer.parseInt(it.getAdd()));mNowData.put(it.getName(), Integer.parseInt(it.getNow()));...}rgData.check(R.id.rb_now);}@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {switch (checkedId) {case R.id.rb_add:mMapView.setMapData(mConfig, mAddData);break;case R.id.rb_now:mMapView.setMapData(mConfig, mNowData);break;...default:break;}}
}
(3)MapView实现
public class MapView extends View {private Paint mPaint;private MapDrawer mMapDrawer;private float mScale = 1f;public MapView(Context context) {super(context);this.init();}public MapView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);this.init();}public MapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.init();}private void init() {mPaint = new Paint();mPaint.setAntiAlias(true);mMapDrawer = new MapDrawerImpl();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int viewWidth = MeasureSpec.getSize(widthMeasureSpec);int viewHeight = MeasureSpec.getSize(heightMeasureSpec);VectorManager.Viewport mViewport = mMapDrawer.getViewport();if (mViewport != null) {mScale = Math.min(viewWidth / mViewport.viewportWidth,viewHeight / mViewport.viewportHeight);}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.scale(mScale, mScale);mMapDrawer.onDraw(canvas, mPaint);}@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouchEvent(MotionEvent event) {MapPointEntity dataPoint = new MapPointEntity(event.getX() / mScale,event.getY() / mScale);boolean needInvalidate = mMapDrawer.onTouch(dataPoint);if (needInvalidate) {invalidate();return true;}return super.onTouchEvent(event);}public void setMapData(final MapConfigEntity mapData, final Map<String, Integer> data) {mMapDrawer.setData(mapData, data);requestLayout();invalidate();}
}
(4)MapDrawer实现
public class MapDrawerImpl implements MapDrawer {private List<MapItemDrawer> mMapListDrawer;private VectorManager mViewportManager;public MapDrawerImpl() {mViewportManager = new VectorManager();mMapListDrawer = new ArrayList<>();}@Overridepublic VectorManager.Viewport getViewport() {if (mMapListDrawer != null && !mMapListDrawer.isEmpty()) {for (MapItemDrawer it : mMapListDrawer) {mViewportManager.setPath(it.getPath());}}return mViewportManager.getViewport();}@Overridepublic void setData(final MapConfigEntity config, final Map<String, Integer> data) {mMapListDrawer = null;List<MapItemEntity> itemEntityList = config.getMapItemList();if (itemEntityList != null && !itemEntityList.isEmpty()) {mMapListDrawer = new ArrayList<>();for (MapItemEntity it : itemEntityList) {Path path = PathParser.createPathFromPathData(it.getPathData());MapItemDrawer itemDrawer = new MapItemDrawerImpl(it, path,config.getTextSelector(), config.getShapeSelector(),config.getMapLevel(), data);mMapListDrawer.add(itemDrawer);}}}@Overridepublic void onDraw(final Canvas canvas, final Paint paint) {for (MapItemDrawer it : mMapListDrawer) {it.drawRegion(canvas, paint);}for (MapItemDrawer it : mMapListDrawer) {it.drawText(canvas, paint);}}@Overridepublic boolean onTouch(final MapPointEntity dataPoint) {boolean needInvalidate = false;for (MapItemDrawer it : mMapListDrawer) {if (it.checkIsSelected(dataPoint.getX(), dataPoint.getY())) {needInvalidate = true;}}return needInvalidate;}
}public class VectorManager {private RectF mRect = new RectF();private float defaultValue = -1f;private float mLeft = defaultValue;private float mRight = defaultValue;private float mTop = defaultValue;private float mBottom = defaultValue;public void setPath(Path path) {path.computeBounds(mRect, true);if (Float.compare(mLeft, defaultValue) == 0) {mLeft = mRect.left;} else {mLeft = Math.min(mLeft, mRect.left);}if (Float.compare(mRight, defaultValue) == 0) {mRight = mRect.right;} else {mRight = Math.max(mRight, mRect.right);}if (Float.compare(mTop, defaultValue) == 0) {mTop = mRect.top;} else {mTop = Math.min(mTop, mRect.top);}if (Float.compare(mTop, defaultValue) == 0) {mBottom = mRect.bottom;} else {mBottom = Math.max(mBottom, mRect.bottom);}}public Viewport getViewport() {float mViewportWidth = mRight - mLeft;float mViewportHeight = mBottom - mTop;return new Viewport(mViewportWidth, mViewportHeight);}public static class Viewport {public float viewportWidth;public float viewportHeight;public Viewport(float viewportWidth, float viewportHeight) {this.viewportWidth = viewportWidth;this.viewportHeight = viewportHeight;}}
}
(5)MapItemDrawer实现
public class MapItemDrawerImpl implements MapItemDrawer {private final MapItemEntity mConfig;private MapStyleRuler mNormalStyle;private MapStyleRuler mSelectStyle;private boolean mIsSelected;private Path mPath;public MapItemDrawerImpl(final MapItemEntity config, final Path path,final MapTextSelector textSelector, final MapShapeSelector shapeSelector,final List<MapLevelEntity> mapLevel, final Map<String, Integer> data) {this.mConfig = config;this.mPath = path;this.mIsSelected = false;this.mNormalStyle = new MapNormalStyle(config.getName(),mapLevel, data,textSelector, shapeSelector);this.mSelectStyle = new MapSelectStyle(textSelector, shapeSelector);}@Overridepublic void drawText(final Canvas canvas, final Paint paint) {MapTextSelector.MapTextStyle textStyle = getTextStyle(mIsSelected);this.drawTextByStyle(canvas, paint, textStyle);}private void drawTextByStyle(final Canvas canvas, final Paint paint,final MapTextSelector.MapTextStyle entity) {paint.setStyle(Paint.Style.FILL);paint.setColor(Color.parseColor(entity.getTextColor()));paint.setTextSize(entity.getTextSize());List<String> namePoint = mConfig.getNamePoint();MapPointEntity pointEntity = new MapPointEntity(namePoint.get(0), namePoint.get(1));canvas.drawText(mConfig.getName(),pointEntity.getX(),pointEntity.getY(),paint);}@Overridepublic void drawRegion(Canvas canvas, Paint paint) {MapShapeSelector.MapShapeStyle shapeStyle = getRegionStyle(mIsSelected);this.drawRegionByStyle(canvas, paint, shapeStyle);}private void drawRegionByStyle(Canvas canvas, Paint paint,MapShapeSelector.MapShapeStyle entity) {paint.setStyle(Paint.Style.FILL);paint.setColor(Color.parseColor(entity.getFillColor()));canvas.drawPath(mPath, paint);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(entity.getStrokeWidth());paint.setColor(Color.parseColor(entity.getStrokeColor()));canvas.drawPath(mPath, paint);}@Overridepublic boolean checkIsSelected(final float x, final float y) {RectF rectF = new RectF();mPath.computeBounds(rectF, true);Region region = new Region();region.setPath(mPath, new Region((int) rectF.left,(int) rectF.top,(int) rectF.right,(int) rectF.bottom));return this.mIsSelected = region.contains((int) x, (int) y);}@Overridepublic Path getPath() {return mPath;}private MapTextSelector.MapTextStyle getTextStyle(final boolean isSelected) {MapTextSelector.MapTextStyle textStyle;if (isSelected) {textStyle = mSelectStyle.getTextStyle();} else {textStyle = mNormalStyle.getTextStyle();}return textStyle;}private MapShapeSelector.MapShapeStyle getRegionStyle(final boolean isSelected) {MapShapeSelector.MapShapeStyle shapeStyle;if (isSelected) {shapeStyle = mSelectStyle.getShapeStyle();} else {shapeStyle = mNormalStyle.getShapeStyle();}return shapeStyle;}
}
(6)MapStyleRuler实现

MapNormalStyle

public class MapNormalStyle implements MapStyleRuler {private final String mName;private final List<MapLevelEntity> mLevel;private final Map<String, Integer> mData;private final MapTextSelector mDefaultTextSelector;private final MapShapeSelector mDefaultShapeSelector;public MapNormalStyle(final String mName,final List<MapLevelEntity> mLevel,final Map<String, Integer> mData,final MapTextSelector mDefaultTextSelector,final MapShapeSelector mDefaultShapeSelector) {this.mName = mName;this.mLevel = mLevel;this.mData = mData;this.mDefaultTextSelector = mDefaultTextSelector;this.mDefaultShapeSelector = mDefaultShapeSelector;}@Overridepublic MapTextSelector.MapTextStyle getTextStyle() {return mDefaultTextSelector.getOnNormalState();}@Overridepublic MapShapeSelector.MapShapeStyle getShapeStyle() {MapShapeSelector.MapShapeStyle shapeStyle = mDefaultShapeSelector.getOnNormalState();String colorString = getColor(mName);if (!TextUtils.isEmpty(colorString)) {shapeStyle.setFillColor(colorString);}return shapeStyle;}private String getColor(final String name) {String color = null;Integer data = getMapItemData(name);for (MapLevelEntity it : mLevel) {int min = Integer.parseInt(it.getMin());int max = Integer.parseInt(it.getMax());if (data >= min && data <= max) {color = it.getColor();break;}}return color;}private Integer getMapItemData(final String name) {Integer data = null;for (String key : mData.keySet()) {if (name.equals(key)) {data = mData.get(key);break;}}return data;}
}

MapSelectStyle

public class MapSelectStyle implements MapStyleRuler {private final MapTextSelector mDefaultTextSelector;private final MapShapeSelector mDefaultShapeSelector;public MapSelectStyle(final MapTextSelector mDefaultTextSelector,final MapShapeSelector mDefaultShapeSelector) {this.mDefaultTextSelector = mDefaultTextSelector;this.mDefaultShapeSelector = mDefaultShapeSelector;}@Overridepublic MapTextSelector.MapTextStyle getTextStyle() {return mDefaultTextSelector.getOnSelectedState();}@Overridepublic MapShapeSelector.MapShapeStyle getShapeStyle() {return mDefaultShapeSelector.getOnSelectedState();}
}
3.4、功能验证

选择分析数据的类型,可以得到不同的分析图像。
功能验证通过。

4、最后

由于定义数据是可以改变的,观测的数据类型也是可以改变的。
只要改变定义数据,就可以绘制不同的不规则图形;只要改变观测数据,就可以表示不同的分析图像。

5、参考文献
  1. Android View重绘和更新常用的方法

【问答】用SVG矢量图形自定义不规则控件——中国地图相关推荐

  1. WPF自定义动画控件 风机

    原文:WPF自定义动画控件 风机 一:创建WPF项目 二:在项目下添加文件Themes,在此文件下添加新项 "资源词典"取名为 Generic.xaml  注意大小写,之前遇到因为 ...

  2. Android 手机卫士--自定义组合控件构件布局结构

    由于设置中心条目中的布局都很类似,所以可以考虑使用自定义组合控件来简化实现 本文地址:http://www.cnblogs.com/wuyudong/p/5909043.html,转载请注明源地址. ...

  3. Android View体系(十)自定义组合控件

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

  4. iOS自定义View 控件自动计算size能力

    iOS自定义View 控件自动计算size能力 背景 在使用 UILabel 和 UIImage 的时候,不用指定宽高约束,控件也不会报约束缺失,还可以根据内容自己确定适合的宽高,特别适合 Xib 和 ...

  5. VS2010 自定义用户控件未出现在工具箱的解决方案

    VS2010 自定义用户控件未出现在工具箱的解决方案 参考文章: (1)VS2010 自定义用户控件未出现在工具箱的解决方案 (2)https://www.cnblogs.com/lyout/arch ...

  6. [置顶] 分步实现具有分页功能的自定义DataList控件【附源代码】

    一.控件也是类 [效果] [操作步骤] 1.  新建网站Web 2.  添加类CustomDataList.cs(系统会提示你把类建在App_Code文件夹中),代码如下: using System; ...

  7. [转] 使用模板自定义 WPF 控件

      [转] 使用模板自定义 WPF 控件                                                                                 ...

  8. 自定义组合控件:下拉选择框

    Spinner 自定义组合控件之下拉选择框 项目概述 下拉选择框主要是通过在EditText 下用PopupWindow 动态显示ListView 控件来实现的.下拉选择框可以方便用户的输入效率,以此 ...

  9. 自定义组合控件:Banner、轮播图、广告栏控件

    1. 项目概述 这里,我们使用自定义组合控件实现一个自动轮播的广告条,也叫轮播图,完整版的效果图如下图所示.其实,这就是我们经常见到的滚动广告,默认情况下每隔N 秒会自动滚动,用手指左右滑动时也会切换 ...

最新文章

  1. 图灵奖得主Yann LeCun:我的论文也被NeurIPS拒了
  2. django ---- models继承
  3. BZOJ 4448 主席树+树链剖分(在线)
  4. LiveVideoStackCon讲师热身分享 ( 十一 ) —— 短视频APP的架构设计
  5. java mysql nclob_java语言操作Oracle数据库中的CLOB数据类型 (转)
  6. P1638 逛画展(直尺法)
  7. (76)FPGA随机函数($dist_uniform)
  8. powershell 使用_使用PowerShell提取Azure成本
  9. Codeforces Round #584 (Div. 1 + Div. 2)
  10. 正则表达式的语法汇总
  11. C语言中access/_access函数的使用
  12. UVa 10105 - Polynomial Coefficients
  13. android 重力感应切换屏幕,Android 重力感应和屏幕旋转关系
  14. PHPCMS9.6.0最新版SQL注入和前台GETSHELL漏洞分析 (实验新课)
  15. 什么软件能识别树木花草?亲测好用的软件分享
  16. Skype应用将在7月1日停止支持Windows Phone 8/8.1等系统
  17. HBuilder教程
  18. stm32出现ram、rom不够用,调试方法
  19. 华视电子web读取身份证信息
  20. java markdown转word_Markdown 格式如何转换成 Word?

热门文章

  1. consul服务发现入门篇
  2. creo 二次开发 protookit 官方make file 案例试运行
  3. 谷粒商城 高级篇 (十四) ---------- 商品详情
  4. SPI Flash芯片W25Q32英文版数据手册解读(二)---------存储器知识,寄存器
  5. ICMAX解析无线路由器WAN口应该怎么设置
  6. 大学生用什么样的笔记本电脑好
  7. 在龙门吊上,看到破浪而来的智能时代
  8. 华为核心交换机HW_S7706添加静态路由
  9. table 点击文字按钮预览图片
  10. PCAN-USB FD选型使用比较