¿Por qué mi escala de diseño vectorial no es la esperada?

97

Estoy intentando usar elementos de diseño vectoriales en mi aplicación de Android. De http://developer.android.com/training/material/drawables.html (énfasis mío):

En Android 5.0 (API nivel 21) y superior, puede definir elementos de diseño vectoriales, que escalan sin perder definición.

Usando este dibujable:

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

y este ImageView:

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

produce esta imagen borrosa cuando se intenta mostrar el icono a 400 dp (en un dispositivo móvil de alta resolución de alrededor de 2015 que ejecuta una paleta):

blurryBellIcon

Cambiar el ancho y el alto en la definición del vector dibujable a 200dp mejora significativamente la situación en el tamaño renderizado de 400dp. Sin embargo, configurar esto como un elemento de dibujo para un elemento TextView (es decir, el icono a la izquierda del texto) ahora crea un icono enorme.

Mis preguntas:

1) ¿Por qué hay una especificación de ancho / alto en el vector dibujable? Pensé que el objetivo de estos es que se escalan hacia arriba y hacia abajo sin pérdidas, lo que hace que el ancho y la altura no tengan sentido en su definición.

2) ¿Es posible usar un único elemento de dibujo vectorial que funcione como elemento de dibujo de 24 dp en un TextView pero que se amplíe bien para usar imágenes mucho más grandes también? Por ejemplo, ¿cómo evito crear varios elementos de diseño vectoriales de diferentes tamaños y, en su lugar, uso uno que se adapte a mis requisitos renderizados?

3) ¿Cómo utilizo eficazmente los atributos de ancho / alto y cuál es la diferencia con viewportWidth / Height?

Detalles adicionales:

  • El dispositivo está ejecutando API 22
  • Usando Android Studio v1.5.1 con Gradle versión 1.5.0
  • Manifest es compilar y nivel objetivo 23, nivel mínimo 15. También intenté mover el nivel mínimo a 21, pero esto no hizo ninguna diferencia.
  • La descompilación del APK (con el nivel mínimo establecido en 21) muestra un solo recurso XML en la carpeta dibujable. No se producen imágenes rasterizadas.
Chris Knight
fuente
Para ser claro. ¿Estás usando Android Studio? ¿Qué versión de Android Studio y qué versión del complemento de Gradle? Cuando hago clic con el botón derecho en la carpeta dibujable en Android Studio y la elijo New -> Vector Asset, suelta la imagen vectorial XML en mi carpeta dibujable. Sin embargo, si utilizo apktool para descomprimir el APK que se compila, veo que los archivos XML están instalados drawable-anydpi-v21y escalan correctamente en dispositivos API 21+. Los archivos ráster se colocan en las drawable-<mdpi/hdpi/etc>-v4carpetas y no se usan en dispositivos API 21+ (basado en el hecho de que escalan correctamente)
Cory Charlton
@Cory Charlton. Curioso. Estoy usando Studio 1.5.1 con Gradle 1.5.0. Después de cambiar el nivel mínimo a 21, el único lugar donde aparece la imagen en el APK es la drawablecarpeta. El ancho / alto en el archivo xml dibujable de vectores es 24 dp. Especificar un ImageView de 400dp de alto / ancho definitivamente está creando una imagen mal escalada.
Chris Knight
Eso es lo que esperaría. La única razón por la que termino con el drawable-anydpi-v21es porque my minSdkVersiones menor que 21. ¿Algún cambio en el comportamiento si lo pones minSdkVersionen menos de 21? ¿Qué hay de mover el XML a drawable-anydpi? No esperaría que hubiera un cambio, pero también esperaría que su imagen vectorial se escale correctamente ...
Cory Charlton
Cambiar la versión mínima de nuevo a 15 produce los mismos resultados que usted. Un solo archivo xml drawable-anydpi-v21con varias imágenes rasterizadas en mdi / hdpi / etc. carpetas. Sin embargo, no hay cambios en el resultado final renderizado.
Chris Knight
Muy extraño ... Modifiqué una de mis aplicaciones usando su imagen y funciona bien (vea mi respuesta editada)
Cory Charlton

