Dlaczego moje skalowanie do rysowania wektorów nie jest zgodne z oczekiwaniami?

97

Próbuję użyć rysunków wektorowych w mojej aplikacji na Androida. Z http://developer.android.com/training/material/drawables.html (moje wyróżnienie):

W systemie Android 5.0 (poziom interfejsu API 21) i nowszych można zdefiniować elementy wektorowe, które skalują się bez utraty definicji.

Korzystanie z tego do rysowania:

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

i ten ImageView:

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

powoduje wyświetlenie tego rozmytego obrazu podczas próby wyświetlenia ikony w rozdzielczości 400 dp (na dużym urządzeniu mobilnym o wysokiej rozdzielczości z około 2015 r., na którym działa Lollipop):

blurryBellIcon

Zmiana szerokości i wysokości w definicji wektora rysowanego do 200dp znacząco poprawia sytuację przy wyrenderowanym rozmiarze 400dp. Jednak ustawienie tego jako możliwego do rysowania dla elementu TextView (tj. Ikony po lewej stronie tekstu) tworzy teraz ogromną ikonę.

Moje pytania:

1) Dlaczego w wektorze do rysowania istnieje specyfikacja szerokości / wysokości? Myślałem, że chodzi o to, że skalują się w górę iw dół bezstratnie, przez co szerokość i wysokość nie mają znaczenia w swojej definicji?

2) Czy możliwe jest użycie pojedynczego wektora do rysowania, który działa jako rysowany w formacie 24dp na TextView, ale dobrze skaluje się, aby używać również znacznie większych obrazów? Np. Jak uniknąć tworzenia wielu rysunków wektorowych o różnych rozmiarach i zamiast tego użyć takiego, który skaluje się do moich renderowanych wymagań?

3) Jak efektywnie wykorzystać atrybuty szerokości / wysokości i jaka jest różnica z viewportWidth / Height?

Dodatkowe Szczegóły:

  • Na urządzeniu działa API 22
  • Korzystanie z Android Studio w wersji 1.5.1 z Gradle w wersji 1.5.0
  • Manifest jest kompilacją i docelowym poziomem 23, minimalnym poziomem 15. Próbowałem również przenieść minimalny poziom na 21, ale to nie miało znaczenia.
  • Dekompilacja APK (z minimalnym poziomem ustawionym na 21) pokazuje pojedynczy zasób XML w folderze do rysowania. Nie są tworzone żadne obrazy rasteryzowane.
Chris Knight
źródło
Żeby było jasne. Używasz Android Studio? Jaka wersja Android Studio i jaka wersja wtyczki Gradle? Kiedy klikam prawym przyciskiem myszy folder do rysowania w Android Studio i wybieram New -> Vector Asset, upuszczam XML obrazu wektorowego do mojego folderu do rysowania. Jeśli jednak używam apktool do rozpakowywania kompilowanego pliku APK, widzę, że pliki XML są drawable-anydpi-v21zapisane i poprawnie skalują na urządzeniach z interfejsem API 21+. Pliki rastrowe są umieszczone w drawable-<mdpi/hdpi/etc>-v4folderach i nie są wykorzystywane w urządzeniach API 21+ (na podstawie faktu, że skala ich poprawnie)
Cory Charlton
@Cory Charlton. Ciekawy. Używam Studio 1.5.1 z Gradle 1.5.0. Po zmianie poziomu min na 21 jedynym miejscem, w którym obraz pojawia się w pliku APK, jest drawablefolder. Szerokość / wysokość w wektorowym pliku XML z możliwością rysowania wynosi 24 dp. Określenie ImageView o wysokości / szerokości 400 dp zdecydowanie tworzy słabo skalowany obraz.
Chris Knight
Tego bym się spodziewał. Jedynym powodem, dla którego kończę z tym drawable-anydpi-v21jest to, że mój minSdkVersionjest mniejszy niż 21. Jakakolwiek zmiana w zachowaniu, jeśli ustawisz minSdkVersionmniej niż 21? A co z przeniesieniem XML do drawable-anydpi? Nie spodziewałbym się zmiany, ale spodziewałbym się również, że twój obraz wektorowy będzie poprawnie skalowany ...
Cory Charlton
Zmiana wersji min z powrotem na 15 daje takie same wyniki, jak Ty. Pojedynczy plik xml drawable-anydpi-v21z różnymi rasteryzowanymi obrazami w mdi / hdpi / etc. lornetka składana. Brak zmian w końcowym wyniku renderowania.
Chris Knight
Bardzo dziwne ... Zmodyfikowałem jedną z moich aplikacji przy użyciu twojego obrazu i działa dobrze (zobacz moją zredagowaną odpowiedź)
Cory Charlton

