La columna 80

El blog técnico-personal de Ángel J. Vico… en español

Diseños Android básicos: LinearLayout

Posted by Ángel J. Vico en 15 de septiembre de 2012

LinearLayout es otro de los diseños básicos con que contamos en Android para crear las interfaces de usuario de nuestras aplicaciones. A diferencia de FrameLayout, que coloca cada vista que se incluye en él sobre la anterior, LinearLayout coloca las vistas una a continuación de otra, sin superponerlas nunca. Es decir, las alinea.

Esta alineación puede ser vertical u horizontal. O sea, que LinearLayout sirve para colocar vistas en una misma fila o columna, pero no ambas cosas a la vez.

Las vistas se colocan en el mismo orden en se agregan al diseño o en el que aparecen en el archivo XML.

De forma predeterminada, LinearLayout tiende a establecer el mismo tamaño para cada vista que contiene, repartiendo el espacio disponible de forma equitativa entre todas ellas. Sin embargo, algunas propiedades de las vistas y ciertos parámetros que el propio LinearLayout añade permiten variar este comportamiento.

Veamos cómo sacarle partido a este diseño.

Propiedades

Comencemos por algunas de las propiedades que tiene LinearLayout, además de las que le aporta la clase View y que ya vimos en un post anterior:

  • android:orientation (setOrientation): la principal propiedad de LinearLayout. Determina si las vistas se alinean verticalmente u horizontalmente. Puede tomar dos valores: horizontal o vertical. De forma predeterminada, la orientación es vertical.

  • android:baselineAligned (setBaselineAligned): de forma predeterminada, LinearLayout alinea las vistas que contiene en función de su línea base (línea invisible sobre la que apoya el texto). Esto permite que un TextView y un Button queden alineados verticalmente de forma natural en un diseño horizontal. Sin embargo, este tipo de alineación puede dar problemas en algunos casos. Establecer esta propiedad a false desactiva este alineamiento. La siguiente captura muestra ambas opciones:

    Opciones de baselineAligned

  • android:gravity (setGravity): permite definir la gravedad del propio LinearLayout, o sea, cómo se alinea todo su contenido. Afecta al conjunto de las vistas que se incluyen en él, pero no al contenido de las propias vistas. En la siguiente captura se ve más claro. Los valores que puede tomar son los habituales para la gravedad (los que vimos en el post de FrameLayout).

    Ejemplo de gravity

    En la captura se muestra un LinearLayout horizontal que contiene dos TextView. El atributo android:gravity del LinearLayout tiene el valor center_horizontal, lo que provoca que las dos vistas que contienen se centren en el espacio horizontal que ocupa el diseño. Se puede ver cómo la gravedad del diseño no afecta al contenido de los TextView, que mantienen su alineación predeterminada: a la izquierda.

Parámetros de diseño

Al igual que el resto de diseños, LinearLayout define su propia clase de parámetros de diseño, que añade propiedades a las vistas que contiene. En este caso, LinearLayout.LayourParams hereda de ViewGroup.MarginLayoutParams. Esta última clase, como ya vimos en el post sobre padding y margen, hereda de ViewGroup.LayoutParams añadiendo las propiedades que permiten definir márgenes para las vistas.

Esta jerarquía de clases permite que LinearLayout.LayoutParams herede parámetros genéricos como android:layout_width y android:layout_height y, al mismo tiempo, los de los márgenes (android:layout_marginLeft, android:layout_marginTop, etc). A todos estos añade dos de cosecha propia: gravedad y peso.

Gravedad

La gravedad (android:layout_gravity) es idéntica a la que proporciona FrameLayout, por lo que no volveré a describirla aquí. Lo que sí hay que tener en cuenta es en qué se diferencia respecto a la gravedad del propio diseño (android:gravity), comentada en el apartado anterior.

Aunque funcionan de forma muy similar, la propiedad android:gravity afecta al conjunto de todas las vistas contenidas en el diseño, mientras que el parámetro android:layout_gravity sólo afecta a la vista a la que se le asigna. Las siguientes capturas dejarán más claras las diferencias:

Ejemplo de gravity y layout_gravity

