Diseño Android: Dashboard UI con LinearLayout


VERSIONES

  1. 27/12/2012 (Primera publicación)
  2. 10/08/2013:
    • Proyecto migrado de ActionBar Sherlock a ActionBarCompat
    • Actualizado y testeado en Jelly Bean 4.3
  3. 11/09/2015:
    • Actualizado a Android Marshmallow
    • PercentRelativeLayout
    • Nuevo dashboard con ImageView simulando botones

    android

    El patrón de diseño Dashboard, que podríamos traducir como Panel de control, es un patrón de diseño de interfaces gráficas que llegó a ser muy popular entre las aplicaciones Android. Su objetivo es ubicar en una misma pantalla principal las acciones/secciones más importantes de la aplicación. Por ejemplo, en versiones antigüas Yelp utilizaba este patrón.

    yelp

    Actualmente (2015) su utilización es escasa y se prefiere utilizar elementos como NavigationDrawner. De hecho, Yelp optó por esta solución y ya no utiliza el dashboard mostrado anteriormente.

    No hay un widget o layout Dashboard en la SDK de Android pero podemos recurrir a varias alternativas:

    • Implementar un layout adecuado.
    • Utilizar un GridView.
    • Utilizar un layout ya existente que permita crear una visualización de “celdas” de tamaño ajustable

    Optaremos por el último enfoque que a su vez se puede aplicar de dos formas distintas:

    • Utilizar un LinearLayout y repartir el espacio disponible entre sus hijos de forma proporcional gracias al atributo layout_weight
    • Utilizar PercentRelativeLayout, publicado por Google en agosto de 2015

    La segunda opción es más fléxible y eficiente pero el presente tutorial utiliza la primera ya que RelativePercentLayout no estaba disponible en el momento de su primera redacción (2012). Por tanto, recomiendo echar un vistazo antes al Tip Android #38: PercentRelativeLayout para la construcción de un dashboard.

    EL ATRIBUTO layout_weight

    El uso del atributo layout_weight será la base de nuestro dashboard. Este atributo permite que dentro de un linear layout se distribuya de forma proporcional todo el espacio no ocupado entre sus hijos según la “importancia” que cada uno de ellos tenga asignado dado el valor de su weight. Vamos a verlo con un ejemplo para un LinearLayout horizontal, aunque es aplicable también para uno vertical:

    • Si tenemos en el linear layout horizontal tres widgets, por ejemplo Checkbox, se apilarían de la siguiente forma :

      cbksinweight

      Pero si a cada uno se le define el atributo android:layout_weight=”1″, el espacio sobrante se repartirá por igual entre los tres widgets, y como consecuencia todos estarán separados por la misma distancia:

      cbkconweight

    • Del mismo modo, se puede comprobar que si uno tuviera como weight por ejemplo 3 y los otros 1, el que tiene tres consumirá el triple del espacio sobrante que los elementos que tienen uno. Y, al igual que en el caso anterior, se repartirá todo el espacio horizontal disponible para el layout y tendremos la siguiente disposición:
    • cbkconweight3

      Por defecto, el valor de weight es 0 lo que implica que cada widget ocupa sólo el espacio necesario para mostrarse según su width. Es el comportamiento “normal” al que estamos acostumbrados y que simplemente apila los elementos, a no ser que juguemos un poco con los márgenes y el padding. Es el comportamiento que observamos en la primera captura de los checkboxes.

      Gracias a esta propiedad, vamos a poder construir un dashboard con botones que ocupen todo el espacio disponible en la pantalla, o bien los posicionaremos de manera que el espaciado sea proporcional.

      Expandir el ancho de los botones

      Nuestro dashboard tendrá seis botones(Button con texto e imagen) organizados en tres filas en formato vertical y dos filas si el dispositivo está en posición horizontal. La estructura que seguirá la interfaz es la siguiente:

      width

      Lo más destacable es que cada botón tiene asignado el valor android:layout_weigh a uno.

      <?xml version="1.0" encoding="utf-8"?>
      <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:fillViewport="true" >
      
          <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:background="@color/background"
              android:orientation="vertical"
              android:padding="@dimen/top_padding" >
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="15dp"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button1"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon1"
                      android:onClick="clicked"
                      android:text="@string/button1" />
      
                  <Button
                      android:id="@+id/button2"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon2"
                      android:onClick="clicked"
                      android:text="@string/button2" />
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button3"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon3"
                      android:onClick="clicked"
                      android:text="@string/button3" />
      
                  <Button
                      android:id="@+id/button4"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon4"
                      android:onClick="clicked"
                      android:text="@string/button4" />
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button5"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon5"
                      android:onClick="clicked"
                      android:text="@string/button5" />
      
                  <Button
                      android:id="@+id/button6"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon6"
                      android:onClick="clicked"
                      android:text="@string/button6" />
              </LinearLayout>
          </LinearLayout>
      
      </ScrollView>
      

      Los botones, puesto que se van a repetir muchas veces, están configurados mediante estilos:

      <resources>
       <style name="dashboard_button">
              <item name="android:layout_width">fill_parent</item>
              <item name="android:layout_height">wrap_content</item>
              <item name="android:layout_weight">1</item>      
              <item name="android:background">@drawable/buttonselector</item>
              <item name="android:padding">5dp</item>
              <item name="android:drawablePadding">8dp</item> 
          </style>
      </resources>
      

      Y este es el drawable que se le aplicará a cada uno:

      <selector xmlns:android="http://schemas.android.com/apk/res/android">
      
          <!-- PRESS -->
          <item android:state_pressed="true">
              
              <shape android:shape="rectangle">                   
      			<solid android:color="@color/buttonpressed"/>           
       			<corners android:bottomLeftRadius="9dp" android:bottomRightRadius="9dp" android:topLeftRadius="9dp" android:topRightRadius="9dp" />        
              </shape>
              
          </item>
          
          
          <!-- FOCUS -->
          <item android:state_focused="true">
              
              <shape android:shape="rectangle">           
                  <solid android:color="@color/buttonfocus"/>
      			<corners android:bottomLeftRadius="9dp" android:bottomRightRadius="9dp" android:topLeftRadius="9dp" android:topRightRadius="9dp" />
              </shape>
              
           </item>
      
          <!-- SELECTED -->
          <item android:state_selected="true">
              
      		<shape android:shape="rectangle">
                  <solid android:color="@color/buttonfocus"/>
                  <corners android:bottomLeftRadius="9dp" android:bottomRightRadius="9dp" android:topLeftRadius="9dp" android:topRightRadius="9dp" />
              </shape>        
              
          </item>
          
          <!-- DEFAULT -->
      		<item android:drawable="@android:color/transparent"/>
      
      </selector>
      

      El resultado en un AVD hdpi y Android 2.1 es el siguiente:

      android-dashboard1-width

      Para el formato landscape, basta con reubicar los botones del layout.

      <?xml version="1.0" encoding="utf-8"?>
      
      <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:fillViewport="true" >
      
          <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:background="@color/background"
              android:orientation="vertical"
              android:padding="@dimen/top_padding" >
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="15dp"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button1"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon1"
                      android:onClick="clicked"
                      android:text="@string/button1" />
      
                  <Button
                      android:id="@+id/button2"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon2"
                      android:onClick="clicked"
                      android:text="@string/button2" />
      
                  <Button
                      android:id="@+id/button3"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon3"
                      android:onClick="clicked"
                      android:text="@string/button3" />
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button4"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon4"
                      android:onClick="clicked"
                      android:text="@string/button4" />
      
                  <Button
                      android:id="@+id/button5"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon5"
                      android:onClick="clicked"
                      android:text="@string/button5" />
      
                  <Button
                      android:id="@+id/button6"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon6"
                      android:onClick="clicked"
                      android:text="@string/button6" />
              </LinearLayout>
          </LinearLayout>
      
      </ScrollView>
      

      android-dashboard-width-landscape

      Expandir el dashboard a toda la pantalla

      El resultado no es del todo satisfactorio en formato vertical ya que los botones ocupan todo el ancho pero queda un tercio de la altura desocupada. Para hacer que el dashboard ocupe toda la altura disponible, basta con repetir la jugada que hicimos con los botones y asignar el atributo android:layout_weigh a uno también a los LinearLayout que representan cada fila.

      <?xml version="1.0" encoding="utf-8"?>
      
      <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:fillViewport="true" >
      
          <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:background="@color/background"
              android:orientation="vertical"
              android:padding="@dimen/top_padding" >
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_marginTop="15dp"
                  android:layout_weight="1"
                  android:gravity="center"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button1"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon1"
                      android:onClick="clicked"
                      android:text="@string/button1" />
      
                  <Button
                      android:id="@+id/button2"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon2"
                      android:onClick="clicked"
                      android:text="@string/button2" />
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_weight="1"
                  android:gravity="center"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button3"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon3"
                      android:onClick="clicked"
                      android:text="@string/button3" />
      
                  <Button
                      android:id="@+id/button4"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon4"
                      android:onClick="clicked"
                      android:text="@string/button4" />
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_weight="1"
                  android:gravity="center"
                  android:orientation="horizontal" >
      
                  <Button
                      android:id="@+id/button5"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon5"
                      android:onClick="clicked"
                      android:text="@string/button5" />
      
                  <Button
                      android:id="@+id/button6"
                      style="@style/dashboard_button"
                      android:drawableTop="@drawable/icon6"
                      android:onClick="clicked"
                      android:text="@string/button6" />
              </LinearLayout>
          </LinearLayout>
      
      </ScrollView>
      

      Hay que tener cuidado con esta forma de proceder y no abusar puesto que al anidar elementos con su weight definido la utilizadad lint lanza un warning ya que se pueden tener problemas de rendimiento. En algunos casos, podremos resolverlos recurriendo a RelativeLayout siempre y cuando no necesitemos el weight (recordad nuevamente la existencia del novedoso PercentRelativeLayout que constituye una mejor solución que la solución propuesta en este tutorial).

      Lint warning

      Ahora sí que hemos conseguido que el dashboard distribuya los botones proporcionalmente entre todo el espacio disponible:

      android-dashboard-full

      El resultado también es bueno en horizontal en un dipositivo de gran resolución como Nexus 7:

      android-dashboard-full-landscape

      Botones centrados y no expandidos

      En los ejemplos anteriores, los botones ocupan todo el ancho disponible lo que podemos comprobar simplemente viendo el cambio del color de fondo al presionarlos. Esto no supone ningún problema puesto que los botones no tienen bordes definidos, pero es posible que necesitemos que el dashboard ocupe toda la pantalla y que los botones no se expandan y conserven siempre el tamaño mínimo que necesiten según su imagen/texto mostrado.

      Para resolver este caso, la solución que he implementado consiste en “envolver” cada botón en un LinearLayout y darle a este layout el weight que tenía asignado el botón. Con esto conseguimos que el botón quede centrado con su tamaño dentro de un “contenedor” que es el que se expande y permite que los botones se ubiquen proporcionalmente por toda la pantalla, y además no aumentamos el “anidamiento” de weight. Funciona pero es un solución un tanto “sucia”; otra posible solución mejor es la que veremos en la siguiente sección.

      center

      <?xml version="1.0" encoding="utf-8"?>
      
      <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:fillViewport="true" >
      
          <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:background="@color/background"
              android:orientation="vertical"
              android:padding="@dimen/top_padding" >
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_marginTop="15dp"
                  android:layout_weight="1"
                  android:baselineAligned="false"
                  android:gravity="center"
                  android:orientation="horizontal" >
      
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button1"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon1"
                          android:onClick="clicked"
                          android:text="@string/button1" />
                  </LinearLayout>
      
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button2"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon2"
                          android:onClick="clicked"
                          android:text="@string/button2" />
                  </LinearLayout>
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_weight="1"
                  android:gravity="center"
                  android:orientation="horizontal" android:baselineAligned="false">
      
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button3"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon3"
                          android:onClick="clicked"
                          android:text="@string/button3" />
                  </LinearLayout>
      
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button4"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon4"
                          android:onClick="clicked"
                          android:text="@string/button4" />
                  </LinearLayout>
              </LinearLayout>
      
              <LinearLayout
                  android:layout_width="fill_parent"
                  android:layout_height="0dp"
                  android:layout_weight="1"
                  android:gravity="center"
                  android:orientation="horizontal" android:baselineAligned="false">
                  
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button5"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon5"
                          android:onClick="clicked"
                          android:text="@string/button5" />
                  </LinearLayout>
      
                  <LinearLayout
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_weight="1"
                      android:gravity="center" >
      
                      <Button
                          android:id="@+id/button6"
                          style="@style/dashboard_button_center"
                          android:drawableTop="@drawable/icon6"
                          android:onClick="clicked"
                          android:text="@string/button6" />
                  </LinearLayout>
              </LinearLayout>
          </LinearLayout>
      
      </ScrollView>
      

      Los botones reciben un nuevo estilo heredado del anterior:

        <style name="dashboard_button_center" parent="@style/dashboard_button">
              <item name="android:layout_width">wrap_content</item>        
              <item name="android:layout_weight">0</item>     
          </style>
      

      En la siguiente captura el resaltado del botón confirma que su tamaño es el mínimo necesario y no está ocupando todo el ancho “sobrante”, pero el dashboard sigue ocupando toda la pantalla como en el ejemplo anterior.

      android-dashboard-center

      Grid completa con botón pulsable en toda la celda

      La última aproximación que veremos al problema del Dashboard con LinearLayout es la que suele resultar más lógica: todo el área de cada “celda” de nuestra grid será el botón. La implementación propuesta, en lugar de utilizar un botón, utiliza un ImageButton que hará que la imagen se expanda hasta ocupar toda la celda; en este caso no usaremos un texto para el botón, sólo la imagen. Una fila de botones quedará como sigue

         <LinearLayout
              android:layout_width="fill_parent"
              android:layout_height="0dp"
              android:layout_weight="1"
              android:gravity="center"
              android:orientation="horizontal" >
      
              <ImageButton
                  style="@style/dashboard_image_button"
                  android:contentDescription="@string/button1"
                  android:onClick="clicked"
                  android:src="@drawable/icon1" />
      
              <ImageButton
                  style="@style/dashboard_image_button"
                  android:contentDescription="@string/button2"
                  android:onClick="clicked"
                  android:src="@drawable/icon2" />
          </LinearLayout>
      

      aplicándose este estilo al ImageView:

          <style name="dashboard_image_button">
              <item name="android:layout_width">0dp</item>
              <item name="android:layout_height">match_parent</item>
              <item name="android:layout_weight">1</item>
              <item name="android:background">@drawable/buttonselector</item>
              <item name="android:padding">@dimen/imagebutton_padding</item>
              <item name="android:scaleType">fitXY</item>
              <item name="android:adjustViewBounds">true</item>
          </style>
      

      imagebutton

      Demo completa en GitHub

      Se encuentra disponible en GitHub la demo completa de todo lo visto en el presente tutorial. Esta demo tiene como dependencia ActionBarCompat que deberá ser importada en Eclipse y definida en el proyecto como una librería (más información aquí). Asimismo, para más información sobre cómo utilizar GitHub, consultar este artículo

One Response to Diseño Android: Dashboard UI con LinearLayout

  1. Excelente muchas gracias por todos los tutos..!!

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

A %d blogueros les gusta esto: