1

编辑一:

根据约瑟夫的回答所做的更改:

在 bytesToDrawable(byte[] imageBytes) 中:

更改了以下内容:使用 BitmapDrawable(Resources res, Bitmap bitmap) 而不是 BitmapDrawable(Bitmap bitmap):

return new BitmapDrawable(ApplicationConstants.ref_currentActivity.getResources(),BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options));

这是该更改的结果:略有不同的问题:

在此处输入图像描述

问题:

如果我将新的构造函数用于可绘制位图并缩放图像以达到所需的目标密度,我还需要使用我的 calculateSampleSize 方法吗?


原始问题:

嗨朋友们,

我的应用程序是基于模块的,因此特定于该模块的图像仅从包含它们的 jar(module) 加载,而不是从主应用程序加载。

每个模块都有自己的 ModularImageLoader - 它基本上允许我根据在 jar 中找到的图像名称来获取 Drawables。

构造函数接受 zipFile(Module A) 和文件名列表(zip 中以“.png”结尾的任何文件)。

进行的研究:

我使用了以下内容:Link to Developer Page on Loading bitmaps Effectively

最初我为每个密度创建大小的图像,但现在我只有一组大小为 96x96 的图像图标。

如果屏幕密度小于 xhdpi,我会加载 96x96 图像的较小采样尺寸 - 36x36(对于 ldpi)、48x48(对于 mdpi)、72x72(对于 hdpi)。否则我只返回 96x96 图像。(查看方法 calculateSampleSize() 和 bytesToDrawable())

我认为用代码更容易理解这个概念:所以这里是 ModularImageLoader

代码:

public class ModularImageLoader
{
    public static HashMap<String, Drawable> moduleImages = new HashMap<String, Drawable>();
    public static int reqHeight = 0;
    public static int reqWidth = 0;
    public ModularImageLoader(ZipFile zip, ArrayList<String> fileNames)
    {
         float sdpi = ApplicationConstants.ref_currentActivity.getResources().getDisplayMetrics().density;
         if(sdpi == 0.75)
         {
            reqHeight = 36;
            reqWidth = 36;
         }
         else if(sdpi == 1.0)
         {
            reqHeight = 48;
            reqWidth = 48;
         }
         else if (sdpi == 1.5)
         {
            reqHeight = 72;
            reqWidth = 72;
         }
         else if (sdpi == 2.0)
         {
            reqHeight = 96;
            reqWidth = 96;
          }
          String names = "";
          for(String fileName : fileNames)
          {
            names += fileName + " ";
          }
          createByteArrayImages(zip, fileNames);
     }

public static Drawable findImageByName(String imageName)
{
    Drawable drawableToReturn = null;
    for (Entry<String, Drawable> ent : moduleImages.entrySet())
    {
        if(ent.getKey().equals(imageName))
        {
            drawableToReturn = ent.getValue();
        }
    }
    return drawableToReturn;
}
private static void createByteArrayImages(ZipFile zip, ArrayList<String> fileNames)
{
    InputStream in = null;
    byte [] temp = null;
    int nativeEndBufSize = 0;
    for(String fileName : fileNames)
    {
        try
        {
            in = zip.getInputStream(zip.getEntry(fileName));
            nativeEndBufSize = in.available();
            temp = toByteArray(in,nativeEndBufSize);

            // get rid of .png
            fileName = fileName.replace(".png", "");
            fileName = fileName.replace("Module Images/", "");
            moduleImages.put(fileName, bytesToDrawable(temp));
        }
        catch(Exception e)
        {
            System.out.println("getImageBytes() threw an exception: " + e.toString());
            e.printStackTrace();
        }
    }
    try
    {
        in.close();
    }
    catch (IOException e)
    {
        System.out.println("Unable to close inputStream!");
        e.toString();
        e.printStackTrace();
    }
}
public static byte[] toByteArray(InputStream is, int length) throws IOException 
{
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    int l;
    byte[] data = new byte[length];
    while ((l = is.read(data, 0, data.length)) != -1) 
    {
      buffer.write(data, 0, l);
    }
    buffer.flush();
    return buffer.toByteArray();
}
public static Drawable bytesToDrawable(byte[] imageBytes)
{
    try
    {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        int imageHeight = options.outHeight;
        int imageWidth = options.outWidth;

        BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
        String imageType = options.outMimeType;
        Log.d("ImageInfo : ", "Height:" + imageHeight +",Width:" +imageWidth + ",Type:" + imageType);

        options.inJustDecodeBounds = false;
        //Calculate sample size
        options.inSampleSize = calculateSampleSize(options);
        return new BitmapDrawable(BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options));
    }
    catch(Exception e)
    {
        Message.errorMessage("Module Loading Error", "The images in this module are too large to load onto cell memory. Please contact your administrator",
                "Source of error: ModularImageLoader - bytesToDrawable method", e.toString());
        return null;
    }
}
public static int calculateSampleSize(BitmapFactory.Options options)
{
    // raw height and width of the image itself
    int sampleSize = 1;
    int height = options.outHeight;
    int width = options.outWidth;
    if(height > reqHeight || width > reqWidth)
    {
        if(width > height)
        {
            sampleSize = Math.round((float)height / (float)reqHeight);
        }
        else
        {
            sampleSize = Math.round((float)width / (float)reqWidth);
        }
    }
    return sampleSize;
}
}