En la captura se muestra un LinearLayout horizontal con fondo rojo. Al igual que en la captura anterior, el atributo android:gravity tiene el valor center_horizontal. La diferencia es que, ahora, el segundo TextView utiliza el parámetro android:layout_gravity con el valor bottom, lo que provoca que se alinee con la parte inferior del LinearLayout. El otro TextView, al no definir ningún alineamiento, se queda en la parte superior (es el comportamiento predeterminado). Pero lo más destacable de la captura es cómo el atributo android:gravity afecta al conjunto de los dos TextView contenidos en el LinearLayout, centrándolos horizontalmente como si fueran un único elemento, en lugar de hacerlo individualmente.

Veamos ahora las dos capturas siguientes:

Ejemplo de layout_gravity en LinearLayout horizontal
Ejemplo de layout_gravity en LinearLayout vertical

Las dos capturas muestran un LinearLayout (con fondo rojo) que contiene dos TextView. El primer TextView tiene los valores bottom y center_horizontal en el parámetro android:layout_gravity, mientras que el segundo sólo tiene el valor center_horizontal en su parámetro android:layout_gravity. En ninguna de las dos capturas se ha utilizado el atributo android:gravity del LinearLayout.

¿En qué se diferencian, entonces, los diseños de ambas capturas? Tan sólo en una cosa: la orientación del LinearLayout. En la captura superior el LinearLayout es horizontal, mientras que en la captura inferior es vertical. Esto pone de relieve una particularidad del parámetro android:layout_gravity cuando se utiliza en un LinearLayout (y que no se daba en el FrameLayout): las opciones de alineamiento que afectan a la dirección en la que se orienta el LinearLayout se ignoran.

En la primera captura, donde el LinearLayout es horizontal, se está ignorando el valor center_horizontal de ambos TextView. En la segunda captura, que muestra un LinearLayout vertical, el valor que se está ignorando es el bottom del primer TextView.

Si lo piensas tiene bastante sentido. La primera regla de un LinearLayout es que las vistas se muestran en orden secuencial, según se hayan añadido al diseño. Permitir total libertad a la hora de usar la gravedad podría alterar ese orden.

Por ejemplo, supongamos que en un LinearLayout vertical se incluyen tres vistas y se utiliza center_vertical en el parámetro android:layout_gravity de la primera y la tercera. Si el diseño tratara de centrar estas dos vistas tendría que alterar la secuencia en que fueran agregadas, colocando la segunda vista en la parte superior y a continuación, en el centro, la primera y la tercera. Como esto no está permitido, lo que se hace es ignorar esos valores de la gravedad. Lo que sí se podría hacer, en este mismo ejemplo, es aplicar la gravedad center_vertical a todo el contenido del LinearLayout usando el atributo android:gravity. En ese caso, las tres vistas quedarían centradas verticalmente, sin que se alterara el orden en el que fueron agregadas.

En la captura inferior, la que muestra un LinearLayout vertical, también se ve claramente dónde está el problema de aplicar las opciones de alineamiento en la dirección del LinearLayout: si se hiciera caso al valor bottom, el TextView con fondo verde se tendría que colocar en la parte inferior del LinearLayout, quedando debajo del TextView con fondo azul, e invirtiendo el orden en que fueron agregados ambos widgets al diseño.

Peso

Ya hemos comentado que LinearLayout, de forma predeterminada, tiende a repartir el espacio disponible de forma uniforme entre las vistas que tiene que distribuir, asignando un tamaño similar a cada una de ellas (aunque teniendo en cuenta las restricciones que la propia vista imponga).

El peso permite variar este comportamiento, asignando a cada vista un nivel de “importancia” que se traduce en más o menos espacio ocupado en el resultado final, de forma proporcional al valor asignado. El atributo que se utiliza para ello es android:layout_weight.

Si, por ejemplo, asignamos pesos 1, 2 y 3 a tres vistas, LinearLayout tratará de asignar la mitad del espacio disponible a la que tiene peso 3 (3 es la mitad de la suma total de los pesos, que es 6 en este ejemplo). De forma similar, la otra mitad del espacio libre la repartirá entre las otras dos vistas tratando de usar el doble para la que tiene peso 2. La siguiente captura muestra esa situación:

