写一个SVGView,并上传到Maven上

上文主要写了自定义View的一些基础,这篇文章主要自定义了SVGView,也算是对上篇文章的巩固,事件的起因是开发APP的时候有一个人体图,可以标注出各个区域的疼痛程度,所以第一时间想到了使用SVG,于是查询了网上不少的样例,但是或多或少都有一些问题,这里自己写了一个,支持Raw文件和File加载,支持区分SVG不同区域点击,支持移动,缩放,翻转

源码

源码点击这里,点这里......

引入

implementation 'io.github.zhaojfGithub.SVGView:SVGView:0.0.3

使用

SVGIdinteger使用svg的文件id
SVGScalefloat设置初始缩放倍数
SVGBackgroundcolor设置控件背景
SVGColorcolor设置区域默认颜色
SVGLineColorcolor设置分割线默认颜色
SVGIsMoveboolean是否启用滑动动能
SVGMoveSpeedfloat设置滑动的速度,越小越快
SVGIsZoomboolean是否启用缩放功能
SVGZoomSpeedfloat设置缩放的速度,越小越快

虽然我也觉得越小越快是反人类设计,这个以后再改

Java调用

val svgView = findViewById<SVGView>(R.id.SVGView)
svgView.zoomSpeed = 1F
svgView.moveSpeed = 3F
svgView.setOnClickListener(SVGView.OnSVGClickListener {
    if (it.select) {
        it.color = Color.RED
    } else {
        it.color = Color.BLUE;
    }
})

还是挺简单的,这里的select是点击修改后才传递进来的,所以只需要操作设置选中时什么样子和未选中是什么样子就行

实现效果

这里原本想放那个人体图了,但是公司的东西,考虑一下,自己随便画了一个SVG图,

demonstration.gif

总体概览

在思考把这个View上传上去的时候,就很充分的考虑了扩展的情况,谁也保证不了用户想区分区域一定要加一个TAG,万一想加一个XXOO呢,所以在设计之初,就把文件的解析,实体类的建造给了一个单独的类,这里有一个SVGHelpInterface的接口SVGHelpImpl实现类,一般来说有自己独特的想法只需要继承SVGHelpImpl就可以了,来看一眼接口内容

List<PathBean> deCodeSVG(Context context, Integer SVGId, File file, @ColorInt Integer color) throws IOException;

RectF getSVGRecF(List<PathBean> list);

PathBean getPathBean(Integer id, String tag, Path path, Integer color);

void onDraw(PathBean pathBean, Canvas canvas, Paint paint, @ColorInt Integer LineColor);

Boolean isClick(PathBean pathBean, Float x, Float y);

可以看到,继续把所有能摘出来的逻辑都拿出来了,什么还不够你玩?建议去继承View,然后全部重写

  • deCodeSVG:这里主要负责解析SVG图片,然后生成对应的实体
  • getSVGRecF:这里就是算这个图片的实际大小的,然后配合在ViewonMeasure方法中定义View的大小
  • getPathBean:这个呢生成一个实体,如果你有自己的实体,不想使用自带的,那么就重写这个方法
  • onDraw:把内容画出来,和View的onDraw一个意思,不过它对应只是一个区域。即一个PathBean
  • isClick:判断点击区域是否在对应区域

具体怎么实现的可以看源码 上面很多地方都使用到了PathBean,ok,PathBean是什么勒,即存储SVG path分割的信息,先看SVG的信息

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="800dp"
    android:height="600dp"
    android:viewportWidth="800"
    android:viewportHeight="600">
  ···
  <path
      android:strokeWidth="1"
      android:pathData="M93,158h55v266h-55z"
      android:fillColor="#fff"
      android:strokeColor="#000"/>
  ···
</vector>

PathBean对应就是SVG内的path标签,然后根据pathData去画出来,对吧很简单,看一下构造函数

public PathBean(Integer id, String tag, Path path, @ColorInt Integer color, Boolean isSelect) {
    RectF rectF = new RectF();
    path.computeBounds(rectF, true);
    Region region = new Region();
    Rect rect = new Rect((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom);
    region.setPath(path, new Region(rect));

    this.id = id;
    this.tag = tag;
    this.path = path;
    this.rectF = rectF;
    this.region = region;
    this.color = color;
    this.isSelect = isSelect;
}

这里主要解释一下Region的作用和Path的作用和区别,为什么在这里解释,因为我在写View的时候我也不知道

  • Region 类用于表示一个二维平面上的矩形区域。它可以用来描述一个区域的边界、形状或位置,并进行相应的操作,如合并、交集、差集、补集等。Region 类的主要作用是进行图形的区域运算和判断。
  • Path 类用于描述和绘制图形的路径。它可以包含直线、曲线、圆弧等不同的线段和曲线段,从而构成复杂的形状和轮廓。
  • OK 明白,Path就是画出这个区域出来的,Region就是判断是不是在点击区域内的

View设计

这里呢就需要上文View基础的内容了

获取XML信息 1686558857430.jpg 计算View大小:

1686558918354.jpg

onDraw:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (PathList.isEmpty() || canvas == null || originRecF == null) {
        return;
    }
    canvas.save();
    canvas.drawColor(svgBackground);
    //默认先移动到中间
    canvas.translate((getWidth() - originRecF.width() * scale) / 2, (getHeight() - originRecF.height() * scale) / 2);
    canvas.translate(lastMoveX, lastMoveY);
    //处理手势位移
    canvas.translate(moveX, moveY);
    canvas.scale(scale, scale);
    for (int i = 0; i < PathList.size(); i++) {
        svgHelp.onDraw(PathList.get(i), canvas, paint, lineColor);
    }
    canvas.restore();
}

