Por que meu drawable vetorial não está dimensionado conforme o esperado?

97

Estou tentando usar drawables vetoriais em meu aplicativo Android. De http://developer.android.com/training/material/drawables.html (ênfase minha):

No Android 5.0 (API de nível 21) e superior, você pode definir drawables vetoriais, que são escalonados sem perder a definição.

Usando este drawable:

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

e este ImageView:

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

produz esta imagem borrada ao tentar exibir o ícone em 400 dp (em um dispositivo móvel grande de alta resolução por volta de 2015 executando pirulito):

blurryBellIcon

Alterar a largura e a altura na definição do drawable vetorial para 200 dp melhora significativamente a situação no tamanho renderizado de 400 dp. No entanto, definir isso como um drawable para um elemento TextView (ou seja, o ícone à esquerda do texto) agora cria um ícone enorme.

Minhas perguntas:

1) Por que há uma especificação de largura / altura no drawable vetorial? Eu pensei que o ponto principal disso é que eles aumentam e diminuem sem perdas, tornando largura e altura sem sentido em sua definição.

2) É possível usar um único drawable vetorial que funciona como um drawable de 24 dp em um TextView, mas é bem dimensionado para usar imagens muito maiores também? Por exemplo, como evito criar vários drawables vetoriais de tamanhos diferentes e, em vez disso, uso um que se adapte aos meus requisitos de renderização?

3) Como uso efetivamente os atributos de largura / altura e qual é a diferença com viewportWidth / Height?

Detalhes adicionais:

  • O dispositivo está executando API 22
  • Usando o Android Studio v1.5.1 com Gradle versão 1.5.0
  • O manifesto é compilado e tem como meta o nível 23, o nível mínimo 15. Também tentei mover o nível mínimo para 21, mas não fez diferença.
  • Descompilar o APK (com nível mínimo definido como 21) mostra um único recurso XML na pasta drawable. Nenhuma imagem rasterizada é produzida.
Chris Knight
fonte
Só para ficar claro. Você está usando o Android Studio? Qual versão do Android Studio e qual versão do plug-in Gradle? Quando eu clico com o botão direito na pasta drawable no Android Studio e escolho New -> Vector Asset, o XML da imagem vetorial é descartado na minha pasta drawable. No entanto, se eu usar o apktool para descompactar o APK construído, vejo que os arquivos XML estão inseridos drawable-anydpi-v21e são dimensionados corretamente em dispositivos API 21+. Os arquivos raster são colocados nas drawable-<mdpi/hdpi/etc>-v4pastas e não são usados ​​em dispositivos API 21+ (com base no fato de que eles são dimensionados corretamente)
Cory Charlton
@Cory Charlton. Curioso. Estou usando o Studio 1.5.1 com Gradle 1.5.0. Depois de alterar o nível mínimo para 21, o único lugar em que a imagem aparece no APK é na drawablepasta. A largura / altura no arquivo xml drawable vetorial é de 24 dp. Especificar um ImageView de 400 dp de altura / largura é definitivamente criar uma imagem com escala insuficiente.
Chris Knight
Isso é o que eu esperava. A única razão pela qual acabo com o drawable-anydpi-v21é porque meu minSdkVersioné menor que 21. Alguma mudança no comportamento se você definir o minSdkVersionpara menos de 21? Que tal mover o XML para drawable-anydpi? Eu não esperaria que houvesse uma mudança, mas também esperaria que sua imagem vetorial fosse dimensionada corretamente ...
Cory Charlton
Alterar a versão mínima de volta para 15 produz os mesmos resultados que você. Um único arquivo xml drawable-anydpi-v21com várias imagens rasterizadas no mdi / hdpi / etc. pastas. Nenhuma alteração no resultado final renderizado.
Chris Knight
Muito estranho ... Modifiquei um dos meus aplicativos usando sua imagem e ele funciona bem (veja minha resposta editada)
Cory Charlton

