Android默认不能直接支持的svg格式的,需要先将文件转换成vector矢量图
示例代码地址:SVGMap
效果
SVG在Android中能做什么
- APP图标:在SDK23后,APP的图标都是由SVG来表示
- 自定义控件:不规则控件,复杂的交互,子控件重叠判断,图表等都可以用SVG来做
- 复杂动画:如根据用户滑动动态显示动画,路径动画
SVG语法
- M=moveto(M X,Y) : 将画笔移动到指定的位置
- L = lineto(L X,Y) : 画直线到指定的位置
- H=horizontal(lineto(H X)) : 画水平线到指定的X坐标位置
- V = vectical(lineto(V Y)) :画垂线到指定的Y坐标位置
- Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY) : 二次贝塞尔曲线
- C = curveto(Q X1,Y1,X2,Y2,ENDX,ENDY) :三次贝塞尔曲线
- S = smooth curveto(S X1,Y1,ENDX,ENDY):平滑过度
- Z = closepath():闭合路径
SVG文件示例(就是我们平时常见的xml文件)
示例,完成地图绘制(UK),并且能够正常点击省份
需求分析
-
- 显示SVG
-
- 各个省份可以单独点击
-
- 缩放及拖动
下载SVG地图的网站https://www.amcharts.com/svg-maps/
如果只需要显示图片,只需要转化就好,按照下面的方法
注:SVG图片转换为xml文件时,会提示报错,根据提示修改SVG文件
转xml方法:文件夹右键->New->Vector asset
Step1:显示地图
Q:1.如何解析获取svg中的信息?
A: 1.1拆分各个省份:名称,路径信息,颜色;1.2解析文件并在画布中绘制
显示图片的代码
- Step1:使用系统方法解析文件,创建path集合(通过DocumentBuilder)
- Step2:轮训path集合,获取数据存储。(使用PathParser将pathdata转换为Path实例)
- Step3:轮训完毕,调用重新绘制方法,在onDraw中进行绘制
主要代码
//开线程进行数据解析
private val loadThread = Thread {
//获取svg图片输入流
val inputStream = context.resources.openRawResource(R.raw.unitedkingdom_high)
//创建解析类DocumentBuilder
val builderFactory = DocumentBuilderFactory.newInstance()
var builder: DocumentBuilder? = null
try {
builder = builderFactory.newDocumentBuilder()
//解析输入流,获取Document实例
val document = builder.parse(inputStream)
val documentElement = document.documentElement
//先找到path
val pathNodeList = documentElement.getElementsByTagName("path")
var left = -1f
var right = -1f
var top = -1f
var bottom = -1f
val list = mutableListOf()
for (i in 0 until pathNodeList.length) {
val element = pathNodeList.item(i) as Element
val pathData = element.getAttribute("d")
val title = element.getAttribute("title")
//将pathData转换成Path
val path = PathParser.createPathFromPathData(pathData)
val proviceItem = ProvinceItem(path, title, colorArray[i % 4])
val rect = RectF()
path.computeBounds(rect, true)
left = if (left == -1f) rect.left else Math.min(left, rect.left)
right = if (right == -1f) rect.right else Math.max(right, rect.right)
top = if (top == -1f) rect.top else Math.min(top, rect.top)
bottom = if (bottom == -1f) rect.bottom else Math.max(bottom, rect.bottom)
list.add(proviceItem)
}
itemList = list
totalRect = RectF(left, top, right, bottom)
// 刷新界面
val handler = Handler(Looper.getMainLooper())
handler.post {
requestLayout()
invalidate()
}
} catch (e: Exception) {
}
}
/**
* Path绘制
*/
fun drawItem(canvas: Canvas, paint: Paint, isSelect: Boolean) {
if (isSelect) {
//绘制内部颜色
paint.clearShadowLayer()
paint.strokeWidth = 1f
paint.color = drawColor
paint.style = Paint.Style.FILL
canvas.drawPath(path, paint)
//绘制边界
paint.style = Paint.Style.STROKE
paint.color = Color.YELLOW
canvas.drawPath(path, paint)
} else {
paint.strokeWidth = 2f
paint.color = Color.BLACK
paint.style = Paint.Style.FILL
paint.setShadowLayer(8f, 0f, 0f, Color.WHITE)
canvas.drawPath(path, paint)
paint.clearShadowLayer()
paint.color = drawColor
paint.style = Paint.Style.FILL
canvas.drawPath(path, paint)
}
}
ProvinceItem.kt
package com.tic.svgmap
import android.graphics.*
/**
* Created by Ting on 2019-11-24.
*/
data class ProvinceItem(
val path: Path,
val name: String,
//模块颜色
val drawColor: Int
) {
//显示省份信息
var clickPoint: PointF? = null
/**
* 判断点击区域是否在当前省份
*
*/
fun isTouch(x: Float, y: Float): Boolean {
//获取Path矩形区域
val rectF = RectF()
path.computeBounds(rectF, true)
val region = Region()
//绘制路径
region.setPath(
path, Region(
rectF.left.toInt(),
rectF.top.toInt(),
rectF.right.toInt(),
rectF.bottom.toInt()
)
)
return region.contains(x.toInt(), y.toInt())
}
/**
* 绘制
*/
fun drawItem(canvas: Canvas, paint: Paint, isSelect: Boolean) {
if (isSelect) {
//绘制内部颜色
paint.clearShadowLayer()
paint.strokeWidth = 1f
paint.color = drawColor
paint.style = Paint.Style.FILL
canvas.drawPath(path, paint)
//绘制边界
paint.style = Paint.Style.STROKE
paint.color = Color.YELLOW
canvas.drawPath(path, paint)
} else {
paint.strokeWidth = 2f
paint.color = Color.BLACK
paint.style = Paint.Style.FILL
paint.setShadowLayer(8f, 0f, 0f, Color.WHITE)
canvas.drawPath(path, paint)
paint.clearShadowLayer()
paint.color = drawColor
paint.style = Paint.Style.FILL
canvas.drawPath(path, paint)
}
}
}
Setp2 实现拖拽点击
1.拖拽,放大缩小
解决方法:通过onTouchEvent的Event动作状态,判断单机双击,进行相对应操作
主要代码
/**
* 单指点击拖动,双指放大
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
//当前缩放系数
var currentScaleCount = 0f
//当前x平移距离
var currentTranslateX = 0f
//当前y平移距离
var currentTranslateY = 0f
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
//单点触控
startPoint.set(event.x, event.y)
mode = DRAG
actionClick = true
}
MotionEvent.ACTION_POINTER_DOWN -> {
//多点触控
oriDis = distance(event)
if (oriDis > 10) {
midPoint = midPoint(event)
mode = ZOOM
}
actionClick = false
}
MotionEvent.ACTION_MOVE -> {
//滑动
if (mode == DRAG) {
//单指拖动
if (abs(x - startPoint.x) > 10 || abs(y - startPoint.y) > 10) {
currentTranslateX = translateX + x - startPoint.x
currentTranslateY = translateY + y - startPoint.y
translateX = currentTranslateX
translateY = currentTranslateY
startPoint.set(x, y)
actionClick = false
invalidate()
}
} else if (mode == ZOOM) {
//两指缩放
//当前两指距离
val newDist = distance(event)
if (abs(newDist - oriDis) > 10) {
val scaleInner = newDist / oriDis
currentScaleCount = scale + (scaleInner - 1)
if (currentScaleCount < 1) {
scale = 1f
} else {
scale = currentScaleCount
}
oriDis = newDist
invalidate()
}
}
}
MotionEvent.ACTION_UP -> {
mode = NONE
if (actionClick) {
handleTouch(x / scale - translateX, y / scale - translateY)
}
}
MotionEvent.ACTION_POINTER_UP -> {
//多点触控
mode = NONE
}
}
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (itemList.size > 0) {
canvas.save()
canvas.scale(scale, scale)
canvas.translate(translateX, translateY)
for (proviceItem in itemList) {
if (proviceItem == select) {
proviceItem.drawItem(
canvas, mPaint, true
)
} else {
proviceItem.drawItem(
canvas, mPaint, false
)
}
}
if (shouldShowText) {
//绘制文本
mPaint.color = Color.RED
mPaint.style = Paint.Style.FILL
mPaint.textSize = 40f
if (select != null && select?.clickPoint != null) {
canvas.drawText(
select!!.name,
select!!.clickPoint!!.x,
select!!.clickPoint!!.y,
mPaint
)
}
}
canvas.restore()
}
}
2.点击显示
当收到抬起动作的时候,传入对应的x,y值
MotionEvent.ACTION_UP -> {
mode = NONE
if (actionClick) {
handleTouch(x / scale - translateX, y / scale - translateY)
}
}
private fun handleTouch(x: Float, y: Float) {
shouldShowText = false
if (itemList.size > 0) {
var selectItem: ProvinceItem? = null
for (provinceItem in itemList) {
if (provinceItem.isTouch(x, y)) {
selectItem = provinceItem
provinceItem.clickPoint = PointF(x, y)
shouldShowText = true
}
}
if (selectItem != null) {
select = selectItem
postInvalidate()
}
}
}