Почему мое векторное масштабирование не соответствует ожиданиям?

97

Я пытаюсь использовать векторные чертежи в своем приложении для Android. Из http://developer.android.com/training/material/drawables.html (выделено мной):

В Android 5.0 (уровень API 21) и выше вы можете определять векторные чертежи, которые масштабируются без потери определения.

Используя этот чертеж:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@color/colorPrimary" android:pathData="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />

и этот ImageView:

<ImageView
    android:layout_width="400dp"
    android:layout_height="400dp"
    android:src="@drawable/icon_bell"/>

создает это размытое изображение при попытке отобразить значок с разрешением 400 dp (на большом мобильном устройстве с высоким разрешением около 2015 года, на котором запущен леденец на палочке):

blurryBellIcon

Изменение ширины и высоты в определении вектора, доступного для рисования, на 200dp значительно улучшает ситуацию при отображаемом размере 400dp. Однако, если установить его как доступный для рисования для элемента TextView (то есть значок слева от текста), теперь создается огромный значок.

Мои вопросы:

1) Почему в векторном чертеже указана ширина / высота? Я думал, что все дело в том, что они масштабируются вверх и вниз без потерь, делая ширину и высоту бессмысленными в своем определении?

2) Можно ли использовать один вектор, который можно рисовать как 24dp в TextView, но хорошо масштабируется, чтобы использовать гораздо большие изображения? Например, как мне избежать создания нескольких векторных чертежей разных размеров и вместо этого использовать тот, который масштабируется в соответствии с моими требованиями к визуализации?

3) Как эффективно использовать атрибуты ширины / высоты и в чем разница с viewportWidth / Height?

Дополнительные детали:

  • Устройство работает под управлением API 22
  • Использование Android Studio v1.5.1 с Gradle версии 1.5.0
  • Манифест - это компиляция и целевой уровень 23, минимальный уровень 15. Я также пробовал переместить минимальный уровень на 21, но это не имело никакого значения.
  • Декомпиляция APK (с минимальным уровнем 21) показывает единственный ресурс XML в папке с возможностью переноса. Растровые изображения не создаются.
Крис Найт
источник
Просто быть чистым. Вы пользуетесь студией Android? Какая версия Android Studio и какая версия плагина Gradle? Когда я щелкаю правой кнопкой мыши папку с возможностью рисования в Android Studio и выбираю New -> Vector Assetее, в мою папку с возможностью рисования удаляется XML-файл векторного изображения. Однако если я использую apktool для распаковки созданного APK, я вижу, что файлы XML drawable-anydpi-v21правильно масштабируются на устройствах с API 21+. Растровые файлы помещаются в drawable-<mdpi/hdpi/etc>-v4папки и не используются на устройствах с API 21+ (на основании того факта, что они правильно масштабируются)
Кори Чарльтон,
@ Кори Чарльтон. Любопытно. Я использую Studio 1.5.1 с Gradle 1.5.0. После изменения минимального уровня на 21 единственное место, где изображение появляется в APK, - это drawableпапка. Ширина / высота в векторном XML-файле с возможностью рисования составляет 24dp. Указание ImageView с высотой / шириной 400dp определенно создает плохо масштабируемое изображение.
Крис Найт
Вот чего я ожидал. Единственная причина, по которой я получаю, drawable-anydpi-v21это то, что my minSdkVersionменьше 21. Какие-нибудь изменения в поведении, если вы установите minSdkVersionменьше 21? А как насчет переноса XML в drawable-anydpi? Я бы не ожидал, что что-то изменится, но я бы также ожидал, что ваше векторное изображение будет правильно масштабироваться ...
Кори Чарльтон,
Изменение min версии обратно на 15 дает те же результаты, что и вы. Один XML-файл drawable-anydpi-v21с различными растровыми изображениями в mdi / hdpi / etc. папки. Однако никаких изменений в конечном результате рендеринга не произошло.
Крис Найт
Очень странно ... Я изменил одно из своих приложений, используя ваше изображение, и оно отлично работает (см. Мой отредактированный ответ)
Кори Чарльтон,