Respostas:

100

Há novas informações sobre esse problema aqui:

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

Parece que o uso android:scaleType="fitXY"fará com que seja dimensionado corretamente no Lollipop.

De um engenheiro do Google:

Olá, deixe-me saber se scaleType = 'fitXY' pode ser uma solução alternativa para você, a fim de obter uma imagem mais nítida.

O marshmallow Vs Lollipop é devido a um tratamento especial de descamação adicionado ao marshmallow.

Além disso, para seus comentários: "Comportamento correto: o drawable vetorial deve ser dimensionado sem perda de qualidade. Portanto, se quisermos usar o mesmo recurso em 3 tamanhos diferentes em nosso aplicativo, não temos que duplicar vector_drawable.xml 3 vezes com diferentes tamanhos codificados. "

Embora eu concorde totalmente que deve ser o caso, na realidade, a plataforma Android tem uma preocupação de desempenho tal que ainda não alcançamos o mundo ideal. Portanto, é realmente recomendado usar 3 diferentes vetores_drawable.xml para melhor desempenho se você tiver certeza de que deseja desenhar 3 tamanhos diferentes na tela ao mesmo tempo.

O detalhe técnico é basicamente que estamos usando um bitmap sob o gancho para armazenar em cache a renderização do caminho complexo, de modo que possamos obter o melhor desempenho de redesenho, em paridade com o redesenho de um drawable de bitmap.

Parque Taehyun
fonte
27
Ok Google, é absolutamente terrível termos que copiar e colar drawables idênticos com as mesmas janelas de exibição e caminhos que possuem apenas tamanhos diferentes.
Miha_x64
Isso corrigiu no dispositivo que exibia meu logotipo pixelizado, mas na outra unidade, onde tudo estava bem antes, agora está com a proporção errada. Não entendo porque isso é um problema, é um vetor! Não pixels! : P
tplive
@Harish Gyanani, android: scaleType = "fitXY" resolve o problema, mas e os ícones dinâmicos e não quadrados?
Manuela
28

1) Por que há uma especificação de largura / altura no drawable vetorial? Eu pensei que o ponto principal disso é que eles aumentam e diminuem sem perdas, tornando largura e altura sem sentido em sua definição.

Para versões do SDK inferiores a 21, onde o sistema de compilação precisa gerar imagens raster e como o tamanho padrão nos casos em que você não especifica a largura / altura.

2) É possível usar um único drawable vetorial que funciona como um drawable de 24 dp em um TextView, bem como uma imagem grande com largura próxima à tela?

Não acredito que isso seja possível se você também precisar direcionar SDKs menores que 21.

3) Como uso efetivamente os atributos de largura / altura e qual é a diferença com viewportWidth / Height?

Documentação: (na verdade não é muito útil agora que reli ...)

android:width

Usado para definir a largura intrínseca do drawable. Isso suporta todas as unidades de dimensão, normalmente especificadas com dp.

android:height

Usado para definir a altura intrínseca do drawable. Isso suporta todas as unidades de dimensão, normalmente especificadas com dp.

android:viewportWidth

Usado para definir a largura do espaço da janela de visualização. A janela de visualização é basicamente a tela virtual onde os caminhos são desenhados.

android:viewportHeight

Usado para definir a altura do espaço da janela de visualização. A janela de visualização é basicamente a tela virtual onde os caminhos são desenhados.

Mais documentação:

Android 4.4 (API de nível 20) e anterior não oferece suporte a drawables vetoriais. Se seu nível mínimo de API estiver definido em um desses níveis de API, o Vector Asset Studio também direciona o Gradle para gerar imagens rasterizadas do drawable vetorial para compatibilidade com versões anteriores. Você pode se referir a ativos vetoriais como Drawableem código Java ou @drawableem código XML; quando seu aplicativo é executado, o vetor ou imagem raster correspondente é exibido automaticamente, dependendo do nível da API.


