本文参考文章 https://github.com/xchengx/ChinaMap https://github.com/jiahuanyu/SVGMapView
示例图:
项目地址 https://gitee.com/zhanpples/kotlin
数据解析:
// dom4j https://mvnrepository.com/artifact/dom4j/dom4j
implementation 'dom4j:dom4j:1.6.1'
/**
* 解析path数据类
*/
data class SvgPathData(var path: Path, var fillColor: Int = Color.TRANSPARENT, var strokeColor: Int = Color.TRANSPARENT)
class XmlParseTools {
companion object {
private const val TAG = "XmlParseTools"
/**
* svg 数据解析
*/
fun parseSvg(context: Context, rawRes: Int): List {
val saxReader = SAXReader()
val rawResource = context.resources.openRawResource(rawRes)
val document = saxReader.read(rawResource)
val rootElement = document.rootElement
Log.e(TAG, "rootElement.name:${rootElement.name}")
val listOf = arrayListOf()
addPath(rootElement, listOf)
val elementsG = rootElement.elements("g")
Log.e(TAG, "elements.name:${elementsG.size}")
elementsG?.forEach {
addPath(it as Element, listOf)
}
return listOf
}
/**
* 添加path 数据
*/
private fun addPath(
rootElement: Element,
listOf: ArrayList
) {
val elements = rootElement.elements("path")
Log.e(TAG, "elements.name:${elements.size}")
elements.forEach {
val element: Element = it as Element
val fillColor = element.attribute("fill")
val strokeColor = element.attribute("stroke")
val pathData = element.attribute("d")
val parser = SvgPathHelper().parserSvgPath(pathData.value)
val svgPathData = SvgPathData(parser)
if (fillColor != null) {
svgPathData.fillColor = Color.parseColor(fillColor.value)
}
if (strokeColor != null) {
svgPathData.strokeColor = Color.parseColor(strokeColor.value)
}
Log.e(TAG, "rootElement.svgPathData:$svgPathData}")
listOf.add(svgPathData)
}
}
/**
* vector 数据解析
*/
fun parseSvgVector(context: Context, rawRes: Int): List {
val saxReader = SAXReader()
val rawResource = context.resources.openRawResource(rawRes)
val document = saxReader.read(rawResource)
val rootElement = document.rootElement
Log.e(TAG, "rootElement.name:${rootElement.name}")
val elements = rootElement.elements("path")
val listOf = arrayListOf()
elements.forEach {
val element: Element = it as Element
val fillColor = element.attribute("fillColor")
val strokeColor = element.attribute("strokeColor")
val pathData = element.attribute("pathData")
val parser = SvgPathHelper().parserSvgPath(pathData.value)
val svgPathData = SvgPathData(parser)
if (fillColor != null) {
svgPathData.fillColor = Color.parseColor(fillColor.value)
}
if (strokeColor != null) {
svgPathData.strokeColor = Color.parseColor(strokeColor.value)
}
listOf.add(svgPathData)
}
return listOf
}
}
}
绘制path:
//绘制逻辑
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
svgList?.let { svg ->
svg.forEach {
strokePaint.color = it.strokeColor
fillPaint.color = it.fillColor
canvas.drawPath(it.path, fillPaint)
canvas.drawPath(it.path, strokePaint)
}
}
}
优化:
根据Path 的computeBounds(RectF bounds, boolean exact)函数可获取path绘制矩形局域,遍历所以path,获取整张svg图的大小,再更具控件大小缩放canvas 实现适配:
{
svgList?.let { svg ->
val rectF1 = RectF()
if (svg.isNotEmpty()) {
svg[0].path.computeBounds(rectF, true)
svg.forEach {
it.path.computeBounds(rectF1, true)
if (rectF.left > rectF1.left) {
rectF.left = rectF1.left
}
if (rectF.right < rectF1.right) {
rectF.right = rectF1.right
}
if (rectF.top > rectF1.top) {
rectF.top = rectF1.top
}
if (rectF.bottom < rectF1.bottom) {
rectF.bottom = rectF1.bottom
}
}
}
//适配View大小
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
scale = min(measuredWidth / rectF.width(), measuredHeight / rectF.height())
dX = (measuredWidth - scale * rectF.width()) / 2
dY = (measuredHeight - scale * rectF.height()) / 2
Log.e(TAG, "$scale---$dX--$dY")
}
//绘制逻辑
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.scale(scale, scale)
canvas.translate(dX / scale, dY / scale)
``````
``````
``````
}
动画:
根据PathMeasure测量path长度以及截取指定长度的path实现绘制动画
//存储绘制结束的path
private val drawPath = arrayListOf()
//path截取长度
private var pathEnd: Float = 0F
//截取后的Path
private var desPath = Path()
/**
* 每次绘制跨度
*/
var drawStep: Float = 50f
/**
* 绘制速度单位毫秒
*/
var drawSpeedTime: Long = 100L
//绘制动画逻辑
private var runnable = object : Runnable {
override fun run() {
svgList?.let { svg ->
if (pathEnd >= pathMeasure.length) {
val path = Path()
path.lineTo(0F, 0F)//解决绘制bug
pathMeasure.getSegment(0F, pathMeasure.length, path, true)
drawPath.add(SvgPathData(path, svg[index].fillColor, svg[index].strokeColor))
if (pathMeasure.nextContour()) {
pathEnd = 0F
} else {
index++
if (index > svg.size - 1) {
invalidate()
postDelayed({
if (isCanAgain) {
index = 0
pathEnd = 0F
drawPath.clear()
pathMeasure.setPath(svg[index].path, true)
post(this)
}
}, againTime)
return
}
pathMeasure.setPath(svg[index].path, true)
}
}
pathEnd += drawStep
invalidate()
postDelayed(this, drawSpeedTime)
}
}
}
//绘制逻辑
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.scale(scale, scale)
canvas.translate(dX / scale, dY / scale)
drawPath.forEach {
strokePaint.color = Color.RED
fillPaint.color = it.fillColor
canvas.drawPath(it.path, fillPaint)
canvas.drawPath(it.path, strokePaint)
}
desPath.reset()
desPath.lineTo(0F, 0F)//解决绘制bug
pathMeasure.getSegment(0F, pathEnd, desPath, true)
strokePaint.color = Color.MAGENTA
canvas.drawPath(desPath, strokePaint)
}
}
扩展,基于path触摸事件Region:
public boolean setPath(Path path, Region clip)//将path和clip的两个区域取交集
public boolean contains(int x, int y)//是否包含指定的点(x,y)