基于path来定义多个不规则的组合view同时设置处理点击事件

话说,今天应该写协程相关的坑的,但是下午两个开始写,写到3点半还没有写完,然后就出去骑自行车去了。sorry啊,但是这个写笔记又是一个不能断更的工程,所以找一个简单的写,估计晚上11点开写,然后12点可以卡一个最后时间吧。

不废话了,开整。

正文

我们还是先来分析一下业务诉求,我们要自定义一个基于path 的view,然后还要自定义点击事件,那么最好的例子是什么呢?那就是中国地图,比如点击某省高亮变颜色啥的。

因为中国版图更新了海域相关位置,但是我这地图path代码还是前年的,所以就不贴效果图了。sorry啊

我们还是先按照惯例,分析一下具体实现的需要处理的逻辑。

  • 中国地图的svg 中的path 绘制区域一定是和我们自定义view的高宽不一致的,所以我们需要一个缩放,而缩放就两个方向,一个canvas 进行缩放,一个是通过设置matrix 进行缩放,对单纯的缩放而言没有多少区别。

    • 这个逻辑里面就涉及到了view的高宽的确定,就是 onMeasure 对吧。但是我们在这里是需要一个缩放倍数的啊,缩放倍数来源于地图的实际大小,但是我们调用postInvalidate之前,手动调用一下onMeasure重新测量,所以可以通过一些逻辑搞定onMeasure。
  • 然后就是文件IO了,同时还有一个xml 解析。

  • 解析出来的xml 中的path 字符串 如何转化为path 对象

  • 如何基于path原生对象同时结合onMeasure 中获取到的宽高算出缩放比例。

  • 如何绘制这些path

  • 如何处理点击事件,及其判断用户点击的是哪个省

当我们搞清楚了这些东西之后,我们就可以直接梭哈,开始撸代码了。先说明一下:

画中国地图的爱国青年数不胜数,所以,这种类型的笔记或者blog 也到处都是,因为我也是抄别人的blog,然后自己写了一遍,然后自己再做一遍笔记,仅此而已,所以代码的重复率特别高,思路都是这些思路,只是在于先看到谁的而已。

设定并获取自定义view的宽高

既然是写demo,通常就是全屏了啊。那么代码就可以是这个样子的。我们在这里主要是为了获得缩放倍数,那么可以假定我们已经知道了地图的原始宽高,并且定义成了一个RectF。

arduino 复制代码
 private RectF viewRectF;

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (viewRectF!=null){
        int width = MeasureSpec.getSize(widthMeasureSpec);
        scale=(float)width/(viewRectF.width());
    }
}

svg读取并解析

读取并不难,就是io,解析也只是xml,但是我们一个地图svg是多个path ,一个省一个path,所以我们就需要考虑,如何存储和想要存储的数据。

定义模型存储省所在的path

arduino 复制代码
public class MapChildCanvas {
    Path path;// 路径
    int drawColor;// 绘制颜色
    String id;
    String cityName;
    }

我们还是按照怎么简单怎么来,存储了省path,id ,名称,然后定义了一个省地图的填充颜色。

io及其解析

数据模型定义好了,那么我们就开整,把svg解析成我们需要的MapChildCanvas 列表。

先定义类成员变量:

ini 复制代码
List<MapChildCanvas> childCanvas=new ArrayList<>();
int [] colors={Color.BLUE,Color.RED,Color.GREEN,Color.YELLOW,Color.BLACK};

然后是文件IO 及其解析

ini 复制代码
InputStream inputStream = getResources().openRawResource(R.raw.china);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
childCanvas.clear();
try {
    builder = factory.newDocumentBuilder();
    Document document = builder.parse(inputStream);
    //获取节点
    Element element = document.getDocumentElement();
    NodeList pathElements = element.getElementsByTagName("path");
    // 获取节点
    for (int i = 0; i < pathElements.getLength(); i++) {
        // 获取path 节点
        Element item = (Element) pathElements.item(i);
        String path = item.getAttribute("d");
        String title = item.getAttribute("title");
        String id = item.getAttribute("id");
        Log.d(TAG, "run: " + title + "  " + id);
        //转换为显示 路径
        Path pathData = PathParser.createPathFromPathData(path);
        MapChildCanvas canvas=new MapChildCanvas(pathData);
        canvas.setId(id);
        canvas.setCityName(title);
        canvas.setDrawColor(colors[i%colors.length]);
        childCanvas.add(canvas);
    }

} catch (Exception e) {
    e.printStackTrace();
}

计算出svg原始的尺寸

可以看到,上面代码,我们把每个省的数据都拿到了,但是我们并没有将所有的path 所需要的矩形空间计算出来,为什么需要计算这个,因为我们要地图完整的显示出来,这也是我们计算缩放倍数的原因。