Ответы:

101

Здесь есть новая информация об этой проблеме:

https://code.google.com/p/android/issues/detail?id=202019

Похоже, что использование android:scaleType="fitXY"позволит правильно масштабировать леденец на палочке.

От инженера Google:

Привет! Сообщите мне, может ли scaleType = 'fitXY' быть обходным решением для вас, чтобы изображение выглядело резким.

Зефир против леденца на палочке - это благодаря специальной обработке от шелушения, добавленной в маршмеллоу.

Также для ваших комментариев: «Правильное поведение: векторный чертеж должен масштабироваться без потери качества. Поэтому, если мы хотим использовать один и тот же актив в трех разных размерах в нашем приложении, нам не нужно дублировать vector_drawable.xml 3 раза с разными жестко запрограммированные размеры ".

Хотя я полностью согласен с тем, что это должно быть так, в действительности платформа Android имеет проблемы с производительностью, поэтому мы еще не достигли идеального мира. Поэтому на самом деле рекомендуется использовать 3 разных файла vector_drawable.xml для повышения производительности, если вы уверены, что хотите рисовать на экране 3 разных размера одновременно.

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

Тэхён Пак
источник
27
Окей, Google, совершенно ужасно, что мы должны копировать и вставлять идентичные чертежи с одинаковыми окнами просмотра и путями, которые имеют только разные размеры.
Miha_x64
Это исправило его на устройстве, на котором мой логотип отображался пиксельным, но на другом устройстве, где раньше все было в порядке, теперь неправильное соотношение сторон. Не понимаю почему это проблема, это вектор! Не пиксели! : P
tplive
@Harish Gyanani, android: scaleType = "fitXY" решает проблему, но как насчет динамических, а не квадратных значков?
Мануэла
29

1) Почему в векторном чертеже указана ширина / высота? Я думал, что все дело в том, что они масштабируются вверх и вниз без потерь, делая ширину и высоту бессмысленными в своем определении?

Для версий SDK менее 21, когда системе сборки необходимо генерировать растровые изображения, и в качестве размера по умолчанию в случаях, когда вы не указываете ширину / высоту.

2) Можно ли использовать один векторный рисунок, который работает как рисунок 24dp в TextView, а также изображение с большой шириной экрана?

Я не верю, что это возможно, если вам также нужно настроить таргетинг на SDK меньше 21.

3) Как эффективно использовать атрибуты ширины / высоты и в чем разница с viewportWidth / Height?

Документация: (на самом деле не очень полезно сейчас, когда я ее перечитал ...)

android:width

Используется для определения внутренней ширины объекта для рисования. Это поддерживает все единицы измерения, обычно указываемые с помощью dp.

android:height

Используется для определения внутренней высоты чертежа. Это поддерживает все единицы измерения, обычно указываемые с помощью dp.

android:viewportWidth

Используется для определения ширины области просмотра. Область просмотра - это, по сути, виртуальный холст, на котором нарисованы пути.

android:viewportHeight

Используется для определения высоты области просмотра. Область просмотра - это, по сути, виртуальный холст, на котором нарисованы пути.

Дополнительная документация:

Android 4.4 (уровень API 20) и ниже не поддерживает векторные чертежи. Если ваш минимальный уровень API установлен на одном из этих уровней API, Vector Asset Studio также предписывает Gradle генерировать растровые изображения вектора, которые можно рисовать для обратной совместимости. Вы можете ссылаться на векторные ресурсы как Drawableв коде Java или @drawableв коде XML; когда ваше приложение запускается, соответствующее векторное или растровое изображение отображается автоматически в зависимости от уровня API.


Изменить: происходит что-то странное. Вот мои результаты в эмуляторе SDK версии 23 (тестовое устройство Lollipop + сейчас мертво ...):

Хорошие звонки на 6.x

И в эмуляторе SDK версии 21:

Дерьмовые колокольчики на 5.x