Odpowiedzi:

102

Tutaj są nowe informacje na temat tego problemu:

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

Wygląda na to, że użycie android:scaleType="fitXY"spowoduje poprawne skalowanie w Lollipopie.

Od inżyniera Google:

Cześć, daj mi znać, jeśli scaleType = 'fitXY' może być dla Ciebie obejściem, aby uzyskać ostry obraz.

Prawoślaz lekarski Vs Lollipop jest efektem specjalnego zabiegu złuszczającego dodanego do ptasie mleczko.

Również dla twoich komentarzy: „Prawidłowe zachowanie: wektor, który można rysować, powinien skalować się bez utraty jakości. Więc jeśli chcemy użyć tego samego zasobu w 3 różnych rozmiarach w naszej aplikacji, nie musimy 3 razy powtarzać vector_drawable.xml z różnymi rozmiary zakodowane na stałe ”.

Chociaż całkowicie się zgadzam, że tak powinno być, w rzeczywistości platforma Android ma problemy z wydajnością, tak że nie dotarliśmy jeszcze do idealnego świata. Dlatego faktycznie zaleca się użycie 3 różnych vector_drawable.xml, aby uzyskać lepszą wydajność, jeśli na pewno chcesz narysować na ekranie 3 różne rozmiary w tym samym czasie.

Szczegóły techniczne są takie, że używamy bitmapy pod hakiem do buforowania złożonego renderowania ścieżki, tak abyśmy mogli uzyskać najlepszą wydajność przerysowywania, na równi z przerysowywaniem mapy bitowej do rysowania.

Park Taehyun
źródło
27
Dobra Google, to absolutnie okropne, że musimy kopiować i wklejać identyczne rysunki z tymi samymi rzutniami i ścieżkami, które mają tylko różne rozmiary.
Miha_x64
Naprawiło to na urządzeniu, które wyświetlało moje logo w pikselach, ale na drugiej jednostce, gdzie wcześniej wszystko było w porządku, teraz ma zły współczynnik proporcji. Nie rozumiem, dlaczego to jest problem, to wektor! Nie piksele! : P
tplive
@Harish Gyanani, android: scaleType = "fitXY" rozwiązuje problem, ale co z ikonami dynamicznymi, a nie kwadratowymi?
Manuela,
29

1) Dlaczego w wektorze do rysowania istnieje specyfikacja szerokości / wysokości? Myślałem, że chodzi o to, że skalują się w górę iw dół bezstratnie, przez co szerokość i wysokość nie mają znaczenia w swojej definicji?

W przypadku wersji SDK mniejszych niż 21, w których system kompilacji musi generować obrazy rastrowe i jako domyślny rozmiar w przypadkach, w których nie określono szerokości / wysokości.

2) Czy możliwe jest użycie pojedynczego wektora do rysowania, który działa jako rysowany w trybie 24dp na TextView, a także dużego obrazu o szerokości bliskiej ekranu?

Nie wierzę, że jest to możliwe, jeśli chcesz również kierować reklamy na zestawy SDK mniejsze niż 21.

3) Jak efektywnie wykorzystać atrybuty szerokości / wysokości i jaka jest różnica z viewportWidth / Height?

Dokumentacja: (właściwie niezbyt przydatna teraz, gdy ją ponownie przeczytałem ...)

android:width

Służy do definiowania wewnętrznej szerokości elementu rysunkowego. Obsługuje wszystkie jednostki wymiarowe, zwykle określone w dp.

android:height

Służy do określenia wewnętrznej wysokości wyciąganej. Obsługuje wszystkie jednostki wymiarowe, zwykle określone w dp.

android:viewportWidth

Służy do definiowania szerokości obszaru rzutni. Rzutnia to w zasadzie wirtualne płótno, na którym rysowane są ścieżki.

android:viewportHeight

Służy do definiowania wysokości obszaru rzutni. Rzutnia to w zasadzie wirtualne płótno, na którym rysowane są ścieżki.

Więcej dokumentacji:

Android 4.4 (poziom API 20) i niższy nie obsługuje rysunków wektorowych. Jeśli minimalny poziom interfejsu API jest ustawiony na jednym z tych poziomów interfejsu API, Vector Asset Studio również kieruje Gradle do generowania obrazów rastrowych wektorów, które można rysować w celu zapewnienia zgodności z poprzednimi wersjami. Możesz odwoływać się do zasobów wektorowych, tak jak Drawablew kodzie Java lub @drawablew kodzie XML; po uruchomieniu aplikacji odpowiedni obraz wektorowy lub rastrowy jest wyświetlany automatycznie w zależności od poziomu interfejsu API.