Edit: Algo estranho está acontecendo. Aqui estão meus resultados no emulador SDK versão 23 (Lollipop + dispositivo de teste está morto agora ...):

Bons sinos em 6.x

E no emulador SDK versão 21:

Sinos ruins no 5.x

Cory Charlton
fonte
1
Obrigado Cory, já tinha visto essa documentação e também não achei muito útil. Meu dispositivo é o Lollipop (v22) e ainda não consigo fazer o gráfico escalar bem, acima dos 24dp especificados no drawable vetorial, independentemente se a altura / peso estão especificados exatamente no ImageView ou não. Eu testei um manifesto com nível de destino e compilação 23 e nível mínimo 21, mas ele não aumentará suavemente meu drawable vetorial sem que eu altere a largura / altura no próprio drawable. Eu realmente não quero criar vários drawables cuja única diferença é altura / largura!
Chris Knight
1
Obrigado pela confirmação e realmente agradeço sua ajuda. Como você demonstrou, claramente deve funcionar como eu esperava. Usar seu código exato produz o mesmo resultado que eu tinha originalmente. Frustrante!
Chris Knight
1
ATUALIZAÇÃO: Executei meu código em relação ao emulador com API 22 e ainda obtenho o mesmo resultado. Executei meu código em um emulador com API 23 e, tada !, funciona. Parece que o aumento de escala está funcionando apenas em dispositivos API 23+. Tentei alterar a versão do SDK de destino, mas não fez diferença.
Chris Knight
Você encontrou uma solução para este problema?
dazza5000
@corycharlton, posso remover width height viewport heighte widthde xml?
VINNUSAURUS
9

O AppCompat 23.2 apresenta drawables vetoriais para todos os dispositivos que executam o Android 2.1 e superior. As imagens são dimensionadas corretamente, independentemente da largura / altura especificada no arquivo XML do drawable vetorial. Infelizmente, essa implementação não é usada na API de nível 21 e acima (em favor da implementação nativa).

A boa notícia é que podemos forçar o AppCompat a usar sua implementação nos níveis de API 21 e 22. A má notícia é que temos que usar reflexão para fazer isso.

Em primeiro lugar, verifique se você está usando a versão mais recente do AppCompat e se ativou a nova implementação de vetor:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

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

Em seguida, chame useCompatVectorIfNeeded();onCreate () do seu aplicativo:

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

Por fim, certifique-se de usar em app:srcCompatvez de android:srcdefinir a imagem de ImageView:

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

Seus drawables vetoriais agora devem ser dimensionados corretamente!

user3210008
fonte
Boa abordagem, mas o AppCompat 25.4 (e talvez anterior) fornece um erro de lint "só pode ser chamado do mesmo grupo de biblioteca". As ferramentas de compilação que estou usando ainda permitem compilar, mas não tenho certeza se há consequências no tempo de execução. Prestes a testar.
androidguy
só pode ser chamado a partir do mesmo grupo de bibliotecas, remova este erro adicionando: lintOptions {disable 'RestrictedApi'}
Usama Saeed US
6

1) Por que há uma especificação de largura / altura no drawable vetorial? Eu pensei que o ponto principal disso é que eles aumentam e diminuem sem perdas, tornando largura e altura sem sentido em sua definição.

Este é apenas o tamanho padrão do vetor, caso você não o defina na visualização do layout. (ou seja, você usa conteúdo de quebra para a altura e largura de sua imagem)

2) É possível usar um único drawable vetorial que funciona como um drawable de 24 dp em um TextView, bem como uma imagem grande com largura próxima à tela?

Sim, é possível e não tive nenhum problema com o redimensionamento, desde que o dispositivo em execução esteja usando pirulito ou superior. Nas APIs anteriores, o vetor é convertido em pngs para diferentes tamanhos de tela ao construir o aplicativo.

3) Como uso efetivamente os atributos de largura / altura e qual é a diferença com viewportWidth / Height?

