基于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 还是要比自己画要好看不少。

相关推荐
666xiaoniuzi42 分钟前
深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp
android·c语言·数据库
沐言人生5 小时前
Android10 Framework—Init进程-8.服务端属性文件创建和mmap映射
android
沐言人生6 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
沐言人生6 小时前
Android10 Framework—Init进程-7.服务端属性安全上下文序列化
android·android studio·android jetpack
追光天使6 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru6 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数7 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数7 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc7 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜8 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility