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() {
@Override
public 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;
}
@Override
protected Void doInBackground(Void... voids) {
mConfig = getMockMapConfig(mContext.get());
mData = getMockMapData(mContext.get());
return null;
}
@Override
protected 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(