Кори Чарльтон
источник
1
Спасибо, Кори, я видел эту документацию и тоже нашел ее не очень полезной. Мое устройство - Lollipop (v22), и все же я не могу добиться хорошего масштабирования графики выше 24dp, указанного в векторном чертеже, независимо от того, точно ли указаны высота / вес в ImageView или нет. Я протестировал манифест с целевым и скомпилированным уровнями 23 и минимальным уровнем 21, но он не будет плавно повышать масштаб моего векторного рисования без изменения ширины / высоты в самом рисованном элементе. Я действительно не хочу создавать несколько чертежей, единственная разница которых - высота / ширина!
Крис Найт,
1
Спасибо за подтверждение и очень признательны за вашу помощь. Как вы продемонстрировали, он явно должен работать так, как я ожидал. Использование вашего точного кода дает тот же результат, что и у меня изначально. Обидно!
Крис Найт
1
ОБНОВЛЕНИЕ: прогнал мой код против эмулятора с API 22, и я все равно получил тот же результат. Проверил мой код против эмулятора с API 23 и, тада !, он работает. Похоже, апскейлинг работает только на устройствах с API 23+. Пытался изменить целевую версию SDK, но это не имело значения.
Крис Найт
Вы нашли решение этой проблемы?
dazza5000 08
@corycharlton можно ли удалить width height viewport heightи widthоткуда xml?
VINNUSAURUS
9

AppCompat 23.2 представляет векторные чертежи для всех устройств под управлением Android 2.1 и выше. Изображения масштабируются правильно, независимо от ширины / высоты, указанной в XML-файле векторного чертежа. К сожалению, эта реализация не используется в API уровня 21 и выше (в пользу собственной реализации).

Хорошая новость заключается в том, что мы можем заставить AppCompat использовать его реализацию на уровнях API 21 и 22. Плохая новость в том, что для этого мы должны использовать отражение.

Прежде всего, убедитесь, что вы используете последнюю версию AppCompat и включили новую реализацию вектора:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

dependencies {
    compile 'com.android.support:appcompat-v7:23.2.0'
}

Затем вызовите useCompatVectorIfNeeded();onCreate () вашего приложения:

private void useCompatVectorIfNeeded() {
    int sdkInt = Build.VERSION.SDK_INT;
    if (sdkInt == 21 || sdkInt == 22) { //vector drawables scale correctly in API level 23
        try {
            AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
            Class<?> inflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
            Class<?> vdcInflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");

            Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object vdcInflateDelegate = constructor.newInstance();

            Class<?> args[] = {String.class, inflateDelegateClass};
            Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
            addDelegate.setAccessible(true);
            addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Наконец, убедитесь, что вы используете app:srcCompatвместо android:srcустановки изображения ImageView:

<ImageView
    android:layout_width="400dp"
    android:layout_height="400dp"
    app:srcCompat="@drawable/icon_bell"/>

Ваши векторные чертежи теперь должны правильно масштабироваться!

user3210008
источник
Хороший подход, но AppCompat 25.4 (и, возможно, ранее) выдает ошибку lint «может быть вызван только из той же группы библиотек». Инструменты сборки, которые я использую, по-прежнему позволяют ему строить, но я не уверен, будут ли последствия во время выполнения. Собираюсь проверить.
androidguy 07
может быть вызван только из той же группы библиотек, удалите эту ошибку, добавив следующее: lintOptions {disable 'RestrictedApi'}
Усама Саид США
6

1) Почему в векторном чертеже указана ширина / высота? Я думал, что все дело в том, что они масштабируются вверх и вниз без потерь, делая ширину и высоту бессмысленными в своем определении?

Это просто размер вектора по умолчанию, если вы не определяете его в виде макета. (т.е. вы используете обертку для высоты и ширины вашего изображения)

2) Можно ли использовать один векторный рисунок, который работает как рисунок 24dp в TextView, а также изображение с большой шириной экрана?

Да, это возможно, и у меня не было проблем с изменением размера, пока работающее устройство использует леденец или выше. В предыдущих API-интерфейсах вектор конвертируется в png для разных размеров экрана при создании приложения.

3) Как эффективно использовать атрибуты ширины / высоты и в чем разница с viewportWidth / Height?