问题:

下图显示了 4 个正在运行的模拟器,这些是它们的规格以及我如何在 eclipse AVD 中设置它们:

LDPI:密度 120,皮肤 QVGA MDPI:密度 160,皮肤 HVGA HDPI:密度 240,皮肤 WVGA800 XHDPI:密度 320,皮肤 800x1280

图像显示问题:

在此处输入图像描述

问题:

根据代码 - 在 XHDPI 窗口中,为什么联系人图像如此之小?新闻图像也是 96x96(除了从主应用程序加载 - 所以它在 res>XHDPI 下)。问题是,我看到它在 MDPI 屏幕和 HDPI 屏幕上加载良好,但对于其他屏幕来说却很奇怪。有任何想法吗?

4

4 回答 4

6

如果您使用 BitmapFactory.Options 提供密度信息,BitmapFactory 能够为您缩放图像。如果您这样做,您应该能够删除 ModularImageLoader 中的自定义缩放代码。

指定 inDensity 和 inTargetDensity - 类似于以下内容:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = DisplayMetrics.DENSITY_MEDIUM;
options.inTargetDensity = activityRef.getResources().getDisplayMetrics().densityDpi;
options.inScaled = true;
return BitmapFactory.decodeStream(openByteStream(), null, options);

BitmapFactory.decodeByteArray 中显然存在一个忽略某些缩放选项的错误,因此您可能需要将字节数组包装在 ByteArrayInputStream 中并使用 BitmapFactory.decodeStream ,如上所示(参见http://code.google.com/p/android /issues/detail?id=7538)。

于 2012-11-21T23:54:07.210 回答
4

您应该使用BitmapDrawable(Resources res, Bitmap bitmap)构造函数来确保可绘制对象的目标密度设置正确,您正在使用的构造函数已被弃用。

如果你看你的LDPI屏幕,那里的联系人图像实际上有点太小了,在你的HDPI屏幕上有点太小了。只有在MDPI屏幕上它看起来完全正确(因为默认目标密度是MDPI)。

于 2012-11-20T22:59:37.573 回答
2

您可以使用创建所需的高度和宽度dp,然后将其转换为px以获得缩放图像的正确尺寸。

假设您希望图像为 32x32dp。

int reqWidth = dpToPx(context, 32);
int reqHeight = dpToPx(context, 32);

public static int dpToPx(Context context, int dp) {
    return (int) (dp * (context.getResources().getDisplayMetrics().densityDpi / 160f) + 0.5f);
}
于 2012-11-20T23:06:23.370 回答
0

ldpi - 36x36 mdpi - 48x48 hdpi - 72x72 xhdpi - 96x96

如果这些都是彼此的纯倍数,那就太好了,因为位图工厂以整数处理样本大小,因此样本大小需要是整数(没有尾随小数才能完全准确)。

解决方案:

在开始采样之前,我为“每种”屏幕类型准备了 1 张图像,如果该图像处于按下状态,我将拥有 2 张单独的图像。

因此,对于一张图像 - 我实际上在应用程序中需要 4 个,如果该图像具有按下状态 - 一张图像将需要 8 个图像。

我的主要目标是减少图像的数量,这样我就不会超载位图堆分配并可能抛出内存不足异常,当我的位图大小完全合理时,我已经看到为我抛出了这个异常(我相信这与堆上的图像数量以及它们各自的图像大小有关(如果我错了,请纠正我..))我当然想减少我的模块大小。

所以我决定这样做:每张图像有 2 张图像 - 一张尺寸为 72,一张尺寸为 96 - 这样我就有了 xhdpi 和 hdpi 密度的屏幕所需的图标,我可以(简单地)采样到需要时使用 ldpi 和 mdpi。

72/2 = 36 96/2 = 48

这样,每张图像我只有 2 张图像,最坏的情况是,如果该图像具有按下状态,我将有 4 张图像。这将图像大小库减少了近 50%,并使我的模块更小。我注意到模块大小从 525 kb 变为大约 329。

这确实是我的目标。

感谢大家的帮助!如果有人有任何问题,请随时发表评论,我会尽快回复您。

于 2012-11-26T14:39:19.607 回答