Поддержка нескольких экранов с использованием параметров Bitmap Factory?

Редактировать один:

Изменения сделаны на основе ответа Иосифа:

В bytesToDrawable(byte[] imageBytes):

Изменено следующее: Использование BitmapDrawable(Ресурсы res, Растровое изображение) вместо BitmapDrawable(Растровое изображение):

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

Это результат этого изменения: Немного другая проблема:

Вопрос:

Если я использую новый конструктор для растрового рисования, и он масштабирует изображения для требуемой целевой плотности, нужно ли мне по-прежнему использовать мой метод CalculateSize?


Оригинальный вопрос:

Привет, друзья,

Мое приложение основано на модуле, и поэтому изображения, специфичные для этого модуля, загружаются только из jar (модуля), который их содержит, а не из основного приложения.

Каждый модуль имеет свой собственный ModularImageLoader - который в основном позволяет мне выбирать Drawables на основе имени изображения, найденного в банке.

Конструктор принимает zipFile (модуль A) и список имен файлов (любой файл, заканчивающийся на ".png" из zip).

Исследования проведены:

Я использовал следующее: Ссылка на страницу разработчика при эффективной загрузке растровых изображений

Изначально я создавал изображения размером для каждой плотности, но теперь у меня есть только один набор иконок размером 96x96.

Если плотность экрана меньше xhdpi, я загружаю меньшие размеры выборки изображения 96x96 - 36x36(для ldpi), 48x48(для mdpi), 72x72(для hdpi). В противном случае я просто возвращаю изображение 96х96. (Посмотрите на метод CalculateSize() и 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 запущенных эмулятора, их характеристики и то, как я их установил в AVD затмения:

LDPI: плотность 120, скин QVGA MDPI: плотность 160, скин HVGA HDPI: плотность 240, кожа WVGA800 XHDPI: плотность 320, кожа 800x1280

Проблема с изображением:

Вопрос:

Исходя из кода - в окне XHDPI почему изображение контактов такое маленькое? Изображение News также имеет размер 96x96 (за исключением того, что оно загружено из основного приложения - поэтому оно находится под res>XHDPI). Дело в том, что я вижу, что он отлично загружается для экранов MDPI и HDPI, но для остальных это странно. Есть идеи?

4 ответа

Решение

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

Было бы замечательно, если бы все они были чистыми кратными друг другу, поскольку фабрика растровых изображений обрабатывает размер выборки в целых числах, и поэтому размер выборки должен быть целым числом (без конечных десятичных дробей, чтобы быть полностью точными).

Решение:

До того, как я начал сэмплирование, у меня было 1 изображение для "каждого" типа экрана, и если бы у этого изображения было нажатое состояние, у меня было бы 2 отдельных изображения для этого.

Поэтому для одного изображения - мне фактически нужно 4 в приложении, и если бы это изображение имело нажатые состояния - одному изображению понадобилось бы 8 изображений.

Моя главная цель состояла в том, чтобы сократить количество изображений, чтобы я не перегружал выделение кучи растровых изображений и, возможно, выбрасывал исключение из нехватки памяти. Я видел это исключение для меня, когда размер моего растрового изображения вполне приемлем (Я считаю, что это как-то связано с количеством изображений в куче, а также с их соответствующими размерами (поправьте меня, если я ошибаюсь, пожалуйста...)), и я хотел, конечно, сократить размер моего модуля.

Поэтому я решил следующее: иметь 2 изображения для каждого изображения - одно размером 72 и одно размером 96 - таким образом, у меня будут значки, необходимые для экранов с плотностью xhdpi и hdpi, и я смогу (просто) сэмплировать до лдпи и мдпи при необходимости.

72/2 = 36 96/2 = 48

Таким образом, у меня было бы только 2 изображения для каждого изображения, и в худшем случае, если бы у этого изображения были нажатые состояния, у меня было бы 4 изображения. Это сокращает размер банка изображений почти на 50% и делает мои модули намного меньше. Я заметил изменение размера модуля с 525 кбайт до 329.

Это действительно то, к чему я стремился.

Спасибо всем за помощь! Если у кого-то есть какие-либо вопросы, пожалуйста, не стесняйтесь оставлять комментарии, и я свяжусь с вами, как только смогу.

BitmapFactory может масштабировать изображения для вас, если вы предоставляете информацию о плотности с помощью BitmapFactory.Options. Если вы сделаете это, вы сможете удалить пользовательский код масштабирования в вашем 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).

Вы должны использовать BitmapDrawable(Resources res, Bitmap bitmap) конструктор, который будет гарантировать, что у drawable задана правильная целевая плотность, используемый вами конструктор устарел.

Если вы посмотрите на свой LDPI Экран изображения контактов там на самом деле немного слишком маленький, и немного слишком маленький в вашем HDPI экран. Только на MDPI экран выглядит совершенно правильно (потому что целевая плотность по умолчанию MDPI).

Вы можете создать желаемую высоту и ширину, используя 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);
}
Другие вопросы по тегам