Чому моє масштабоване векторне масштабування не очікується?


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"/>

створює це розмите зображення при спробі відобразити піктограму з роздільною здатністю 400dp (на великому мобільному пристрої високої роздільної здатності близько 2015 року, на якому працює льодяник):

blurryBellIcon

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

Мої запитання:

1) Чому у малюваному векторі є специфікація ширини / висоти? Я думав, що вся суть цього полягає в тому, що вони масштабуються вгору і вниз без втрат, роблячи ширину та висоту безглуздими у своєму визначенні?

2) Чи можна використовувати один векторний малюнок, який працює як 24dp, що малюється на TextView, але добре масштабується, щоб використовувати також набагато більші зображення? Наприклад, як я можу уникнути створення кількох векторних малюнків різних розмірів і замість цього використовувати той, який масштабується відповідно до моїх відтворених вимог?

3) Як ефективно використовувати атрибути width / height та яка різниця з viewportWidth / Height?

Додаткові деталі:

  • На пристрої працює API 22
  • Використання Android Studio v1.5.1 з Gradle версії 1.5.0
  • Маніфест - це компіляція та цільовий рівень 23, мінімальний рівень 15. Я також намагався перенести мінімальний рівень до 21, але це не мало ніякої різниці.
  • Декомпіляція файлу .apk (з мінімальним рівнем, встановленим на 21) показує один XML-ресурс у папці, яку можна малювати. Растеризовані зображення не створюються.

Просто щоб було зрозуміло. Ви використовуєте Android studio? Яка версія 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 висотою / шириною 400 дп, безумовно, створює погано масштабоване зображення.
Chris Knight

Ось чого я очікував би. Єдина причина, через яку я закінчую, - drawable-anydpi-v21це те, що моя minSdkVersionменше 21. Будь-яка зміна поведінки, якщо ви встановите значення minSdkVersionменше 21? А як щодо переміщення XML drawable-anydpi? Я б не сподівався, що відбудуться зміни, але я також би очікував, що ваше векторне зображення буде правильно масштабовано ...
Кори Чарльтон

Зміна мінімальної версії до 15 дає ті самі результати, що і ви. Один файл xml drawable-anydpi-v21із різними растровими зображеннями в mdi / hdpi / тощо. папки. Хоча жодних змін до кінцевого результату не було.
Chris Knight

Дуже дивно ... Я модифікував один із своїх додатків, використовуючи ваше зображення, і він чудово працює (див. Мою відредаговану відповідь)
Кори Чарльтон,

Відповіді:


100

Тут є нова інформація про цю проблему:

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

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

Від інженера Google:

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

Зефір проти льодяника завдяки спеціальній обробці, що додається до зефіру.

Крім того, для ваших коментарів: "Правильна поведінка: вектор, який можна малювати, повинен масштабуватися без втрати якості. Отже, якщо ми хочемо використовувати один і той самий об’єкт у 3 різних розмірах у нашому додатку, нам не потрібно дублювати vector_drawable.xml 3 рази з різними жорстко закодовані розміри ".

Незважаючи на те, що я повністю згоден, це має бути так, насправді платформа Android має проблеми з продуктивністю, так що ми ще не досягли ідеального світу. Тож насправді рекомендується використовувати 3 різні vector_drawable.xml для кращої роботи, якщо ви впевнені, що хочете намалювати на екрані одночасно 3 різних розміри.

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


27
Гаразд, Google, абсолютно страшно, що ми повинні копіювати-вставляти однакові малюнки з однаковими вікнами перегляду та шляхами, які мають лише різні розміри.
Miha_x64

Це зафіксувало на пристрої, який показував мій логотип пікселізовано, але на іншому блоці, де раніше все було добре, зараз неправильне співвідношення сторін. Я не розумію, чому це проблема, це вектор! Не пікселі! : P
tplive

@Harish Gyanani, android: scaleType = "fitXY" вирішує проблему, а як щодо динамічних і не квадратних піктограм?
Мануела

28

1) Чому у малюваному векторі є специфікація ширини / висоти? Я думав, що вся суть цього полягає в тому, що вони масштабуються вгору і вниз без втрат, роблячи ширину та висоту безглуздими у своєму визначенні?

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

2) Чи можна використовувати один векторний малюнок, який працює як 24dp, що малюється на TextView, а також велике зображення біля екрану?

Я не вірю, що це можливо, якщо вам також потрібно націлити SDK менше 21.

3) Як ефективно використовувати атрибути width / height та яка різниця з viewportWidth / Height?