Respuestas:

100

Hay nueva información sobre este problema aquí:

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

Parece que usarlo android:scaleType="fitXY"hará que se escale correctamente en Lollipop.

De un ingeniero de Google:

Hola, Avísame si scaleType = 'fitXY' puede ser una solución para ti, para que la imagen se vea nítida.

El marshmallow Vs Lollipop se debe a un tratamiento especial de incrustación agregado al marshmallow.

Además, para sus comentarios: "Comportamiento correcto: El vector dibujable debe escalar sin pérdida de calidad. Por lo tanto, si queremos usar el mismo activo en 3 tamaños diferentes en nuestra aplicación, no tenemos que duplicar vector_drawable.xml 3 veces con diferentes tamaños codificados ".

Aunque estoy totalmente de acuerdo en que este debería ser el caso, en realidad, la plataforma Android tiene problemas de rendimiento de tal manera que aún no hemos alcanzado el mundo ideal. Por lo tanto, se recomienda utilizar 3 vectores_drawable.xml diferentes para un mejor rendimiento si está seguro de que desea dibujar 3 tamaños diferentes en la pantalla al mismo tiempo.

El detalle técnico es básicamente que estamos usando un mapa de bits debajo del gancho para almacenar en caché la representación de la ruta compleja, de modo que podamos obtener el mejor rendimiento de redibujado, a la par con redibujar un mapa de bits dibujable.

Parque Taehyun
fuente
27
De acuerdo, Google, es absolutamente terrible que debamos copiar y pegar elementos de diseño idénticos con las mismas ventanas gráficas y rutas que solo tienen tamaños diferentes.
Miha_x64
Esto lo solucionó en el dispositivo que mostraba mi logotipo pixelado, pero en la otra unidad, donde todo estaba bien antes, ahora tiene la relación de aspecto incorrecta. No entiendo por qué esto es un problema, ¡es un vector! ¡No píxeles! : P
tplive el
@Harish Gyanani, android: scaleType = "fitXY" resuelve el problema, pero ¿qué pasa con los iconos dinámicos y no cuadrados?
Manuela
28

1) ¿Por qué hay una especificación de ancho / alto en el vector dibujable? Pensé que el objetivo de estos es que se escalan hacia arriba y hacia abajo sin pérdidas, lo que hace que el ancho y la altura no tengan sentido en su definición.

Para las versiones de SDK inferiores a 21, donde el sistema de compilación necesita generar imágenes ráster y como tamaño predeterminado en los casos en que no especifica el ancho / alto.

2) ¿Es posible utilizar un único elemento de dibujo vectorial que funcione como elemento de dibujo de 24 dp en un TextView, así como una imagen grande de ancho cerca de la pantalla?

No creo que esto sea posible si también necesita orientar los SDK a menos de 21.

3) ¿Cómo utilizo eficazmente los atributos de ancho / alto y cuál es la diferencia con viewportWidth / Height?

Documentación: (en realidad no es muy útil ahora que lo releo ...)

android:width

Se utiliza para definir el ancho intrínseco del dibujable. Esto admite todas las unidades de dimensión, normalmente especificadas con dp.

android:height

Se utiliza para definir la altura intrínseca del dibujable. Esto admite todas las unidades de dimensión, normalmente especificadas con dp.

android:viewportWidth

Se utiliza para definir el ancho del espacio de la ventana gráfica. La ventana gráfica es básicamente el lienzo virtual donde se dibujan las rutas.

android:viewportHeight

Se utiliza para definir la altura del espacio de la ventana gráfica. La ventana gráfica es básicamente el lienzo virtual donde se dibujan las rutas.

Más documentación:

Android 4.4 (API nivel 20) y versiones anteriores no admiten elementos de diseño vectoriales. Si su nivel de API mínimo se establece en uno de estos niveles de API, Vector Asset Studio también indica a Gradle que genere imágenes ráster del vector dibujable para compatibilidad con versiones anteriores. Puede hacer referencia a activos vectoriales como Drawableen código Java o @drawableen código XML; cuando se ejecuta su aplicación, la imagen de trama o vector correspondiente se muestra automáticamente según el nivel de API.