ini 复制代码
builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream);
//获取节点
Element element = document.getDocumentElement();
NodeList pathElements = element.getElementsByTagName("path");
float left=-1;
float bottom=-1;
float right=-1;
float top=-1;
// 获取节点
for (int i = 0; i < pathElements.getLength(); i++) {
    // 获取path 节点
    Element item = (Element) pathElements.item(i);
    String path = item.getAttribute("d");
    String title = item.getAttribute("title");
    String id = item.getAttribute("id");
    Log.d(TAG, "run: " + title + "  " + id);
    //转换为显示 路径
    Path pathData = PathParser.createPathFromPathData(path);
    // 获取到路径的边界,这个主要是获取需要绘制的区域。
    RectF rectF=new RectF();
    pathData.computeBounds(rectF,true);
    // 将值扩展到最外层 view
    left=left==-1?rectF.left:Math.min(rectF.left,left);
    bottom=bottom==-1?rectF.bottom:Math.max(rectF.bottom,bottom);
    right=right==-1?rectF.right:Math.max(rectF.right,right);
    top=top==-1?rectF.top:Math.min(rectF.top,top);
    // 子类 赋值
    MapChildCanvas canvas=new MapChildCanvas(pathData);
    canvas.setId(id);
    canvas.setCityName(title);
    canvas.setDrawColor(colors[i%colors.length]);
    childCanvas.add(canvas);
}
// 发送重新测量
viewRectF = new RectF(left,top,right,bottom);
measure(getMeasuredWidth(),getMeasuredHeight());
postInvalidate();

可以看到,我们将viewRectF已经创建出来了。这个属性矢量图的同学就很容易理解上面的获取代码,整体就是基于将path的上下左右的极端值作为矩形,同时svg 是严格的按照路径的绘制点,所以他的点就是真实的点,这就是具体的效果,没有中间计算环节。

开始绘制

我们知道绘制path,这个显示效果全靠画笔对象,但是我们点击了和未点击还是有点区别,而且不同的省我们定义的颜色不一样,那么画笔在Demo阶段就在画的时候配置。那么就开始绘制。因为我们上面已经有一个 childCanvas 了,所以需要遍历这个列表,遍历代码就补贴了,直接上绘制省的代码:

scss 复制代码
if (select){
    paint.clearShadowLayer();
    paint.setStrokeWidth(2);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(Color.WHITE);
    paint.setShadowLayer(0,0,0,0xffffff);
    canvas.drawPath(path,paint);
    // 描边
    paint.setStyle(Paint.Style.STROKE);
    paint.setColor(Color.BLACK);
    canvas.drawPath(path,paint);
}else {
    //未选中的时候
    paint.setStrokeWidth(1);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(drawColor);
    canvas.drawPath(path,paint);
    // 描边
    paint.setStyle(Paint.Style.STROKE);
    paint.setColor(Color.BLACK);
    canvas.drawPath(path,paint);
}

点击处理

我们先来看计算点击的x,y 是否在path 里面的处理代码:

java 复制代码
public boolean isTouch(float x,float y){
    //创建一个矩形
    RectF rectF = new RectF();
    //获取到当前省份的矩形边界
    path.computeBounds(rectF, true);
    //创建一个区域对象
    Region region = new Region();
    //将path对象放入到Region区域对象中
    region.setPath(path, new Region((int)rectF.left, (int)rectF.top,(int)rectF.right, (int)rectF.bottom));
    //返回是否这个区域包含传进来的坐标
    return region.contains((int)x,(int)y);
}

这个很单纯,直接复制即可。所以我们只需要拿到这个点击事件的event.getX(),event.getY(),然后循环刚刚的childCanvas 判断是否在区域内部就行了。

结束

代码地址。其实写到很简单,比如很多知识点也没有详细的挖掘。但是基于path去自定义出来的view 还是要比自己画要好看不少。

相关推荐
子非衣3 小时前
MySQL修改JSON格式数据示例
android·mysql·json
openinstall全渠道统计6 小时前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
双鱼大猫7 小时前
一句话说透Android里面的ServiceManager的注册服务
android
双鱼大猫7 小时前
一句话说透Android里面的查找服务
android
双鱼大猫7 小时前
一句话说透Android里面的SystemServer进程的作用
android
双鱼大猫7 小时前
一句话说透Android里面的View的绘制流程和实现原理
android
双鱼大猫8 小时前
一句话说透Android里面的Window的内部机制
android
双鱼大猫8 小时前
一句话说透Android里面的为什么要设计Window?
android
双鱼大猫8 小时前
一句话说透Android里面的主线程创建时机,frameworks层面分析
android
苏金标9 小时前
android 快速定位当前页面
android