1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【问答】用SVG矢量图形自定义不规则控件——中国地图

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

时间:2018-07-09 22:08:19

相关推荐

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

1、提出问题

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

2、分析问题

■笔者的分析:

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

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

■SVG

在解决问题之前,我们准备一下关于SVG的知识:

/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) {puteBounds(mRect, true);if (pare(mLeft, defaultValue) == 0) {mLeft = mRect.left;} else {mLeft = Math.min(mLeft, mRect.left);}if (pare(mRight, defaultValue) == 0) {mRight = mRect.right;} else {mRight = Math.max(mRight, mRect.right);}if (pare(mTop, defaultValue) == 0) {mTop = mRect.top;} else {mTop = Math.min(mTop, mRect.top);}if (pare(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();puteBounds(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、参考文献
Android View重绘和更新常用的方法

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。