Editar: Algo extraño está sucediendo. Aquí están mis resultados en la versión 23 del SDK del emulador (el dispositivo de prueba Lollipop + está muerto en este momento ...):

Buenas campanas en 6.x

Y en la versión 21 del SDK del emulador:

Campanas de mierda en 5.x

Cory Charlton
fuente
1
Gracias Cory, había visto esa documentación y tampoco la encontré muy útil. Mi dispositivo es Lollipop (v22) y, sin embargo, no puedo hacer que el gráfico se escale bien, por encima de los 24 dp especificados en el vector dibujable, independientemente de si la altura / peso se especifica exactamente en ImageView o no. He probado un manifiesto con nivel objetivo y compilado 23 y nivel mínimo 21, pero no mejorará mi diseño vectorial sin que cambie el ancho / alto en el propio diseño. ¡Realmente no quiero crear múltiples elementos de diseño cuya única diferencia sea la altura / ancho!
Chris Knight
1
Gracias por la confirmación y realmente agradezco tu ayuda. Como ha demostrado, claramente debería funcionar como lo espero. Usar su código exacto produce el mismo resultado que tenía originalmente. ¡Frustrante!
Chris Knight
1
ACTUALIZACIÓN: Ejecuté mi código contra el emulador con API 22 y todavía obtengo el mismo resultado. Ejecuté mi código contra el emulador con API 23 y, ¡tada !, funciona. Parece que la ampliación solo funciona en dispositivos API 23+. Intenté cambiar la versión del SDK de destino, pero eso no supuso ninguna diferencia.
Chris Knight
¿Encontraste una solución para este problema?
dazza5000
@corycharlton ¿está bien quitar width height viewport heighty widthde xml?
VINNUSAURUS
9

AppCompat 23.2 presenta elementos de diseño vectoriales para todos los dispositivos que ejecutan Android 2.1 y superior. Las imágenes se escalan correctamente, independientemente del ancho / alto especificado en el archivo XML del elemento de diseño vectorial. Desafortunadamente, esta implementación no se utiliza en el nivel de API 21 y superior (a favor de la implementación nativa).

La buena noticia es que podemos obligar a AppCompat a usar su implementación en los niveles de API 21 y 22. La mala noticia es que tenemos que usar la reflexión para hacer esto.

En primer lugar, asegúrese de que está utilizando la última versión de AppCompat y que ha habilitado la nueva implementación de vector:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

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

Luego, llame useCompatVectorIfNeeded();al onCreate () de su aplicación:

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

Finalmente, asegúrese de que está usando en app:srcCompatlugar de android:srcconfigurar la imagen de su ImageView:

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

¡Sus elementos de diseño vectoriales ahora deberían escalar correctamente!

user3210008
fuente
Buen enfoque, pero AppCompat 25.4 (y tal vez antes) da un error de lint "solo se puede llamar desde el mismo grupo de bibliotecas". Las herramientas de compilación que estoy usando todavía lo dejan compilar, pero no estoy seguro de si hay consecuencias en el tiempo de ejecución. A punto de probar.
androidguy
solo se puede llamar desde el mismo grupo de bibliotecas, elimine este error agregando esto: lintOptions {deshabilitar 'RestrictedApi'}
Usama Saeed US
6

1) ¿Por qué hay una especificación de ancho / alto en el vector dibujable? Pensé que el objetivo de estos es que se escalan hacia arriba y hacia abajo sin pérdidas, lo que hace que el ancho y la altura no tengan sentido en su definición.

Este es solo el tamaño predeterminado del vector en caso de que no lo defina en la vista de diseño. (es decir, utiliza contenido envolvente para la altura y el ancho de su vista de imagen)

2) ¿Es posible utilizar un único elemento de dibujo vectorial que funcione como elemento de dibujo de 24 dp en un TextView, así como una imagen grande de ancho cerca de la pantalla?