Это влияет на использование пространства вокруг вашего вектора. т.е. вы можете использовать его, чтобы изменить "гравитацию" вектора внутри области просмотра, сделать векторным поле по умолчанию, оставить определенные части вектора вне области просмотра и т. д. Обычно вы просто устанавливаете их одинаковыми размер.

Эмируа
источник
Спасибо, Эмиура. Мне было бы интересно узнать, как вы достигли нескольких размеров отрисовки, используя одну и ту же возможность рисования. Используя рисование 24dp (для тестовых целей), я изменил свой ImageView на указанную ширину и высоту 400dp и работал на моем устройстве Lollipop (v22), но он по-прежнему плохо отображается, как растровое увеличенное изображение, а не векторное изображение. Также изменил мой манифест, чтобы иметь цель и компилировать против v23 и min версии 21. Никакой разницы.
Крис Найт
В этом случае вам нужно будет использовать только самый большой размер в вашем векторном чертеже, а затем указать размер 24dp в текстовом представлении.
emirua
Теперь я вижу, что вы получаете размытые изображения при увеличении с очень большой разницей между размером в векторном чертеже и размером в макете в Lollipop. Тем не менее, гораздо удобнее просто установить самый большой размер, а не иметь кучу файлов для разных размеров экрана;).
emirua
4

Я решаю свою проблему, просто меняя размер векторного изображения. С самого начала это был ресурс вроде:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300"
    android:viewportWidth="300">

И я изменил его на 150dp (размер ImageView в макете с этим векторным ресурсом), например:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="150dp"
    android:height="150dp"
    android:viewportHeight="300"
    android:viewportWidth="300">

У меня это работает.

Джек-Гриф
источник
спасибо, этого достаточно, чтобы решить проблему на моем планшете, где он был пиксельным.
user2342558
3

Я пробую эти способы, но, к сожалению, ни один из них не сработал. Я стараюсь fitXYв ImageView, я попробовать использовать , app:srcCompatно не могу даже использовать его. но я нашел хитрый способ:

установите Drawable в backgroundсвой ImageView.

Но суть в этом способе - размеры просмотров. если у вас ImageViewнеправильные размеры, drawable получает Stretch. вы должны контролировать его в версиях, предшествующих леденцу, или использовать некоторые представления PercentRelativeLayout.

Надеюсь, это поможет.

Махди Астаней
источник
2

Во-первых : импортируйте svg в drawble: (( https://www.learn2crack.com/2016/02/android-studio-svg.html ))

1-Щелкните правой кнопкой мыши папку с возможностью переноса и выберите New -> Vector Asset.

введите описание изображения здесь

2- Vector Asset Studio предоставляет вам возможность выбрать встроенные значки материалов или ваш собственный локальный файл svg. При необходимости вы можете изменить размер по умолчанию.

введите описание изображения здесь

Во-вторых : если вам нужна поддержка со стороны Android API 7+, вам необходимо использовать запись библиотеки поддержки Android (( https://stackoverflow.com/a/44713221/1140304 ))

1- добавить

vectorDrawables.useSupportLibrary = true

в ваш файл build.gradle.

2-Используйте пространство имен в вашем макете, в котором есть imageView:

xmlns:app="http://schemas.android.com/apk/res-auto"

В-третьих : установите imageView с android: scaleType = "fitXY" и используйте app: srcCompat

 <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        app:srcCompat="@drawable/ic_menu" />

В-четвертых : если вы хотите установить svg в java-код, вам нужно добавить строку ниже при oncreate methoded

 @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
Милад Ахмади
источник
Ваше решение работает с API 21 и выше? user3210008 выше упоминает, что реализация не используется в API 21 и выше.
AJW
2

Для меня причиной преобразования векторов в png был android:fillType="evenodd"атрибут в моем векторе. Когда я удалил все вхождения этого атрибута в моем чертеже (для этого использовался nonzeroтип заливки по умолчанию, примененный к вектору), в следующий раз, когда приложение было построено, все было в порядке.

Махозад
источник