Ejemplo de pesos

Hay que tener en cuenta que el reparto no es exacto. Incluso si la vista no incluye ninguna restricción especial, como android:minWidth, su contenido puede afectar al espacio que se le asigne finalmente. Android siempre tratará de conseguir la mejor visualización posible para cada vista, aunque tenga que incumplir ligeramente alguna de las restricciones que impongamos.

Si no asignamos peso a una vista se supone que su peso es cero. En este caso su tamaño se determinará con las restricciones habituales, y las vistas con peso se repartirán el resto del espacio de forma proporcional a dicho peso:

Ejemplo de peso cero

El valor del peso es de tipo float, por lo que podemos utilizar decimales si queremos más precisión (y complicarnos la vida innecesariamente).

No es necesario, como se menciona en algunos textos, que la suma de los pesos sea 1. Los pesos pueden sumar cualquier cantidad salvo que se utilice la propiedad android:weightSum de LinearLayout. En ese caso, la suma no podrá superar la cifra que se especifique. Esto puede servir para determinar la proporción que ocupa una vista sin asignar peso a las demás. Por ejemplo, si la suma se establece a 1 y se asigna un peso de 0,3, la vista ocupará el 30% del espacio. No lo usarás a menudo.

Uso de LinearLayout en XML y en Java

Tomemos como ejemplo la interfaz de usuario que se muestra en la siguiente captura, creada mediante la combinación de varios LinearLayout:

Ejemplo con varios LinearLayout

El código XML que permite crear ese diseño es el siguiente:

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

    <LinearLayout
        android:id="@+id/num_telefono"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:baselineAligned="true"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/telefono"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="2sp"
            android:paddingRight="2sp"
            android:text="Teléfono:"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/prefijo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/numero"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="9"
            android:textSize="17sp" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingLeft="2sp"
                android:paddingRight="2sp"
                android:text="Descripción:"
                android:textSize="17sp" />

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="right"
                android:paddingRight="2sp"
                android:text="OK"
                android:textColor="#00AA00"
                android:textSize="17sp" />

        </LinearLayout>

        <EditText
            android:id="@+id/descripcion"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />

    </LinearLayout>

</LinearLayout>

El equivalente en Java es el que se muestra a continuación:

   /** Called when the activity is first created. */
   @Override
   public void onCreate (Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);

      DisplayMetrics met = getResources().getDisplayMetrics();

      // Texto "Teléfono:"
      TextView telefono = new TextView(this);
      telefono.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.WRAP_CONTENT,
         LayoutParams.WRAP_CONTENT));
      telefono.setPadding(
         (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 2, met),
         0,
         (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 2, met),
         0);
      telefono.setText("Teléfono:");
      telefono.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);

      // Cuadro de texto para el prefijo
      EditText prefijo = new EditText(this);
      prefijo.setLayoutParams(new LinearLayout.LayoutParams(
         LayoutParams.WRAP_CONTENT,
         LayoutParams.WRAP_CONTENT,
         3));
      prefijo.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);

      // Cuadro de texto para el número
      EditText numero = new EditText(this);
      numero.setLayoutParams(new LinearLayout.LayoutParams(
         LayoutParams.WRAP_CONTENT,
         LayoutParams.WRAP_CONTENT,
         9));
      numero.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);

      // Diseño para el número de teléfono
      LinearLayout num_telefono = new LinearLayout(this);
      num_telefono.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.WRAP_CONTENT));
      num_telefono.setBaselineAligned(true);
      num_telefono.setOrientation(LinearLayout.HORIZONTAL);
      num_telefono.addView(telefono);
      num_telefono.addView(prefijo);
      num_telefono.addView(numero);

      // Texto "Descripción:"
      TextView desc = new TextView(this);
      desc.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.WRAP_CONTENT,
         LayoutParams.WRAP_CONTENT));
      desc.setPadding(
         (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 2, met),
         0,
         (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 2, met),
         0);
      desc.setText("Descripción:");
      desc.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);

      // Texto "OK"
      TextView ok = new TextView(this);
      ok.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.WRAP_CONTENT));
      ok.setGravity(Gravity.RIGHT);
      ok.setPadding(
         0,
         0,
         (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 2, met),
         0);
      ok.setText("OK");
      ok.setTextColor(Color.parseColor("#00AA00"));
      ok.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);

      // Diseño para la línea de texto
      LinearLayout textos = new LinearLayout(this);
      textos.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.WRAP_CONTENT));
      textos.setOrientation(LinearLayout.HORIZONTAL);
      textos.addView(desc);
      textos.addView(ok);

      // Cuadro de texto para la descripción
      EditText descripcion = new EditText(this);
      descripcion.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.FILL_PARENT));

      // Diseño para la parte inferior
      LinearLayout inferior = new LinearLayout(this);
      inferior.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.FILL_PARENT));
      inferior.setOrientation(LinearLayout.VERTICAL);
      inferior.addView(textos);
      inferior.addView(descripcion);

      // Diseño principal de la actividad
      LinearLayout principal = new LinearLayout(this);
      principal.setLayoutParams(new ViewGroup.LayoutParams(
         LayoutParams.FILL_PARENT,
         LayoutParams.FILL_PARENT));
      principal.setOrientation(LinearLayout.VERTICAL);
      principal.addView(num_telefono);
      principal.addView(inferior);

      setContentView(principal);
   }

