Android使用PDF API绘制矢量Drawable拉伸失真问题求助
解决Android PDF API绘制矢量Drawable时的拉伸模糊问题
看起来你遇到的问题是因为直接给VectorDrawable设置了很小的Bounds,导致它被渲染成低分辨率的位图,然后绘制到PDF中就会出现拉伸模糊的情况——毕竟矢量图的优势是无限缩放不损失画质,但如果把它当成固定尺寸的位图来渲染,就浪费了这个特性。
下面是几个关键的修复思路和修改后的代码示例:
核心原因
VectorDrawable在调用setBounds()并绘制到Canvas时,如果Bounds尺寸远小于其固有尺寸,系统会将矢量图形栅格化成对应大小的位图,再进行绘制。而PDF本身是矢量格式,但这里绘制的是栅格化后的位图,自然会模糊。我们需要让VectorDrawable以矢量形式渲染,通过Canvas的缩放来适配目标尺寸,而不是直接限制它的Bounds。
解决方案步骤
- 保持矢量图的固有尺寸,通过Canvas缩放适配目标区域:不要硬编码小尺寸的Bounds,而是利用Canvas的
scale()方法来缩小矢量图,这样矢量图形会保持矢量特性渲染,不会丢失细节。 - 保持宽高比,避免拉伸:计算目标区域的宽高比和矢量图的固有宽高比,取合适的缩放比例,确保图标不会被拉伸变形。
- 适配PDF的DPI(可选但推荐):PDF的单位是“点”(1点 = 1/72 英寸),而Android设备通常使用160DPI(mdpi)作为基准。如果你的目标尺寸是基于Android的dp,需要转换为PDF的点:
dpValue * (72f / 160f),这样在PDF中显示的大小会更符合预期。
修改后的代码示例
针对你代码中的moonIcon和starIcon绘制部分,修改如下:
Drawable starIcon = getContext().getDrawable(R.drawable.ic_star); VectorDrawableCompat moonIcon = VectorDrawableCompat.create(getContext().getResources(), R.drawable.ic_moon, getActivity().getTheme()); PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 2).create(); PdfDocument.Page page = pdfDocument.startPage(pageInfo); Canvas canvas = page.getCanvas(); float x = drawX + (perItemWidth * col); RectF rectF = new RectF(); rectF.set(x + 2.5F - perItemWidth, drawY + 1, x + 1, drawY + perItemHeight - 1); float importantSpaceFromLeft = (rectF.width() * 10F) / 100F; float importantSpaceFromTop = ((rectF.height() * 2F) / 100F); // --- 处理moonIcon --- Rect moonIconBounds = new Rect(); moonIconBounds.left = (int) (rectF.left + ((rectF.height() * 10F) / 100F)) + 10; moonIconBounds.right = (int) (rectF.left + ((rectF.height() * 10F) / 100F)) + 20; moonIconBounds.bottom = (int) (rectF.bottom - importantSpaceFromTop) - 10 - 2; moonIconBounds.top = (int) (rectF.bottom - importantSpaceFromTop) - 20 - 2; // 获取moonIcon的固有宽高(矢量图的原始尺寸) int moonIntrinsicWidth = moonIcon.getIntrinsicWidth(); int moonIntrinsicHeight = moonIcon.getIntrinsicHeight(); if (moonIntrinsicWidth > 0 && moonIntrinsicHeight > 0) { // 计算目标尺寸和缩放比例,保持宽高比 float targetMoonWidth = moonIconBounds.width(); float targetMoonHeight = moonIconBounds.height(); float scaleX = targetMoonWidth / moonIntrinsicWidth; float scaleY = targetMoonHeight / moonIntrinsicHeight; float moonScale = Math.min(scaleX, scaleY); // 避免拉伸 canvas.save(); // 平移到目标位置的左上角 canvas.translate(moonIconBounds.left, moonIconBounds.top); // 缩放画布,让矢量图适配目标尺寸 canvas.scale(moonScale, moonScale); // 设置Bounds为矢量图的固有尺寸,保证矢量渲染 moonIcon.setBounds(0, 0, moonIntrinsicWidth, moonIntrinsicHeight); moonIcon.draw(canvas); canvas.restore(); } // --- 处理starIcon --- Rect starIconBounds = new Rect(); starIconBounds.left = (int) (rectF.left + ((rectF.height() * 10F) / 100F)) + 10; starIconBounds.right = (int) (rectF.left + ((rectF.height() * 10F) / 100F)) + 20; starIconBounds.bottom = (int) (rectF.bottom - importantSpaceFromTop); starIconBounds.top = (int) (rectF.bottom - importantSpaceFromTop) - 10; // 同样处理starIcon if (starIcon instanceof VectorDrawableCompat || starIcon instanceof VectorDrawable) { int starIntrinsicWidth = starIcon.getIntrinsicWidth(); int starIntrinsicHeight = starIcon.getIntrinsicHeight(); if (starIntrinsicWidth > 0 && starIntrinsicHeight > 0) { float targetStarWidth = starIconBounds.width(); float targetStarHeight = starIconBounds.height(); float scaleX = targetStarWidth / starIntrinsicWidth; float scaleY = targetStarHeight / starIntrinsicHeight; float starScale = Math.min(scaleX, scaleY); canvas.save(); canvas.translate(starIconBounds.left, starIconBounds.top); canvas.scale(starScale, starScale); starIcon.setBounds(0, 0, starIntrinsicWidth, starIntrinsicHeight); starIcon.draw(canvas); canvas.restore(); } } else { // 如果是普通Drawable(比如BitmapDrawable),按原方式处理 starIcon.setBounds(starIconBounds); starIcon.draw(canvas); }
额外检查点
- 确认你的
ic_star和ic_moon确实是VectorDrawable(后缀为.xml,内容包含<vector>标签),而不是被转换为BitmapDrawable。有些SVG转换工具可能会默认生成BitmapDrawable,这会直接导致模糊。 - 检查VectorDrawable的
viewportWidth和viewportHeight是否设置合理,这是矢量图的坐标系统基准,不合理的设置也可能导致缩放异常。
这样修改后,矢量图会以矢量形式渲染到PDF中,即使尺寸很小也能保持清晰,不会出现拉伸模糊的问题。
内容的提问来源于stack exchange,提问作者Ashvin solanki