首先是位移到View的中间,为什么,因为你写个View还是,然后处理手势的位移操作,然后再处理缩放,最后通过SVGHelp的onDraw一个一个画出来,至此,一个基本的显示就完成了,接下来处理事件分发。

事件分发

说到事件分发,那就是需要在onTouchEvent来做操作了, 这里使用的是官方GestureDetector.OnGestureListener实现的点击 estureDetector.SimpleOnGestureListener实现的位移,至于为什么不在GestureDetector.OnGestureListener把位移一并处理了,因为要区分点击,位移操作,这里分开写。ScaleGestureDetector.SimpleOnScaleGestureListener实现的缩放

看一下如何实现:

@Override
public boolean onTouchEvent(MotionEvent event) {
    gestureClick.onTouchEvent(event);
    int pointerCount = event.getPointerCount();
    if (pointerCount == 1) {
        gestureMove.onTouchEvent(event);
    } else {
        gestureZoom.onTouchEvent(event);
    }
    if (event.getAction() == MotionEvent.ACTION_UP) {
        if (moveNumber > MAX_MOVE_NUMBER) {
            //只有经历了滑动才记录
            lastMoveX += moveX;
            lastMoveY += moveY;
            moveX = 0F;
            moveY = 0F;
        }
        moveNumber = 0;
    }
    return true;
}

第一步注册点击事件,不做区分,第二步获取了触摸屏幕的手指数量,如果只一根手指,那么就事件就给到位移操作,如果是多根就给到位移操作,再往下是记录之前位置的距离,以方便在继续位置,不处理就会每次从中间开始,值得注意的是有一个moveNumber > MAX_MOVE_NUMBER的阈值判断,在实际测试的过程中,发现缩放操作中间会断触,导致突然跳到了位移,以至于发生了大批量的位移操作,这里设置了一个阈值,主要就是为了防止这类事件的发生,return true说明这个事件我已经处理过了,不用管了

点击处理

@Override
public boolean onSingleTapUp(@NonNull MotionEvent e) {
    boolean result = false;
    for (PathBean pathBean : PathList) {
        float x = (e.getX() - (getWidth() - originRecF.width() * scale) / 2 - lastMoveX) / scale;
        float y = (e.getY() - (getHeight() - originRecF.height() * scale) / 2 - lastMoveY) / scale;
        if (svgHelp.isClick(pathBean, x, y)) {
            pathBean.setSelect(!pathBean.getSelect());
            if (onClickListener != null) {
                onClickListener.onClick(pathBean);
            }
            result = true;
            invalidate();
        }
    }
    return result;
}

此方法为点击处理,这里值得关注的只有当前坐标转换为原本坐标的计算,因为我找不到更好的办法,只能这样咯 首先减去了位移到居中的距离,然后再减去手指位移的距离最后除以缩放倍数,计算出原本对应坐标,然后判断在不在这个区域内

位移操作

@Override
public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
    if (!isMove) {
        return false;
    }
    if (moveNumber <= MAX_MOVE_NUMBER) {
        moveNumber++;
        return true;
    }
    float deltaX = (e2.getX() - e1.getX()) / moveSpeed;
    float deltaY = (e2.getY() - e1.getY()) / moveSpeed;
    moveX = deltaX;
    moveY = deltaY;
    invalidate();
    return true;
}

额,好像没什么可以解释的们主要是还是这个阈值,对应onTouchEvent方法的处理,然后通过手指位移距离和设置的位移速度,得出需要的位移距离,嗯 简单

缩放操作

@Override
public boolean onScale(@NonNull ScaleGestureDetector detector) {
    if (!isZoom) {
        return false;
    }
    float scaleGap = (detector.getScaleFactor() - lastScale) / zoomSpeed;
    //因为如果scale为负数,会造成图像倒转,到0会看不到,故设置为0.1
    if (scale + scaleGap > 0.1) {
        scale += scaleGap;
    }
    invalidate();
    return true;
}

在实际的始终有一次,缩放缩多了,天啊撸,图像竟然翻转了过来,原因是缩放scale变成了辅助,造成了画布翻转,然后就变成倒着显示了,这里主要做一下限制,最小倍数为1。

结束

至此基本方法就全部结束了,怎么说呢,还是第一次写一个比较完整的View,因为之前写的没有这么规范,有兴趣可以看一下源码,其中有不合适的地方,希望大家批评指正

想要上传到maven步骤可以看下一篇文章# 写一个SVGView,并上传到Maven(下)

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务