La forma de construir el diseño por código es similar a la que ya hemos visto en posts anteriores: primero se crean los widgets, luego el diseño que los contiene directamente y después se añaden los primeros dentro del segundo. Se sigue en sentido ascendente, creando los contenedores superiores e insertando en ellos las vistas que tienen que contener. Lo último que se crea es el diseño principal de la vista, al que se añade todo el contenido. El último paso consiste en asignar ese diseño principal a la vista, con el método setContentView.

Para asignar el peso a los EditText se ha usado la clase LayoutParams que se define en LinearLayout en lugar de la que incluye ViewGroup y que se ha utilizado en el resto de los casos. LinearLayout.LayoutParams proporciona un constructor que, además de los parámetros para el ancho y el alto de la vista, permite especificar un valor para el peso.

Para asignar el padding de los widgets que lo necesitan, se ha utilizado el método setPadding que incluye la clase TextView. A diferencia de la versión XML, en Java no tenemos métodos separados para cada padding y es necesario especificar los cuatro valores en todas las ocasiones. Por suerte, podemos usar el valor cero para los límites en los que no queramos colocar un padding.

En este ejemplo, la gravedad (como propiedad) no la hemos usado en un diseño, sino en un widget. En concreto, en un TextView. Como veremos más adelante, muchos widgets incorporan propiedades similares a las de los diseños. En algunos casos, ambos las heredan de la clase View. En otros casos, como en este, simplemente definen la misma propiedad y con el mismo nombre.

La propiedad android:gravity de TextView funciona exactamente igual y permite los mismos valores que su homóloga en LinearLayout, de la que hemos hablado más arriba. En Java, los posibles valores los proporciona la clase Gravity.

En este caso se ha utilizado la propiedad android:gravity en lugar del parámetro android:layout_gravity porque el TextView que se quería alinear a la derecha está incluido en un LinearLayout horizontal. Como ya hemos explicado, una gravedad horizontal utilizada en un LinearLayout también horizontal se ignora. Para conseguir el efecto deseado (que el texto OK quedara alineado a la derecha) ha sido necesario hacer que el TextView ocupara todo el espacio disponible (atributo android:width con valor FILL_PARENT) y usar gravedad derecha en el contenido.

Por último, para definir el color del texto OK en el código Java se ha utilizado el método parseColor de la clase Color. Este método acepta la misma cadena de texto que permite definir colores en un archivo XML.

Hay, no obstante, otras formas de crear y utilizar colores tanto en XML como en Java. Las veremos en un futuro post.

Recuerda que en esta página dispones de enlaces a todos los artículos sobre Android que he publicado en La columna 80.

Una respuesta to “Diseños Android básicos: LinearLayout”

  1. Pablo Sanz said

    Me ha resultado muy interesante y didáctico. Creo que me voy a leer todos los artículos tuyos. Enhorabuena.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s