写一个SVGView,并上传到Maven上

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

源码

源码点击这里,点这里……

引入

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

使用

SVGId integer 使用svg的文件id
SVGScale float 设置初始缩放倍数
SVGBackground color 设置控件背景
SVGColor color 设置区域默认颜色
SVGLineColor color 设置分割线默认颜色
SVGIsMove boolean 是否启用滑动动能
SVGMoveSpeed float 设置滑动的速度,越小越快
SVGIsZoom boolean 是否启用缩放功能
SVGZoomSpeed float 设置缩放的速度,越小越快

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

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,因为之前写的没有这么规范,有兴趣可以看一下源码,其中有不合适的地方,希望大家批评指正

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYybnuQS' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片