Edycja: dzieje się coś dziwnego. Oto moje wyniki w emulatorze SDK w wersji 23 (urządzenie testowe Lollipop + jest teraz martwe ...):

Dobre dzwony na 6.x

A w emulatorze SDK w wersji 21:

Brzydkie dzwony na 5.x

Cory Charlton
źródło
1
Dzięki Cory, widziałem tę dokumentację i również uznałem ją za niezbyt przydatną. Moje urządzenie to Lollipop (v22), a mimo to nie mogę dobrze skalować grafiki, powyżej 24dp określonego w wektorze do rysowania, niezależnie od tego, czy wysokość / waga są dokładnie określone w ImageView, czy nie. Przetestowałem manifest z docelowym i kompilacyjnym poziomem 23 i minimalnym poziomem 21, ale nie będzie on płynnie skalować mojego wektora do rysowania bez zmiany szerokości / wysokości w samym rysunku. Naprawdę nie chcę tworzyć wielu obiektów do rysowania, których jedyną różnicą jest wysokość / szerokość!
Chris Knight
1
Dziękuję za potwierdzenie i bardzo dziękuję za pomoc. Jak wykazałeś, powinno to działać tak, jak tego oczekuję. Użycie dokładnego kodu daje taki sam wynik jak pierwotnie. Denerwujący!
Chris Knight
1
AKTUALIZACJA: Uruchomiłem kod na emulatorze z API 22 i nadal otrzymuję ten sam wynik. Uruchomiłem mój kod na emulatorze z API 23 i, tada !, działa. Wygląda na to, że skalowanie działa tylko na urządzeniach z interfejsem API 23+. Próbowałem zmienić docelową wersję SDK, ale to nie miało znaczenia.
Chris Knight
Czy znalazłeś rozwiązanie tego problemu?
dazza5000
@corycharlton to jest w porządku, aby usunąć width height viewport heighti widthod xml?
VINNUSAURUS
10

AppCompat 23.2 wprowadza rysunki wektorowe dla wszystkich urządzeń z systemem Android 2.1 lub nowszym. Obrazy są skalowane prawidłowo, niezależnie od szerokości / wysokości określonej w pliku XML elementu wektorowego do rysowania. Niestety ta implementacja nie jest używana na poziomie API 21 i wyższym (na korzyść implementacji natywnej).

Dobra wiadomość jest taka, że ​​możemy zmusić AppCompat do używania jego implementacji na poziomach API 21 i 22. Zła wiadomość jest taka, że ​​musimy użyć do tego refleksji.

Przede wszystkim upewnij się, że używasz najnowszej wersji AppCompat i że masz włączoną nową implementację wektorów:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

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

Następnie wywołaj useCompatVectorIfNeeded();onCreate () swojej aplikacji:

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();
        }
    }
}

Na koniec upewnij się, że używasz app:srcCompatzamiast android:srcustawiania obrazu ImageView:

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

Twoje rysunki wektorowe powinny teraz poprawnie skalować się!

user3210008
źródło
Dobre podejście, ale AppCompat 25.4 (i być może wcześniej) wyświetla błąd lint „można wywołać tylko z tej samej grupy bibliotek”. Narzędzia do kompilacji, których używam, nadal pozwalają na kompilację, ale nie jestem pewien, czy istnieją konsekwencje działania. Zaraz przetestuję.
androidguy
można wywołać tylko z tej samej grupy bibliotek, usuń ten błąd, dodając: lintOptions {wyłącz 'RestrictedApi'}
Usama Saeed US
6

1) Dlaczego w wektorze do rysowania istnieje specyfikacja szerokości / wysokości? Myślałem, że chodzi o to, że skalują się w górę iw dół bezstratnie, przez co szerokość i wysokość nie mają znaczenia w swojej definicji?

To tylko domyślny rozmiar wektora na wypadek, gdybyś nie zdefiniował go w widoku układu. (tj. używasz zawijania zawartości dla wysokości i szerokości widoku obrazu)

2) Czy możliwe jest użycie pojedynczego wektora do rysowania, który działa jako rysowany w trybie 24dp na TextView, a także dużego obrazu o szerokości bliskiej ekranu?

Tak, jest to możliwe i nie miałem żadnego problemu ze zmianą rozmiaru, o ile uruchomione urządzenie korzysta z Lollipopa lub nowszego. W poprzednich interfejsach API wektor jest konwertowany na pliki PNG dla różnych rozmiarów ekranu podczas tworzenia aplikacji.