Документація: (насправді не дуже корисна зараз, коли я перечитала її ...)

android:width

Використовується для визначення внутрішньої ширини малюваного. Це підтримує всі одиниці вимірювання, зазвичай вказані з dp.

android:height

Використовується для визначення внутрішньої висоти, що малюється. Це підтримує всі одиниці вимірювання, зазвичай вказані з dp.

android:viewportWidth

Використовується для визначення ширини простору області перегляду. Viewport - це в основному віртуальне полотно, на якому намальовані шляхи.

android:viewportHeight

Використовується для визначення висоти простору області перегляду. Viewport - це в основному віртуальне полотно, на якому намальовані шляхи.

Більше документації:

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, проте він не буде плавно нарощувати мій векторний малюнок, не змінюючи ширину / висоту в самому малюваному. Я не дуже хочу створювати кілька малюнків, які відрізняються лише висотою / шириною!
Chris Knight

1
Дякуємо за підтвердження та щиро вдячні за вашу допомогу. Як ви продемонстрували, це, очевидно, має працювати, як я очікую. Використання вашого точного коду дає той самий результат, що і у мене спочатку. Розчарування!
Chris Knight

1
ОНОВЛЕННЯ: Пропонував мій код емулятору за допомогою API 22, і я все одно отримую той самий результат. Порівняв мій код із емулятором за допомогою API 23, і, тоді !, він працює. Схоже, модернізація працює лише на пристроях API 23+. Спробував змінити цільову версію SDK, але це не мало значення.
Chris Knight

Ви знайшли рішення цього питання?
dazza5000

@corycharlton це нормально видаляти width height viewport heightі widthз xml?
VINNUSAURUS

9

AppCompat 23.2 представляє векторні малюнки для всіх пристроїв під управлінням Android 2.1 та новіших версій. Зображення правильно масштабуються, незалежно від ширини / висоти, зазначених у файлі XML векторного малюнка. На жаль, ця реалізація не використовується на рівні API 21 та вище (на користь власної реалізації).

Хороша новина полягає в тому, що ми можемо змусити AppCompat використовувати його реалізацію на рівнях 21 і 22. API. Погана новина полягає в тому, що для цього нам потрібно використовувати рефлексію.

Перш за все, переконайтеся, що ви користуєтесь останньою версією 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"/>

Ваші векторні малюнки тепер повинні масштабуватися правильно!


Хороший підхід, але AppCompat 25.4 (і, можливо, раніше) видає помилку, що "можна викликати лише з тієї ж групи бібліотек". Інструменти побудови, які я використовую, все ще дозволяють йому будуватися, але я не впевнений, чи є наслідки виконання. Про тестування.
androidguy

можна викликати лише з тієї ж групи бібліотек, видаліть цю помилку, додавши наступне: lintOptions {вимкнути 'RestrictedApi'}
Усама Саїд, США

6

1) Чому у малюваному векторі є специфікація ширини / висоти? Я думав, що вся суть цього полягає в тому, що вони масштабуються вгору і вниз без втрат, роблячи ширину та висоту безглуздими у своєму визначенні?

Це лише розмір вектора за замовчуванням, якщо ви не визначите його у поданні макета. (тобто ви використовуєте вміст обтікання для висоти та ширини перегляду зображення)

2) Чи можна використовувати один векторний малюнок, який працює як 24dp, що малюється на TextView, а також велике зображення біля екрану?

Так, це можливо, і у мене не виникало жодних проблем зі зміною розміру, поки працюючий пристрій використовує льодяник або вище. У попередніх API, вектор перетворюється у PNG для різних розмірів екрану під час створення програми.

3) Як ефективно використовувати атрибути width / height та яка різниця з 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.

Але сенс цього шляху - розміри Views. якщо у вас ImageViewнеправильні розміри, витягуваний отримує Stretch. ви повинні керувати ним у версіях до льодяника або використовувати деякі подання PercentRelativeLayout.

Сподіваюся, це корисно.


2

Спочатку : імпортуйте svg у малюнок: (( https://www.learn2crack.com/2016/02/android-studio-svg.html ))

1-Клацніть правою кнопкою миші на папці, що малюється, виберіть Створити -> Векторний актив

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

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 methoed

 @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"атрибут у моєму векторному малюнку. Коли я видалив усі випадки появи цього атрибуту у своєму drawable (для цього тип nonzeroзаливки за замовчуванням застосовується до вектора), наступного разу, коли додаток будувався, все було нормально.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.