使用Xamarin开发Android:Canvas中图片自动缩放问题咨询
问题分析与解决方案
首先,咱们来拆解你遇到的核心问题:图片在xhdpi模拟器上自动缩放是Android资源加载机制的默认行为,并非什么"后台隐式操作"——只是你可能没注意到Drawable资源适配的细节,而文档其实是有明确说明的。下面一步步给你讲清楚:
为什么图片会自动缩放?
Android的资源系统会根据当前设备的屏幕密度,自动缩放Drawable资源来保证视觉一致性。具体来说:
- 每个屏幕密度对应一个密度因子:mdpi是基准(因子1.0),xhdpi的因子是2.0,也就是说xhdpi屏幕的像素密度是mdpi的2倍。
- 当你从默认的
drawable目录加载图片时,Android会把这张图当作mdpi资源处理。在xhdpi设备上,系统会自动把它放大2倍(32×32 → 64×64),这样图片在屏幕上的视觉大小和在mdpi设备上一致。 - 而mdpi设备的密度因子是1.0,所以图片保持原始尺寸显示,这就是为什么你在mdpi模拟器上看到的是正常的32×32。
你之前查阅的dp相关文档,是针对布局尺寸的适配(比如控件的宽高用dp),而Drawable资源的缩放是另一个维度的适配规则,这部分在"支持不同屏幕密度"的文档章节里有详细说明,可能你之前没关联到一起。
你的代码里的额外问题
除了资源加载的默认行为,你的代码还有两个会加重缩放问题的点:
- partialBitmap的尺寸错误:
你用Resources.Configuration.ScreenWidthDp和ScreenHeightDp来创建Bitmap,但Bitmap.CreateBitmap需要的是像素值,而ScreenWidthDp是dp单位。比如xhdpi设备上,360dp的宽度对应720像素,但你直接用360作为宽度创建Bitmap,导致这个Bitmap本身尺寸偏小,后续绘制到屏幕时会被拉伸,图片自然看起来更大。 - OnDraw中错误回收Bitmap:
OnDraw方法会频繁调用(比如屏幕刷新、View重绘时),你在这里调用partialBitmap.Recycle();会直接销毁Bitmap,下次绘制时就会报错,这是严重的内存问题。
解决步骤
1. 让图片保持原始像素尺寸
要避免系统自动缩放图片,把你的32×32图片放在drawable-nodpi目录下(如果没有这个目录,就在Resources下新建)。这个目录的资源不会被系统缩放,无论什么密度的设备,都会以原始像素尺寸加载。
2. 修正partialBitmap的创建逻辑
先正确获取屏幕的像素尺寸,再创建Bitmap:
public MyCanvasPath(Context context) : base(context) //constructor { // 先初始化DisplayMetrics获取密度因子 DisplayMetrics dm = new DisplayMetrics(); ((Activity)context).WindowManager.DefaultDisplay.GetMetrics(dm); float scale = dm.Density; // 把dp转换为像素 int screenWidthPx = (int)(Resources.Configuration.ScreenWidthDp * scale); int screenHeightPx = (int)(Resources.Configuration.ScreenHeightDp * scale); partialBitmap = Bitmap.CreateBitmap(screenWidthPx, screenHeightPx, Bitmap.Config.Argb8888); // 加载图片(现在放在drawable-nodpi,不会被缩放) jp1 = BitmapFactory.DecodeResource(Resources, Resource.Drawable.jp1); jp2 = BitmapFactory.DecodeResource(Resources, Resource.Drawable.jp2); jp3 = BitmapFactory.DecodeResource(Resources, Resource.Drawable.jp3); jp4 = BitmapFactory.DecodeResource(Resources, Resource.Drawable.jp4); Canvas myCanvas = new Canvas(partialBitmap); b = myCanvas.Height; a = myCanvas.Width; bw = jp1.Width; // 现在bw应该是32,不会再是64 IList<Bitmap> pImgList = new List<Bitmap> { jp1, jp2, jp3, jp4 }; imgCount = pImgList.Count; for (int x = 0; x < imgCount; x++) { myCanvas.DrawBitmap(pImgList.ElementAt(x), bw * x, 0, null); } }
3. 修正Bitmap的回收时机
把partialBitmap.Recycle();移到View的销毁方法中,比如重写OnDetachedFromWindow:
protected override void OnDetachedFromWindow() { base.OnDetachedFromWindow(); if (partialBitmap != null && !partialBitmap.IsRecycled) { partialBitmap.Recycle(); partialBitmap = null; } // 顺便回收其他图片资源 foreach(var bmp in new List<Bitmap>{jp1,jp2,jp3,jp4}) { if(bmp != null && !bmp.IsRecycled) { bmp.Recycle(); } } }
总结
- 图片自动缩放是Android资源适配的默认行为,根源是资源目录的密度匹配规则,文档中是有明确说明的(只是和dp的适配规则是分开的)。
- 通过将图片放在
drawable-nodpi目录,可以强制保持原始像素尺寸。 - 修正Bitmap的创建和回收逻辑,避免额外的拉伸和内存问题。
内容的提问来源于stack exchange,提问作者ContemplativeCyborg
相关产品推荐
相关产品推荐

