需求:做小型地图的一个显示功能
实现思路:自定义view解析对应的svg文件,在利用canvas的画笔画出相应的地图线条。各个线条围成的区域可以表示成各个元素比如树,街道,路灯,汽车等等一切事物。然后通过view的点击事件可以给自定义控件添加点击,长按,放大缩小等等操作。
需要美工给一个svg格式的图片。
//当然这里并不止一个path路径,这里的path路径就是地图的线条
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="3000dp"
android:height="3000dp"
android:viewportWidth="3000"
android:viewportHeight="3000">
<path
android:dataName="CCCC"
android:cabinId="9a43312887844fa3a2815dfac018f6ee"
android:pathData="M7,2297H157v60H7v-60Z"
android:strokeWidth="8.333"
android:fillType="evenOdd"
android:strokeColor="#000"/>
</vector>
path标签内可存放一切你想要存放的信息,可以是表示一棵树,一栋房子都随意,只需要按照相应的格式解析出来存放到对象内就行了。
//控件自适应请重写onMeasure方法,我这里是直接全屏了。
public class MapView extends View {
private Paint paint;
private Context mContext;
private int[] colors = new int[]{Color.BLUE,Color.CYAN,Color.YELLOW,Color.GREEN};
private List<MapPathItem> pathItemList = new ArrayList<>();
GestureDetector gestureDetector;
ScaleGestureDetector scaleGestureDetector;
//双指操作下的中心位置
float focusX = 0,focusY = 0;
float posX = 0, posY = 0;
int viewWidth, viewHeight;
//是否初始化了
boolean hasInitViewSize;
float widthScale, heightScale;
float scaleFactor = 1.0f;
private int imgWidth = 3000,imgHeight=3000;
private MapPathItem selectPathItem;
//地图最大的矩阵
private RectF mapRectF;
private List<UserLocationInf> users;
private List<MapPathItem> usersLocationInfo;
private Bitmap userLocationIcon;
private itemClickListener itemClickListener;
public MapView(Context context) {
super(context);
}
public MapView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
this.mContext = context;
this.paint = new Paint();
users = new ArrayList<>();
usersLocationInfo = new ArrayList<>();
userLocationIcon = BitmapFactory.decodeResource(getResources(),R.drawable.avtar_icon);
gestureDetector = new GestureDetector(context, new MySimpleOnGestureDetector());
scaleGestureDetector = new ScaleGestureDetector(context, new MySimpleScaleOnGestureDetector());
thread.start();
}
//双指缩放
class MySimpleScaleOnGestureDetector extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = scaleFactor < 1 ? (float) 1 : scaleFactor > 3 ? 3 : scaleFactor;
focusX = detector.getFocusX();
focusY = detector.getFocusY();
invalidate();
return true;
}
}
//单指移动
class MySimpleOnGestureDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
posX -= distanceX;
posY -= distanceY;
invalidate();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//点击区域是舱段区域才触发listener
for (MapPathItem item:pathItemList){
if(item.isTouch(e.getX()-posX,e.getY()-posY,scaleFactor)){
onTouch(e.getX(),e.getY());
}
}
return super.onSingleTapConfirmed(e);
}
}
public void initSize() {
viewWidth = getWidth();
viewHeight = getHeight();
if (viewWidth < 0 && viewHeight < 0) {
return;
}
hasInitViewSize = true;
widthScale = viewWidth / imgWidth;
heightScale = viewHeight / imgHeight;
scaleFactor = Math.min(widthScale, heightScale);
scaleFactor = 1f;
posX = viewWidth / 2 - imgWidth / 2;
posY = viewHeight / 2 - imgHeight / 2;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
scaleGestureDetector.onTouchEvent(event); //双指缩放
gestureDetector.onTouchEvent(event); //单指移动
//点击区域事件
return true;
}
@RequiresApi(api = Build.VERSION_CODES.N)
public void refreshUserLocation(List<UserLocationInf> users){
usersLocationInfo.clear();
Log.e("users", String.valueOf(users.size()));
for (MapPathItem item:pathItemList){
users.forEach(userLocationInf -> {
if(item.getCabinId().equals(userLocationInf.getCabinId())){
usersLocationInfo.add(item);
}
});
}
Log.e("usersLocationInfosize", String.valueOf(usersLocationInfo.size()));
invalidate();
}
private void onTouch(float x, float y) {
for (MapPathItem item:pathItemList){
if(item.isTouch(x-posX,y-posY,scaleFactor)){
selectPathItem = item;
}
}
//从新绘制
if(selectPathItem!=null){
//设置点击事件
//ToastShow.showText(getContext(),"点击了item"+selectPathItem.getCabinId());
if(itemClickListener!=null){
itemClickListener.itemClick(selectPathItem);
}
Log.e("选中",selectPathItem.toString());
invalidate();
}
}
public interface itemClickListener{
void itemClick(MapPathItem selectPathItem);
}
private void checkBounds() { //检查边界
if (scaleFactor > widthScale) {
posX = Math.min(posX, (scaleFactor - 1) * (imgWidth / 2));
posX = Math.max(posX, viewWidth - imgHeight*scaleFactor - (scaleFactor - 1) * (imgWidth / 2));
} else {
posX = Math.max(posX, (scaleFactor - 1) * (imgWidth / 2));
posX = Math.min(posX, viewWidth - imgWidth*scaleFactor - (scaleFactor - 1) * (imgWidth / 2));
}
if (scaleFactor > heightScale) {
posY = Math.min(posY, (scaleFactor - 1) * (imgHeight / 2));
posY = Math.max(posY, viewHeight - imgHeight*scaleFactor - (scaleFactor - 1) * (imgHeight / 2));
} else {
posY = Math.max(posY, (scaleFactor - 1) * (imgHeight / 2));
posY = Math.min(posY, viewHeight - imgHeight*scaleFactor - (scaleFactor - 1) * (imgHeight / 2));
}
}
//子线程加载svg
private Thread thread = new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = mContext.getResources().openRawResource(R.raw.cabins);
//数据流转化为path DocumentBuilderFactory dom解析工厂
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(inputStream);
//根节点
Element documentElement = document.getDocumentElement();
String attribute = documentElement.getAttribute("android:viewportWidth");
Log.e("viewportWidth",attribute);
//子节点
NodeList nodeList = documentElement.getElementsByTagName("path");
//svg的上下左右边界
float left = -1,top = -1,right = -1,bottom = -1;
//解析子节点属性
for(int i = 0;i <nodeList.getLength(); i ++){
Element element = (Element)nodeList.item(i);
//拿到路径
String pathData = element.getAttribute("android:pathData");
String dataName = element.getAttribute("android:dataName");
String cabinId = element.getAttribute("android:cabinId");
Path path = PathParser.createPathFromPathData(pathData);
MapPathItem pathItem = new MapPathItem(path);
pathItem.setDataName(dataName);
pathItem.setCabinId(cabinId);
pathItem.setColor(Color.BLACK);
pathItemList.add(pathItem);
RectF rectF = new RectF();
path.computeBounds(rectF,true);
left = left ==-1?rectF.left:Math.min(rectF.left,left);
top = top ==-1?rectF.top:Math.min(rectF.top,top);
right = right ==-1?rectF.right:Math.min(rectF.right,right);
bottom = bottom ==-1?rectF.bottom:Math.min(rectF.bottom,bottom);
}
mapRectF = new RectF(left,top,right,bottom);
measure(getMeasuredWidth(),getMeasuredHeight());
handler.sendEmptyMessage(1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
@SuppressLint("HandlerLeak")
private Handler handler = new Handler(){
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
invalidate();
}
};
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!hasInitViewSize) {
initSize();
}
checkBounds();
canvas.translate(posX,posY);
//canvas.scale(scaleFactor, scaleFactor, focusX-posX, focusY-posY);
canvas.scale(scaleFactor,scaleFactor);
for (MapPathItem pathItem : pathItemList) {
pathItem.drawItem(canvas,paint,false);
}
if(selectPathItem!=null){
Log.e("sadasd","重绘");
selectPathItem.drawItem(canvas,paint,true);
}
if(usersLocationInfo.size()!= 0 ){
usersLocationInfo.forEach(user->{
user.drawUserIcon(canvas,paint,userLocationIcon);
});
}
}
public void setItemClickListener(MapView.itemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
}
public class MapPathItem {
//画笔路径
private Path path;
private int color;
private String dataName;
private String cabinId;
public MapPathItem(Path path) {
this.path = path;
}
public void setColor(int color) {
this.color = color;
}
public void drawItem(Canvas canvas, Paint paint,boolean isSelect){
if(isSelect){
//画边框
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
paint.setShadowLayer(6,0,0,Color.RED);
paint.setColor(Color.BLACK);
canvas.drawPath(path, paint);
//填充色
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(4);
}else {
//清除阴影
paint.clearShadowLayer();
//画边框
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawPath(path, paint);
//填充色
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
}
canvas.drawPath(path, paint);
}
public void drawUserIcon(Canvas canvas, Paint paint, Bitmap b) {
RectF rectF = new RectF();
path.computeBounds(rectF,true);
Rect src=new Rect(0,0,b.getWidth(),b.getHeight());
canvas.drawBitmap(b,src,rectF,paint);
}
/**
* 判断当前区域是否选中
* @param x
* @param y
* @param scaleFactor 缩放倍数
* @return
*/
public boolean isTouch(float x,float y,float scaleFactor){
RectF rectF = new RectF();
path.computeBounds(rectF,true);
//区域
Region region = new Region();
region.set((int)(rectF.left*scaleFactor),(int)(rectF.top*scaleFactor),(int)(rectF.right*scaleFactor),(int)(rectF.bottom*scaleFactor));
return region.contains((int)x,(int)y);
}
public String getDataName() {
return dataName;
}
public void setDataName(String dataName) {
this.dataName = dataName;
}
public String getCabinId() {
return cabinId;
}
public void setCabinId(String cabinId) {
this.cabinId = cabinId;
}
}