Tiempo de lectura: 6 minutos

ViewStub la vista ‘invisible’

Cuando comenzamos a desarrollar en Android, es muy normal tender a cargar un layout con todos los elementos necesarios para la aplicación. A medida que vamos aprendiendo, empezamos a usar los <include/> y los <merge/> que nos permiten rehusar vistas y mejorar el rendimiento.

Además de lo contado, una práctica que sigue siendo habitual entre los desarrolladores Android, es mostrar vistas ocultas para luego ‘jugar’ con su visibilidad según ciertos comportamientos de la aplicación. Esto aunque parece una tarea inofensiva es un proceso que puede consumir recursos innecesarios.

Ej: Tengo un Layout A que contiene dos pestañas. La pestaña A.1 muestra un layout y la pestaña A.2 muestra otro layout diferente. Aquí es muy común cargar el Layout principal A con la pestaña A.1 visible y el contenido de la pestaña A.2 lo mostramos como invisible. Se puede dar el caso que el usuario nunca visualice la pestaña A.2. En este caso, la estructura de la vista se carga por completo se muestre o no.

Veamos un diagrama de lo que acabo de contar.

En este diagrama podemos ver el arbol de vista que se carga pese a tener la pestaña a no visible:

viewstub_1_800

 

Para evitar que se carga por completo la estructura de la vista, existe una widget que extiende de Views y que se llama ViewStub.

ViewStub es una vista que permite inflar un layout en tiempo de ejecución. Haciendo una comparativa con los estados de visibilidad descritos anteriormente, un ViewStub infla un layout sólo cuando ejecutamos el método inflate o establecemos su visibilidad a visible.

Y aquí podemos ver como sería el árbol de vistas usando ViewStub:

viewstub_2

Antes de pasar a ver el código, os pongo el enlace del proyecto donde está todo el código aquí expuesto.

GitHub – HowTo-ViewStub

Así sería un elemento ViewStub.

/**
*
*/
<ViewStub
   android:id="@+id/vsLore"
   android:inflatedId="@+id/details"
   android:layout="@layout/details"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

Vamos a ver los distintos parámetros.

  • android:id = es el identificador común de la vista.
  • android:inflatedId = Es el identificador de la vista principal que vamos a cargar. Este identificador sobreescribe al identificador que pueda tener el padre de la vista cargada. Si no se ponde, se conserva el id de la vista padre del layout cargado.
  • android:layout:”@layout/details” el layout que se va a cargar.

Código usando la visibilidad para mostrar u ocultar vistas.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:visibility="visible"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
    </LinearLayout>
    
    <LinearLayout
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
    </LinearLayout>
    
</LinearLayout>

Código usando la ViewStub

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:visibility="visible"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ViewStub
            android:id="@+id/vsLore"
            android:inflatedId="@+id/details"
            android:layout="@layout/details"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>

 Aquí el layout que infla ViewStub

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/details"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Para hacer ‘visible’ el layout que tiene asignado el ViewStub se tienen dos formas:

  1. setVisibility(View.VISIBLE). Simplemente cambia la visibilidad de ViewStub. ojo! no hace falta poner como estado inicial INVISIBLE/GONE.
  2. inflate(): La ventaja de usar inflate con respecto a la anterior, es que este método devuelve la vista que se acaba de mostrar. Esto es útil para poder acceder a las vistas del layout.

Uno de los errores comunes que se comete cuando se usa ViewStub, es acceder a los elementos del layout asignado antes de inflar el ViewStub. Para poder acceder a las vistas del layout, debería hacerse la búsqueda (findViewById) después de inflar el ViewStub o hacer la búsqueda sobre la vista devuelta al ejecutar inflate().

Aquí os dejo un ejemplo de código de cómo obtener los elementos del ViewStub con la vista devuelta.

    /**
    * viewInflated es un atributo tipo View.
    */
    private void onPost(){
        if (viewInflated == null) {
            viewInflated = vsLore.inflate();
            inflateViewAfterViewStub(viewInflated);
        }
    }

    private void inflateViewAfterViewStub(View viewInflated){
        if (viewInflated != null){
            tv1 = (TextView) viewInflated.findViewById(R.id.tv1);
            tv2 = (TextView) viewInflated.findViewById(R.id.tv2);
            tv3 = (TextView) viewInflated.findViewById(R.id.tv3);
        }
    }

 

Y aquí haciendo un findview una vez inflado el layout.

    
    private void onPost(){
        if (viewInflated == null) {
            viewInflated = vsLore.inflate();
            //inflateViewAfterViewStub();
            inflateViewAfterViewStub2();
        }
    }

    private void inflateViewAfterViewStub2(){
        if (viewInflated != null){
            tv1 = (TextView) findViewById(R.id.tv1);
            tv2 = (TextView) findViewById(R.id.tv2);
            tv3 = (TextView) findViewById(R.id.tv3);
        }
    }

Como habéis podido ver, siempre realizo una comprobación (viewInflated != null). Esto lo hago porque una vez inflado el ViewStub, este se elimina del layout y si volvemos a ejecutar el método que lo infla, nos dará error. Esto hay que controlarlo.

Y poco más. ViewStub es un componente muy trivial pero que se usa poco, debemos intentar usarlo siempre que sea posible.

Código disponible: GitHub – HowTo-ViewStub