上文首要写了自定义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图,

写一个SVGView,并上传到Maven(上)

整体概览

在考虑把这个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信息

写一个SVGView,并上传到Maven(上)
计算View大小:

写一个SVGView,并上传到Maven(上)

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(下)