Sí, es posible y no he tenido ningún problema con el cambio de tamaño siempre que el dispositivo en ejecución esté usando lollipop o superior. En las API anteriores, el vector se convierte a png para diferentes tamaños de pantalla cuando crea la aplicación.

3) ¿Cómo utilizo eficazmente los atributos de ancho / alto y cuál es la diferencia con viewportWidth / Height?

Esto afecta cómo se usa el espacio alrededor de su vector. es decir, puede usarlo para cambiar la "gravedad" del vector dentro de la ventana gráfica, hacer que el vector tenga un margen predeterminado, dejar ciertas partes del vector fuera de la ventana gráfica, etc. Talla.

emirua
fuente
Gracias Emiura. Me interesaría saber cómo lograste varios tamaños renderizados usando el mismo dibujante. Usando un dibujable de 24dp (para propósitos de prueba) cambié mi ImageView para tener un ancho y alto específico de 400dp y lo ejecuté en mi dispositivo Lollipop (v22), pero todavía se está reproduciendo mal, como una imagen escalada rasterizada en lugar de una imagen vectorial. También cambié mi manifiesto para tener destino y compilar contra v23 y versión mínima 21. No hay diferencia.
Chris Knight
En este caso, solo necesitaría usar el tamaño más grande en su vector dibujable y luego especificar el tamaño de 24 dp en su vista de texto.
emirua
Ahora veo que obtienes imágenes borrosas cuando agrandas con diferencias muy grandes entre el tamaño en el vector dibujable y el tamaño en el diseño en Lollipop. Sin embargo, todavía es mucho más ordenado establecer el tamaño más grande en lugar de tener un montón de archivos para diferentes tamaños de pantalla;).
emirua
4

Resuelvo mi problema, solo cambio el tamaño de la imagen vectorial. Desde el principio fue un 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">

Y lo cambié a 150dp (tamaño de ImageView en el diseño con este recurso de vector) 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">

Me está funcionando.

Djek-Grif
fuente
gracias, esto es suficiente para resolver el problema en mi tableta donde aparecía pixelada.
user2342558
3

Probé muchas de estas formas, pero desafortunadamente, ninguna funcionó. Trato fitXYde ImageView, trato de usarapp:srcCompat , pero no puedo incluso utilizarlo. pero encontré una forma engañosa:

establece el Drawable en backgroundde tuImageView .

Pero el punto de esta forma son las dimensiones de Vistas. si tuImageView tiene dimensiones incorrectas, el elemento dibujable se estira. debes controlarlo en versiones anteriores a la piruleta o usar algunas vistas PercentRelativeLayout.

Espero que sea de ayuda.

Mahdi Astanei
fuente
2

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

1-Haga clic con el botón derecho en la carpeta dibujable, seleccione Nuevo -> Activo vectorial

ingrese la descripción de la imagen aquí

2- Vector Asset Studio le brinda la opción de seleccionar los íconos de Material incorporados o su propio archivo svg local. Puede anular el tamaño predeterminado si es necesario.

ingrese la descripción de la imagen aquí

Segundo : si desea compatibilidad con la API de Android 7+, debe utilizar la grabación de la biblioteca de compatibilidad de Android (( https://stackoverflow.com/a/44713221/1140304 ))

1- agregar

vectorDrawables.useSupportLibrary = true

a su archivo build.gradle.

2-Use el espacio de nombres en su diseño que tiene imageView:

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

Tercero : configure imageView con android: scaleType = "fitXY" y use la aplicación: srcCompat

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

Cuarto : si desea configurar svg en el código java, debe agregar la siguiente línea en el método oncreate

 @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
Milad Ahmadi
fuente
¿Su solución funciona en API 21 y superior? user3210008 menciona que la implementación no se utiliza en API 21 y superior.
AJW
2

Para mí, la razón que causó que los vectores se convirtieran a png fue el android:fillType="evenodd"atributo en mi vector dibujable. Cuando eliminé todas las apariciones de este atributo en mi elemento de dibujo (por lo tanto, el nonzerotipo de relleno predeterminado aplicado al vector), la próxima vez que se compiló la aplicación, todo estuvo bien.

Mahozad
fuente