3) Jak efektywnie wykorzystać atrybuty szerokości / wysokości i jaka jest różnica z viewportWidth / Height?

Wpływa to na sposób wykorzystania przestrzeni wokół wektora. tzn. możesz go użyć do zmiany "grawitacji" wektora wewnątrz rzutni, sprawić, by wektor miał domyślny margines, zostawić pewne części wektora poza rzutnią itd. Normalnie po prostu ustawiasz je na takie same rozmiar.

emirua
źródło
Dzięki Emiura. Byłbym zainteresowany tym, w jaki sposób uzyskasz wiele renderowanych rozmiarów przy użyciu tego samego elementu do rysowania. Używając 24dp drawable (do celów testowych), zmieniłem mój ImageView, aby miał określoną szerokość i wysokość 400dp i działał na moim urządzeniu Lollipop (v22), ale nadal renderuje się słabo, jak rastrowany przeskalowany obraz, a nie obraz wektorowy. Zmieniono także mój manifest, aby zawierał cel i kompilację względem wersji 23 i min 21. Bez różnicy.
Chris Knight
W takim przypadku wystarczy użyć największego rozmiaru w swoim wektorze do rysowania, a następnie określić rozmiar 24 dp w widoku tekstu.
emirua
Widzę teraz, że po powiększeniu otrzymujesz rozmyte obrazy z bardzo dużymi różnicami między rozmiarem w wektorze, który można narysować, a rozmiarem w układzie w Lollipop. Jednak nadal jest o wiele bardziej zgrabne po prostu ustawienie największego rozmiaru, zamiast mieć kilka plików dla różnych rozmiarów ekranu;).
emirua
4

Rozwiązuję swój problem po prostu zmieniam rozmiar obrazu wektorowego. Od początku był to zasób:

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

I zmieniłem to na 150dp (rozmiar ImageView w układzie z tym zasobem wektorowym), na przykład:

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

Na mnie to działa.

Djek-Grif
źródło
dzięki, to wystarczy, aby rozwiązać problem na moim tablecie, gdzie pojawił się piksel.
user2342558
3

Muszę próbować tych sposobów, ale niestety żaden z nich nie zadziałał. Staram fitXYsię ImageView, staram używając app:srcCompatale nawet nie można go używać. ale znalazłem podstępny sposób:

ustaw Drawable to backgroundof your ImageView.

Ale celem tej drogi są wymiary widoków. jeśli masz ImageViewnieprawidłowe wymiary, element do rysowania zostanie rozciągnięty. musisz kontrolować to w wersjach wcześniejszych niż Lollipop lub Użyj niektórych widoków PercentRelativeLayout.

Mam nadzieję, że to pomocne.

Mahdi Astanei
źródło
2

Po pierwsze : zaimportuj svg do rysowania: (( https://www.learn2crack.com/2016/02/android-studio-svg.html ))

1 Kliknij prawym przyciskiem myszy folder do rysowania i wybierz Nowy -> Zasób wektorowy

wprowadź opis obrazu tutaj

2- Vector Asset Studio zapewnia opcję wyboru wbudowanych ikon materiałów lub własnego lokalnego pliku svg. W razie potrzeby możesz zastąpić domyślny rozmiar.

wprowadź opis obrazu tutaj

Po drugie : jeśli chcesz uzyskać wsparcie z Android API 7+, musisz użyć nagrania Android Support Library (( https://stackoverflow.com/a/44713221/1140304 ))

1- dodaj

vectorDrawables.useSupportLibrary = true

do pliku build.gradle.

2-Użyj przestrzeni nazw w układzie zawierającym imageView:

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

Po trzecie : ustaw imageView w android: scaleType = "fitXY" i użyj app: srcCompat

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

Po czwarte : jeśli chcesz ustawić svg w kodzie java, musisz dodać poniższy wiersz na oncreate methoed

 @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
Milad Ahmadi
źródło
Czy Twoje rozwiązanie działa na API 21 i nowszych? user3210008 powyżej wspomina, że ​​implementacja nie jest używana w API 21 i nowszych.
AJW,
2

Dla mnie powodem, dla którego wektory były konwertowane na png, był android:fillType="evenodd"atrybut w moim wektorze do rysowania. Kiedy usunąłem wszystkie wystąpienia tego atrybutu z mojego rysunku (w związku z tym domyślny nonzerotyp wypełnienia zastosowany do wektora), następnym razem, gdy aplikacja została zbudowana, wszystko było w porządku.

Mahozad
źródło