├── .gitignore ├── README.md ├── SVGView ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── java │ └── com │ │ └── zjf │ │ └── svgview │ │ ├── PathBean.java │ │ ├── SVGHelpImpl.java │ │ ├── SVGHelpInterface.java │ │ └── SVGView.java │ └── res │ └── values │ └── attr.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zjf │ │ └── svgdemo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zjf │ │ │ └── svgdemo │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ └── ic_square.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── zjf │ └── svgdemo │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── images └── demonstration.gif ├── publish.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | /bulid 5 | .gradle 6 | .idea 7 | /local.properties 8 | 9 | /gradle 10 | 11 | # Log file 12 | *.log 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | replay_pid* 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVGView 2 | 3 | 一款处理复杂不规则图形的View,支付缩放,点击,变色操作,保留扩展功能,后续有时间会根据反馈进行迭代 4 | 5 | ### 引入 6 | 7 | ```groovy 8 | implementation 'io.github.zhaojfGithub.SVGView:SVGView:0.0.3' 9 | ``` 10 | 11 | ### 效果图 12 | ![默认](images/demonstration.gif) 13 | 14 | ### XML属性 15 | 16 | | Attributes | format | describe | 17 | |:-------------:|---------|--------------| 18 | | SVGId | integer | 使用svg的文件id | 19 | | SVGScale | float | 设置初始缩放倍数 | 20 | | SVGBackground | color | 设置控件背景 | 21 | | SVGColor | color | 设置区域默认颜色 | 22 | | SVGLineColor | color | 设置分割线默认颜色 | 23 | | SVGIsMove | boolean | 是否启用滑动动能 | 24 | | SVGMoveSpeed | float | 设置滑动的速度,约小越快 | 25 | | SVGIsZoom | boolean | 是否启用缩放功能 | 26 | | SVGZoomSpeed | float | 设置缩放的速度,约小越快 | 27 | 28 | ### 简单使用 29 | 30 | 1. 给UI要一份SVG图 31 | 2. 通过Android studio 转成xml 32 | 3. 创建一个raw文件夹,如果没有就创建一个,和res同级目录 33 | 4. xml引入,当然也可以通过Java引入,其中SVGId是必须的 34 | 5. 也支持File引入,通过Java调用setSVGFile方法 35 | 36 | ```xml 37 | 38 | 45 | ``` 46 | 处理点击区域 47 | ```kotlin 48 | val svgView = findViewById(R.id.SVGView) 49 | svgView.zoomSpeed = 1F 50 | svgView.moveSpeed = 3F 51 | svgView.setOnClickListener(SVGView.OnSVGClickListener { 52 | if (it.select) { 53 | it.color = Color.RED 54 | } else { 55 | it.color = Color.BLUE 56 | } 57 | }) 58 | ``` 59 | 60 | 至此设置完毕了 61 | 62 | ### 点击额外设置 63 | 64 | 因为SVG转化XML是由一个一个path组成的,假如我想要知道我点击的区域是那一部分,那么就需要加一个TAG,用来标识所点击的区域,例如: 65 | 66 | ```xml 67 | 68 | 75 | ``` 76 | 77 | 其中 android:tag="这是一个标识"是必须的,如果想使用其他标识可以重写SVGHelpImpl的deCodeSVG方法, 78 | 79 | ### 点击回调处理 80 | 81 | 回调默认返回的是PathBean,如果需要添加自定义字段,请继承PathBean,然后重写SVGHelpImpl的getPathBean方法 82 | 83 | 关于更多可以查看源码,代码是最好的老师 -------------------------------------------------------------------------------- /SVGView/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle -------------------------------------------------------------------------------- /SVGView/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | ext { 6 | PUBLISH_ARTIFACT_ID = "SVGView" 7 | } 8 | 9 | apply from: '../publish.gradle' 10 | 11 | android { 12 | namespace 'com.zjf.svgview' 13 | compileSdk 33 14 | 15 | defaultConfig { 16 | //applicationId "com.zjf.svgview" 17 | minSdk 16 18 | targetSdk 33 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation 'androidx.appcompat:appcompat:1.6.1' 39 | } 40 | -------------------------------------------------------------------------------- /SVGView/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /SVGView/src/main/java/com/zjf/svgview/PathBean.java: -------------------------------------------------------------------------------- 1 | package com.zjf.svgview; 2 | 3 | import android.graphics.Path; 4 | import android.graphics.Rect; 5 | import android.graphics.RectF; 6 | import android.graphics.Region; 7 | 8 | import androidx.annotation.ColorInt; 9 | 10 | /** 11 | * @author zjf 12 | * @date 2023/5/29 13 | */ 14 | public class PathBean { 15 | private Integer id; 16 | private String tag; 17 | 18 | private Path path; 19 | private RectF rectF; 20 | private Region region; 21 | @ColorInt 22 | private Integer color; 23 | 24 | private Boolean isSelect; 25 | 26 | public PathBean(Integer id, String tag, Path path, @ColorInt Integer color, Boolean isSelect) { 27 | RectF rectF = new RectF(); 28 | path.computeBounds(rectF, true); 29 | Region region = new Region(); 30 | Rect rect = new Rect((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom); 31 | region.setPath(path, new Region(rect)); 32 | 33 | this.id = id; 34 | this.tag = tag; 35 | this.path = path; 36 | this.rectF = rectF; 37 | this.region = region; 38 | this.color = color; 39 | this.isSelect = isSelect; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "PathBean{" + 45 | "id=" + id + 46 | ", tag='" + tag + '\'' + 47 | ", path=" + path + 48 | ", rectF=" + rectF + 49 | ", region=" + region + 50 | ", color=" + color + 51 | ", isSelect=" + isSelect + 52 | '}'; 53 | } 54 | 55 | public Integer getId() { 56 | return id; 57 | } 58 | 59 | public void setId(Integer id) { 60 | this.id = id; 61 | } 62 | 63 | public String getTag() { 64 | return tag; 65 | } 66 | 67 | public void setTag(String tag) { 68 | this.tag = tag; 69 | } 70 | 71 | public Path getPath() { 72 | return path; 73 | } 74 | 75 | public void setPath(Path path) { 76 | this.path = path; 77 | } 78 | 79 | public Boolean getSelect() { 80 | return isSelect; 81 | } 82 | 83 | public void setSelect(Boolean select) { 84 | isSelect = select; 85 | } 86 | 87 | public RectF getRectF() { 88 | return rectF; 89 | } 90 | 91 | public void setRectF(RectF rectF) { 92 | this.rectF = rectF; 93 | } 94 | 95 | public Region getRegion() { 96 | return region; 97 | } 98 | 99 | public void setRegion(Region region) { 100 | this.region = region; 101 | } 102 | 103 | public Integer getColor() { 104 | return color; 105 | } 106 | 107 | public void setColor(@ColorInt Integer color) { 108 | this.color = color; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SVGView/src/main/java/com/zjf/svgview/SVGHelpImpl.java: -------------------------------------------------------------------------------- 1 | package com.zjf.svgview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.RectF; 8 | import android.os.Build; 9 | 10 | import androidx.annotation.ColorInt; 11 | import androidx.core.graphics.PathParser; 12 | 13 | import org.w3c.dom.Document; 14 | import org.w3c.dom.Element; 15 | import org.w3c.dom.NodeList; 16 | 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.nio.file.Files; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import javax.xml.parsers.DocumentBuilder; 26 | import javax.xml.parsers.DocumentBuilderFactory; 27 | 28 | /** 29 | * @author zjf 30 | * @date 2023/5/29 31 | * 处理SVG图片相关的类,支持继承重写,然后重新赋值给SVGView 32 | */ 33 | public class SVGHelpImpl implements SVGHelpInterface { 34 | 35 | /** 36 | * 解析SVG文件,如果标注Path不为android:tag或者自定义了实体类 需要重写此方法 37 | * @param context 上下文 38 | * @param SVGId RAW文件夹的图片ID,可为null 39 | * @param file 图片路径 可为null, 40 | * @param color 住背景颜色 41 | * @return 如果SVGId与file都为null,return null,否则return正常图片解析结果 42 | */ 43 | @Override 44 | public List deCodeSVG(Context context, Integer SVGId, File file, @ColorInt Integer color) throws IOException { 45 | InputStream inputStream = null; 46 | List pathList = new ArrayList<>(); 47 | try { 48 | if (SVGId != null) { 49 | inputStream = context.getResources().openRawResource(SVGId); 50 | } else if (file != null && file.exists()) { 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 52 | inputStream = Files.newInputStream(file.toPath()); 53 | }else { 54 | inputStream = new FileInputStream(file); 55 | } 56 | } else { 57 | return null; 58 | } 59 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 60 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 61 | Document parse = documentBuilder.parse(inputStream); 62 | NodeList paths = parse.getDocumentElement().getElementsByTagName("path"); 63 | for (int i = 0; i < paths.getLength(); i++) { 64 | Element item = (Element) paths.item(i); 65 | if (item == null) { 66 | continue; 67 | } 68 | String pathStr = item.getAttribute("android:pathData"); 69 | String pathTag = item.getAttribute("android:tag"); 70 | Path pathData = PathParser.createPathFromPathData(pathStr); 71 | PathBean pathBean = getPathBean(i, pathTag, pathData, color); 72 | pathList.add(pathBean); 73 | } 74 | return pathList; 75 | } catch (Exception exception) { 76 | exception.printStackTrace(); 77 | } finally { 78 | assert inputStream != null; 79 | inputStream.close(); 80 | } 81 | return null; 82 | } 83 | 84 | /** 85 | * 计算原本图片占位大小,不一定是svg本身所标识的大小 86 | * @param list 解析文件 87 | * @return 图片的RecF对象 88 | */ 89 | @Override 90 | public RectF getSVGRecF(List list) { 91 | if (list == null){ 92 | return null; 93 | } 94 | float left = -1F, top = -1F, right = -1F, bottom = -1F; 95 | for (int i = 0; i < list.size(); i++) { 96 | RectF bounds = new RectF(); 97 | list.get(i).getPath().computeBounds(bounds, true); 98 | left = left == -1 ? bounds.left : Math.min(left, bounds.left); 99 | top = top == -1 ? bounds.top : Math.min(top, bounds.top); 100 | right = right == -1 ? bounds.right : Math.max(right, bounds.right); 101 | bottom = bottom == -1 ? bounds.bottom : Math.max(bottom, bounds.bottom); 102 | } 103 | return new RectF(left, top, right, bottom); 104 | } 105 | 106 | /** 107 | * 获取一个实体类,如果有自定义的实体,可以不用此方法,但是需要重写deCodeSVG 108 | */ 109 | @Override 110 | public PathBean getPathBean(Integer id, String tag, Path path, Integer color) { 111 | return new PathBean(id, tag, path, color, false); 112 | } 113 | 114 | /** 115 | * @param pathBean 数据 116 | * @param canvas 画布 117 | * @param paint 画笔 118 | * @param LineColor 分割线颜色 119 | */ 120 | @Override 121 | public void onDraw(PathBean pathBean, Canvas canvas, Paint paint, Integer LineColor) { 122 | paint.setColor(pathBean.getColor()); 123 | paint.setStyle(Paint.Style.FILL); 124 | canvas.drawPath(pathBean.getPath(), paint); 125 | //交割线 126 | paint.setStrokeWidth(1); 127 | paint.setColor(LineColor); 128 | paint.setStyle(Paint.Style.STROKE); 129 | canvas.drawPath(pathBean.getPath(), paint); 130 | } 131 | 132 | /** 133 | * 判断点击区域是是否在Path内 134 | * @param pathBean 数据 135 | * @param x X轴坐标 136 | * @param y Y轴坐标 137 | * @return true false 138 | */ 139 | @Override 140 | public Boolean isClick(PathBean pathBean, Float x, Float y) { 141 | return pathBean.getRegion().contains(x.intValue(), y.intValue()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /SVGView/src/main/java/com/zjf/svgview/SVGHelpInterface.java: -------------------------------------------------------------------------------- 1 | package com.zjf.svgview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.RectF; 8 | 9 | import androidx.annotation.ColorInt; 10 | import androidx.annotation.ColorRes; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.List; 15 | 16 | /** 17 | * @author zjf 18 | * @date 2023/5/29 19 | */ 20 | public interface SVGHelpInterface { 21 | List deCodeSVG(Context context, Integer SVGId, File file, @ColorInt Integer color) throws IOException; 22 | 23 | RectF getSVGRecF(List list); 24 | 25 | PathBean getPathBean(Integer id, String tag, Path path, Integer color); 26 | 27 | void onDraw(PathBean pathBean, Canvas canvas, Paint paint, @ColorInt Integer LineColor); 28 | 29 | Boolean isClick(PathBean pathBean, Float x, Float y); 30 | } 31 | -------------------------------------------------------------------------------- /SVGView/src/main/java/com/zjf/svgview/SVGView.java: -------------------------------------------------------------------------------- 1 | package com.zjf.svgview; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.GestureDetector; 14 | import android.view.MotionEvent; 15 | import android.view.ScaleGestureDetector; 16 | import android.view.View; 17 | 18 | import androidx.annotation.ColorInt; 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.annotation.RawRes; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | /** 29 | * @author zjf 30 | * @date 2023/5/29 31 | */ 32 | public class SVGView extends View { 33 | 34 | /** 35 | * 默认阈值,一般不做修改,太少了会不好控制滑动和缩放 36 | */ 37 | private static final int MAX_MOVE_NUMBER = 5; 38 | 39 | /** 40 | * 默认移动速度 41 | */ 42 | private static final float MOVE_INIT_SPEED = 2; 43 | 44 | /** 45 | * 默认缩放速度 46 | */ 47 | private static final float ZOOM_INIT_SPEED = 3; 48 | 49 | private OnSVGClickListener onClickListener; 50 | 51 | private SVGHelpInterface svgHelp; 52 | 53 | private Integer svgId; 54 | 55 | private File file; 56 | 57 | /** 58 | * 整体背景 59 | */ 60 | private int svgBackground; 61 | 62 | /** 63 | * 分割线颜色 64 | */ 65 | private int lineColor; 66 | 67 | private List PathList; 68 | 69 | /** 70 | * 当前缩放倍数 71 | */ 72 | private Float scale; 73 | 74 | /** 75 | * 图像的默认大小 76 | */ 77 | private RectF originRecF; 78 | 79 | private Paint paint; 80 | 81 | /** 82 | * 处理区域点击 83 | */ 84 | private GestureDetector gestureClick; 85 | 86 | /** 87 | * 处理手势移动 88 | */ 89 | private GestureDetector gestureMove; 90 | 91 | /** 92 | * 处理手势缩放 93 | */ 94 | private ScaleGestureDetector gestureZoom; 95 | 96 | /** 97 | * 区分缩放与滑动的阈值 98 | */ 99 | private int moveNumber = 0; 100 | 101 | /** 102 | * 当前位移的X轴距离 103 | */ 104 | private Float moveX = 0F; 105 | 106 | /** 107 | * 当前位移的Y轴距离 108 | */ 109 | private Float moveY = 0F; 110 | 111 | /** 112 | * 之前滑动的X轴距离(因为可能经历了多次滑动,所以需要记录一下) 113 | */ 114 | private Float lastMoveX = 0F; 115 | 116 | /** 117 | * 之前滑动的Y轴距离 118 | */ 119 | private Float lastMoveY = 0F; 120 | 121 | /** 122 | * 是否可以触发滑动 123 | */ 124 | private boolean isMove; 125 | /** 126 | * 移动的速度 127 | */ 128 | private float moveSpeed; 129 | 130 | /** 131 | * 是否可以触发缩放 132 | */ 133 | private boolean isZoom; 134 | 135 | /** 136 | * 缩放的速度 137 | */ 138 | private float zoomSpeed; 139 | 140 | /** 141 | * 主区域的颜色 142 | */ 143 | @ColorInt 144 | private Integer color; 145 | 146 | 147 | public SVGView(Context context) { 148 | this(context, null); 149 | } 150 | 151 | public SVGView(Context context, @Nullable AttributeSet attrs) { 152 | this(context, attrs, 0); 153 | } 154 | 155 | public SVGView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 156 | super(context, attrs, defStyleAttr); 157 | TypedArray typedArray = null; 158 | try { 159 | typedArray = context.obtainStyledAttributes(attrs, R.styleable.SVGView); 160 | svgId = typedArray.getResourceId(R.styleable.SVGView_SVGId, -1); 161 | scale = typedArray.getFloat(R.styleable.SVGView_SVGScale, 1); 162 | svgBackground = typedArray.getColor(R.styleable.SVGView_SVGBackground, Color.WHITE); 163 | color = typedArray.getColor(R.styleable.SVGView_SVGColor, Color.BLACK); 164 | lineColor = typedArray.getColor(R.styleable.SVGView_SVGLineColor, Color.BLACK); 165 | isMove = typedArray.getBoolean(R.styleable.SVGView_SVGIsMove, false); 166 | isZoom = typedArray.getBoolean(R.styleable.SVGView_SVGIsZoom, false); 167 | moveSpeed = typedArray.getFloat(R.styleable.SVGView_SVGMoveSpeed, MOVE_INIT_SPEED); 168 | zoomSpeed = typedArray.getFloat(R.styleable.SVGView_SVGZoomSpeed, ZOOM_INIT_SPEED); 169 | } catch (Exception e) { 170 | e.printStackTrace(); 171 | } finally { 172 | assert typedArray != null; 173 | typedArray.recycle(); 174 | } 175 | init(); 176 | } 177 | 178 | @Override 179 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 180 | if (originRecF == null) { 181 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 182 | return; 183 | } 184 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 185 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 186 | int heightMode = MeasureSpec.getSize(heightMeasureSpec); 187 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 188 | 189 | int desiredWidth = (int) originRecF.width() + getPaddingLeft() + getPaddingRight(); 190 | int desiredHeight = (int) originRecF.height() + getPaddingTop() + getPaddingBottom(); 191 | int measuredWidth; 192 | int measuredHeight = desiredHeight; 193 | switch (widthMode) { 194 | case MeasureSpec.EXACTLY: 195 | measuredWidth = widthSize; 196 | break; 197 | case MeasureSpec.AT_MOST: 198 | measuredWidth = Math.min(desiredWidth, widthSize); 199 | break; 200 | default: 201 | measuredWidth = desiredWidth; 202 | } 203 | if (heightMode == MeasureSpec.EXACTLY) { 204 | measuredHeight = heightSize; 205 | } 206 | setMeasuredDimension(measuredWidth, measuredHeight); 207 | } 208 | 209 | private void init() { 210 | paint = new Paint(); 211 | paint.setAntiAlias(true); 212 | PathList = new ArrayList<>(); 213 | gestureClick = new GestureDetector(getContext(), getOnClickGesture()); 214 | gestureMove = new GestureDetector(getContext(), getOnMoveGesture()); 215 | gestureZoom = new ScaleGestureDetector(getContext(), getOnZoomGesture()); 216 | if (svgHelp == null) { 217 | svgHelp = new SVGHelpImpl(); 218 | } 219 | if (svgId == null || svgId == -1) { 220 | return; 221 | } 222 | deCodeSVG(); 223 | } 224 | 225 | private void deCodeSVG() { 226 | new Thread(() -> { 227 | try { 228 | List list = svgHelp.deCodeSVG(getContext(), svgId, file, color); 229 | PathList.clear(); 230 | PathList.addAll(list); 231 | originRecF = svgHelp.getSVGRecF(PathList); 232 | post(this::requestLayout); 233 | post(this::invalidate); 234 | } catch (IOException e) { 235 | e.printStackTrace(); 236 | } 237 | }).start(); 238 | } 239 | 240 | private GestureDetector.OnGestureListener getOnClickGesture() { 241 | return new GestureDetector.OnGestureListener() { 242 | @Override 243 | public boolean onDown(@NonNull MotionEvent e) { 244 | return true; 245 | } 246 | 247 | @Override 248 | public void onShowPress(@NonNull MotionEvent e) { 249 | 250 | } 251 | 252 | @Override 253 | public boolean onSingleTapUp(@NonNull MotionEvent e) { 254 | boolean result = false; 255 | for (PathBean pathBean : PathList) { 256 | float x = (e.getX() - (getWidth() - originRecF.width() * scale) / 2 - lastMoveX) / scale; 257 | float y = (e.getY() - (getHeight() - originRecF.height() * scale) / 2 - lastMoveY) / scale; 258 | if (svgHelp.isClick(pathBean, x, y)) { 259 | pathBean.setSelect(!pathBean.getSelect()); 260 | if (onClickListener != null) { 261 | onClickListener.onClick(pathBean); 262 | } 263 | result = true; 264 | invalidate(); 265 | } 266 | } 267 | return result; 268 | } 269 | 270 | @Override 271 | public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { 272 | return false; 273 | } 274 | 275 | @Override 276 | public void onLongPress(@NonNull MotionEvent e) { 277 | 278 | } 279 | 280 | @Override 281 | public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { 282 | return false; 283 | } 284 | }; 285 | } 286 | 287 | private GestureDetector.SimpleOnGestureListener getOnMoveGesture() { 288 | return new GestureDetector.SimpleOnGestureListener() { 289 | 290 | @Override 291 | public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { 292 | if (!isMove) { 293 | return false; 294 | } 295 | if (moveNumber <= MAX_MOVE_NUMBER) { 296 | moveNumber++; 297 | return true; 298 | } 299 | float deltaX = (e2.getX() - e1.getX()) / moveSpeed; 300 | float deltaY = (e2.getY() - e1.getY()) / moveSpeed; 301 | moveX = deltaX; 302 | moveY = deltaY; 303 | invalidate(); 304 | return true; 305 | } 306 | }; 307 | } 308 | 309 | private ScaleGestureDetector.SimpleOnScaleGestureListener getOnZoomGesture() { 310 | return new ScaleGestureDetector.SimpleOnScaleGestureListener() { 311 | Float lastScale; 312 | 313 | @Override 314 | public boolean onScaleBegin(@NonNull ScaleGestureDetector detector) { 315 | lastScale = 1F; 316 | return super.onScaleBegin(detector); 317 | } 318 | 319 | @Override 320 | public boolean onScale(@NonNull ScaleGestureDetector detector) { 321 | if (!isZoom) { 322 | return false; 323 | } 324 | float scaleGap = (detector.getScaleFactor() - lastScale) / zoomSpeed; 325 | //因为如果scale为负数,会造成图像倒转,到0会看不到,故设置为0.1 326 | if (scale + scaleGap > 0.1) { 327 | scale += scaleGap; 328 | } 329 | invalidate(); 330 | return true; 331 | } 332 | 333 | @Override 334 | public void onScaleEnd(@NonNull ScaleGestureDetector detector) { 335 | super.onScaleEnd(detector); 336 | } 337 | }; 338 | } 339 | 340 | @Override 341 | protected void onDraw(Canvas canvas) { 342 | super.onDraw(canvas); 343 | if (PathList.isEmpty() || canvas == null || originRecF == null) { 344 | return; 345 | } 346 | canvas.save(); 347 | canvas.drawColor(svgBackground); 348 | //默认先移动到中间 349 | canvas.translate((getWidth() - originRecF.width() * scale) / 2, (getHeight() - originRecF.height() * scale) / 2); 350 | canvas.translate(lastMoveX, lastMoveY); 351 | //处理手势位移 352 | canvas.translate(moveX, moveY); 353 | canvas.scale(scale, scale); 354 | for (int i = 0; i < PathList.size(); i++) { 355 | svgHelp.onDraw(PathList.get(i), canvas, paint, lineColor); 356 | } 357 | canvas.restore(); 358 | } 359 | 360 | @SuppressLint("ClickableViewAccessibility") 361 | @Override 362 | public boolean onTouchEvent(MotionEvent event) { 363 | gestureClick.onTouchEvent(event); 364 | int pointerCount = event.getPointerCount(); 365 | if (pointerCount == 1) { 366 | gestureMove.onTouchEvent(event); 367 | } else { 368 | gestureZoom.onTouchEvent(event); 369 | } 370 | if (event.getAction() == MotionEvent.ACTION_UP) { 371 | if (moveNumber > MAX_MOVE_NUMBER) { 372 | //只有经历了滑动才记录 373 | lastMoveX += moveX; 374 | lastMoveY += moveY; 375 | moveX = 0F; 376 | moveY = 0F; 377 | } 378 | moveNumber = 0; 379 | } 380 | return true; 381 | } 382 | 383 | @Override 384 | protected void onConfigurationChanged(Configuration newConfig) { 385 | super.onConfigurationChanged(newConfig); 386 | requestLayout(); 387 | } 388 | 389 | public void setSVGId(@RawRes int svgId) { 390 | this.file = null; 391 | this.svgId = svgId; 392 | deCodeSVG(); 393 | } 394 | 395 | public void setSVGFile(File file) { 396 | if (!file.exists()) { 397 | return; 398 | } 399 | this.svgId = null; 400 | this.file = file; 401 | deCodeSVG(); 402 | } 403 | 404 | public boolean isMove() { 405 | return isMove; 406 | } 407 | 408 | public void setMove(boolean move) { 409 | isMove = move; 410 | } 411 | 412 | public float getMoveSpeed() { 413 | return moveSpeed; 414 | } 415 | 416 | public void setMoveSpeed(float moveSpeed) { 417 | this.moveSpeed = moveSpeed; 418 | } 419 | 420 | public boolean isZoom() { 421 | return isZoom; 422 | } 423 | 424 | public void setZoom(boolean zoom) { 425 | isZoom = zoom; 426 | } 427 | 428 | public float getZoomSpeed() { 429 | return zoomSpeed; 430 | } 431 | 432 | public void setZoomSpeed(float zoomSpeed) { 433 | this.zoomSpeed = zoomSpeed; 434 | } 435 | 436 | public Integer getSVGId() { 437 | return svgId; 438 | } 439 | 440 | public Float getSVGScale() { 441 | return scale; 442 | } 443 | 444 | /** 445 | * 设置SVG提取辅助类 446 | * 447 | * @param svgHelp SVG提取 448 | */ 449 | public void setSVGHelp(SVGHelpInterface svgHelp) { 450 | this.svgHelp = svgHelp; 451 | } 452 | 453 | /** 454 | * 设置点击事件 455 | * 456 | * @param onClickListener 点击回调 457 | */ 458 | public void setOnClickListener(OnSVGClickListener onClickListener) { 459 | this.onClickListener = onClickListener; 460 | } 461 | 462 | public interface OnSVGClickListener { 463 | void onClick(PathBean pathBean); 464 | } 465 | 466 | } 467 | -------------------------------------------------------------------------------- /SVGView/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.zjf.svgdemo' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | applicationId "com.zjf.svgdemo" 12 | minSdk 24 13 | targetSdk 33 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation 'androidx.core:core-ktx:1.8.0' 38 | implementation 'androidx.appcompat:appcompat:1.6.1' 39 | implementation 'com.google.android.material:material:1.5.0' 40 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 41 | implementation project(path: ':SVGView') 42 | testImplementation 'junit:junit:4.13.2' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 45 | 46 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zjf/svgdemo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zjf.svgdemo 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zjf.svgdemo", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjf/svgdemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zjf.svgdemo 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.zjf.svgview.SVGView 7 | 8 | class MainActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_main) 13 | val svgView = findViewById(R.id.SVGView) 14 | svgView.zoomSpeed = 1F 15 | svgView.moveSpeed = 3F 16 | svgView.setOnClickListener(SVGView.OnSVGClickListener { 17 | if (it.select) { 18 | it.color = Color.RED 19 | } else { 20 | it.color = Color.BLUE; 21 | } 22 | }) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaojfGithub/SVGView/18cc74503aba3266a67d80495935fc76a659d6d6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/raw/ic_square.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SVGDemo 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 |