1.背景
接到个需求是矢量图里的文字需要根据系统语言自动适配,这就需要把String字符串转为路径,再绘制出来。
2.String转Path
这一步比较简单,直接问GitHub Copilot马上就有了如下方法:
fun stringToPath(text: String, textSize: Float): Path {
// 创建Paint对象并设置字体大小
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.textSize = textSize
}
// 创建Path对象
val path = Path()
// 获取文本的边界框
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
// 计算路径的起始点,使文本居中
val x = -bounds.left.toFloat()
val y = -bounds.top.toFloat()
// 将文本转换为路径
paint.getTextPath(text, 0, text.length, x, y, path)
return path
}
路径数据获取正常。
3. Path转SVG
这一步就麻烦了,问了GitHub Copilot、Kimi、ChatGPT,给出的基本都是一样的逻辑,这是Kimi给出的,这里只需要把打印出来的字符保存为.svg格式就能直接用浏览器打开,或者拷贝Path数据到Android Studio的XML矢量图的vector标签替换Path参数就能预览。
fun pathToSvg(path: Path): String {
val pathMeasure = PathMeasure(path, false)
val svgBuilder = StringBuilder()
svgBuilder.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n")
svgBuilder.append("<path d=\"")
val pos = FloatArray(2)
val tan = FloatArray(2)
while (pathMeasure.nextContour()) {
pathMeasure.getPosTan(0f, pos, tan)
svgBuilder.append("M ${pos[0]} ${pos[1]}")
val length = pathMeasure.length
var distance = 0f
val step = 1f // 设置步长为1,可以根据需要调整
while (distance < length) {
pathMeasure.getPosTan(distance, pos, tan)
svgBuilder.append(" L ${pos[0]} ${pos[1]}")
distance += step
}
}
svgBuilder.append("\" />\n")
svgBuilder.append("</svg>")
return svgBuilder.toString()
}
返回的结果会丢失第一个字母,或者汉字封口异常。一直怀疑的PathMeasure问题,想寻找其他方法或者其他支持库,都没能实现,只能回来调试参数:
首先把Path放到自定义View的onDraw方法,显示正常:
canvas.drawPath(stringToPath(“User用户”, 50f), mHandlePaint!!)
说明转换没问题,那就是Path数据量化问题。
从单个字母输入发现返回SVG数据为空,这才发现是循环问题。pathMeasure.nextContour()这个方法直接跳过了第一条路径。正确循环如下:
// 将路径转换为SVG格式的字符串
fun pathToSvg(path: Path): String {
val pathMeasure = PathMeasure(path, false)
val svgBuilder = StringBuilder()
svgBuilder.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n")
svgBuilder.append("<path d=\"")
val pos = FloatArray(2)
val tan = FloatArray(2)
Log.d("TAG", "pathMeasure.length: ${pathMeasure.length}")
while (pathMeasure.length > 0) {
pathMeasure.getPosTan(0f, pos, tan)
svgBuilder.append("M ${pos[0]} ${pos[1]}")
val length = pathMeasure.length
var distance = 0f
val step = 2f // 设置步长为1,可以根据需要调整
while (distance < length) {
pathMeasure.getPosTan(distance, pos, tan)
svgBuilder.append(" L ${pos[0]} ${pos[1]}")
distance += step
}
pathMeasure.nextContour()
}
svgBuilder.append("\" />\n")
svgBuilder.append("</svg>")
return svgBuilder.toString()
}
问题解决,收工。