Isso afeta como o espaço ao redor do vetor é usado. ou seja, você pode usá-lo para alterar a "gravidade" do vetor dentro da janela de visualização, fazer com que o vetor tenha uma margem padrão, deixar certas partes do vetor fora da janela de visualização, etc ... Normalmente, você apenas as define como iguais Tamanho.

emirua
fonte
Obrigado Emiura. Estou interessado em saber como você conseguiu vários tamanhos renderizados usando o mesmo drawable. Usando um drawable de 24 dp (para fins de teste), mudei meu ImageView para ter uma largura e altura especificadas de 400 dp e executei em meu dispositivo Lollipop (v22), mas ainda está renderizando mal, como uma imagem rasterizada com escala aumentada em vez de uma imagem vetorial. Também mudei meu manifesto para ter como alvo e compilar contra v23 e min versão 21. Nenhuma diferença.
Chris Knight
Nesse caso, você só precisa usar o maior tamanho em seu drawable vetorial e, em seguida, especificar o tamanho de 24 dp em sua visualização de texto.
emirua
Vejo agora que você obtém imagens borradas ao ampliar com grandes diferenças entre o tamanho no drawable vetorial e o tamanho no layout no Lollipop. No entanto, ainda é muito mais simples definir o tamanho maior em vez de ter um monte de arquivos para tamanhos de tela diferentes;).
emirua
4

Eu resolvo meu problema apenas alterando o tamanho da imagem vetorial. Desde o início era um recurso como:

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

E eu mudei para 150 dp (tamanho de ImageView no layout com este recurso de vetor) como:

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

Está funcionando para mim.

Djek-Grif
fonte
obrigado, isso é o suficiente para resolver o problema no meu tablet onde parecia pixelado.
user2342558
3

Eu tento muitas dessas maneiras, mas, infelizmente, nenhuma delas funcionou. Eu tento fitXYem ImageView, eu tente usar app:srcCompatmas não pode até usá-lo. mas achei um jeito complicado:

defina o Drawable como backgrounddo seu ImageView.

Mas o ponto dessa forma são as dimensões das Visualizações. se o seu ImageViewtiver dimensões incorretas, o drawable obtém Stretch. você deve controlá-lo nas versões pré-pirulito ou usar algumas visualizações PercentRelativeLayout.

Espero que seja útil.

Mahdi Astanei
fonte
2

Primeiro : importe svg para drawble: (( https://www.learn2crack.com/2016/02/android-studio-svg.html ))

1-Clique com o botão direito na pasta drawable e selecione New -> Vector Asset

insira a descrição da imagem aqui

2- O Vector Asset Studio oferece a opção de selecionar os ícones de materiais embutidos ou seu próprio arquivo svg local. Você pode substituir o tamanho padrão, se necessário.

insira a descrição da imagem aqui

Segundo : se você deseja suporte do Android API 7 ou superior, é necessário usar a biblioteca de suporte do Android de acordo (( https://stackoverflow.com/a/44713221/1140304 ))

1- adicionar

vectorDrawables.useSupportLibrary = true

ao seu arquivo build.gradle.

2-Use namespace em seu layout que tenha imageView:

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

Terceiro : defina imageView com android: scaleType = "fitXY" e use app: srcCompat

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

Quarto : se você deseja definir svg no código java, você deve adicionar a linha abaixo em oncreate methoed

 @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
Milad Ahmadi
fonte
A sua solução funciona na API 21 e superior? user3210008 acima menciona que a implementação não é usada na API 21 e acima.
AJW de
2

Para mim, o motivo que fez com que os vetores fossem convertidos em png foi um android:fillType="evenodd"atributo no meu drawable vetorial. Quando removi todas as ocorrências desse atributo em meu drawable (portanto, o nonzerotipo de preenchimento padrão aplicado ao vetor), da próxima vez que o aplicativo foi construído estava tudo bem.

Mahozad
fonte