Cómo crear interfaces de usuario dinámicas para dispositivos Android*

Descargar el artículo

Descargar Cómo crear interfaces de usuario dinámicas para dispositivos Android* [PDF 1MB]

Sinopsis

¿Qué significa "interfaz de usuario dinámica" para los desarrolladores de Android*? Los desarrolladores de aplicaciones para Android necesitan que la interfaz de usuario (UI, user interface) de sus aplicaciones se adapte a la dinámica natural del contenido de estas aplicaciones. También se espera que la UI se ajuste bien a la mayoría de los dispositivos Android y funcione fluidamente en ellos, más allá del tamaño de la pantalla, la resolución o la densidad de píxeles. Dada la variedad de dispositivos Android que hay en el mercado y la exigencia de mantenerse al ritmo de actualización de los SDK de Android, este puede ser un reto complicado.

En este documento, nos concentraremos en un puñado de técnicas de programación útiles para lograr una UI dinámica: uso de la barra de acciones, pestañas y vistas deslizables (swipe) con datos de aplicaciones dinámicas para mejorar la navegación de la pantalla, uso de fragmentos de Android para diseños multipanel y de vista maestra para diferentes tamaños de pantallas, y el uso del sistema de recursos de Android con la finalidad de mejorar la presentación de los gráficos y el contenido de texto para pantallas con diferentes resoluciones y densidades.

Índice

  1. Introducción
  2. Ejemplo de aplicación: menú de restaurante
  3. Barra de acciones, pestañas, vista deslizable y datos de aplicaciones dinámicas
  4. Fragmentos de UI de Android, diseño multipanel y vista maestra/detallada
  5. Sistema de recursos de Android, gráficos y texto, resolución y densidad de la pantalla
  6. Conclusión
  7. Enlaces de consulta

Introducción

El tema que se tratará en este documento es la creación de interfaces de usuario dinámicas, desde tres aspectos:

  1. Mostrar el contenido con el patrón y los controles más recientes de UI de Android.Lo que se espera, como mínimo, es que la UI de la aplicación se comporte como el resto de los sistemas Android. Una de las claves es diseñar la aplicación con los componentes, los controles y los patrones de navegación más recientes. Si uno quiere cierto grado de personalización, no hay problema, pero conviene respetar las últimas pautas y tendencias de Android. ¿Y cómo presentamos el contenido de aplicaciones dinámicas con esta última tendencia de UI? Aquí vamos a mostrar cómo usar los componentes más recientes de UI de Android, tales como barra de acciones, pestañas y vistas deslizables, para presentar datos de aplicaciones dinámicas.
  2. Diseño de la UI y flujo de navegación. ¿Tiene sentido tener el mismo diseño de UI para teléfonos de 4" y tabletas electrónicas de 11"? Si se desea aprovechar al máximo las pantallas grandes, a veces conviene usar diferentes diseños para diferentes tamaños de pantalla. En este documento, comentaremos el uso de Android Fragment para crear diseños multipanel o de un solo panel y vistas maestras/detalladas que se ajusten a pantallas de diferente tamaño.
  3. Resolución de la pantalla y densidad. Además del diseño de la UI, si la aplicación tiene gráficos, ¿qué se puede hacer para asegurarse de que los gráficos no se estiren o pixelen en dispositivos con diferentes resoluciones de pantalla y densidades de píxeles? ¿Y qué ocurre con el tamaño de letra de los elementos de texto? Puede que un elemento de texto con letra de tamaño 20 se vea perfecto en los teléfonos, pero quede demasiado pequeño en las tabletas. Veremos qué se puede hacer con el sistema de recursos de Android para encargarnos de estas cuestiones.

Ejemplo de aplicación: menú de restaurante

Para ilustrar los conceptos de programación que se describen en este documento, escribí una aplicación de Android que permite a los usuarios navegar un menú de restaurante organizado por categorías de comida. Esta aplicación incluye ejemplos de programación de los temas que se tratan aquí.

Se da por supuesto que los lectores de este artículo tienen conocimientos básicos de programación en Java y conceptos de desarrollo en Android. No es la intención que este texto sea una guía instructiva de Android, sino que se centra en el reducido grupo de técnicas esenciales para UI con la idea de ayudar a los desarrolladores a crear una UI dinámica.

Además, los fragmentos de código que se incluyen son código de ejemplo tomado de la aplicación del menú. Estos fragmentos de código se escogieron para ilustrar solo los conceptos de programación que se tratan en este documento. No dan una visión integral de la estructura y los detalles de la aplicación. Todo aquel interesado en tener una visión completa del desarrollo de aplicaciones Android a partir de la aplicación de ejemplo, que incluya el manejo del ciclo de vida de los fragmentos, la actualización de la selección de elementos de cuadrícula de UI, la retención de la selección del usuario y datos de aplicaciones desde un fragmento durante el cambio de configuración, o ejemplos de cómo dar estilo a la UI en el recurso, pueden obtener información más detallada en los enlaces de consulta para desarrolladores Android de la última sección del artículo.

Barra de acciones, pestañas, vista deslizable y datos de aplicaciones dinámicas

Al trabajar en la UI de la aplicación "Menú de restaurante", hay varios puntos que se deben tener en cuenta:

  1. La UI debe permitir a los usuarios acceder a las funciones esenciales de la aplicación desde la pantalla principal.
  2. La UI debe permitir al dueño de la aplicación de menú de restaurante agregar y eliminar platos y artículos dinámicamente.
  3. La UI debe mantener una presentación homogénea y permitir cambiar de vistas entre categorías de comidas.
  4. La UI debe presentar la comida y la información del menú por medio de imágenes siempre que sea posible.
  5. La UI debe permitir, para navegar la pantalla, usar gestos que sean intuitivos para el sistema Android.

Para cumplir con estos requisitos, se eligieron la barra de acciones, las pestañas y las vistas deslizables de Android. En realidad, desde Android 3.0, estos elementos de UI son uno de los patrones de UI más importantes que recomienda Android. Estos elementos se usan en la mayoría de las aplicaciones Android estándares, como la de correo electrónico, la de música, el almanaque, Hangouts y Play Store. Las siguientes capturas de pantalla muestran cómo presentamos un menú de restaurante navegable con estos elementos de UI.



Figura 1: Vista general de pantalla de una aplicación de menú de restaurante

Cómo crear una barra de acción y pestañas con vistas deslizables

En esta sección describimos cómo crear una barra de acción y pestañas con vistas deslizables para una aplicación Android.

  1. El primer paso es agregar ViewPager al archivo de diseño de la pantalla principal con el fin de controlar las vistas deslizables para las pestañas (activity_main.xml).
        <android.support.v4.view.ViewPager        
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity" >
    
            <android.support.v4.view.PagerTitleStrip
                android:id="@+id/pager_title_strip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="top"
                android:background="#333333"
                android:paddingBottom="4dp"
                android:paddingTop="4dp"
                android:textColor="#ffffff"/>        
        </android.support.v4.view.ViewPager>
    
  2. En MainActivity.java (la pantalla principal de la aplicación), hay que inflar el layout activity_main.xml, recuperar el ViewPager del archivo de diseño de la actividad, crear un ViewPagerAdapter para controlar la creación y la inicialización de cada página para la etiqueta, y asignar el OnPageChangeListener al ViewPager.
    // Se establece la vista deslizable para cada pestaña
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setOnPageChangeListener(this);
    mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), this);
    mViewPager.setAdapter(mPagerAdapter);
    
  3. Ahora es momento de implementar OnPageChangeListener y controlar las tareas relacionadas con la aplicación durante el cambio de vista. El código debería, como mínimo, establecer la pestaña seleccionada en la barra de acciones cuando los usuarios deslizan la vista para pasar a la pestaña siguiente.
    /**
     * * Se llama a este método cuando el usuario se desliza de una pestaña a otra
     */
    public void onPageSelected(int position) {
            // al cambiar la página
            // hacer que quede seleccionada la pestaña respetada
            mActionBar.setSelectedNavigationItem(position);
            mCurrentViewedCategory = (String) mActionBar.getTabAt(position).getText();
    }
    
    /**
     * Llamada de respuesta relacionada con la vista deslizable de pestaña
     */
    public void onPageScrolled(int arg0, float arg1, int arg2) {
    }
    
    /**
     * Llamada de respuesta relacionada con la vista deslizable de pestaña
     */
    public void onPageScrollStateChanged(int arg0) {
    }
    
    
  4. Hay que definir PagerAdapter (hereda de FragmentStatePagerAdapter) para que controle la vista de cada pestaña. En el ejemplo, PagerAdapter está definida como clase interna de MainActivity.java. Se llama a "getItem" durante la inicialización de cada página. En este caso, cada página contiene un fragmento de la vista maestra/detallada de los datos del menú que se muestra en la Figura 1. Los detalles de la programación de fragmentos se verán en la próxima sección.

    Consejos

    Hay dos tipos de PagerAdapter: FragmentPagerAdapter y FragmentStatePagerAdapter. Para el manejo eficiente de la memoria, se recomienda el primero si el número del paginador es fijo, y el segundo si ese número se asigna dinámicamente. En el caso de FragmentStatePagerAdapter, el paginador se destruye cuando el usuario navega hacia fuera de la página. El ejemplo usa FragmentStatePagerAdapter porque el número de categoría de comida puede cambiar según los datos de la aplicación.

    /**
    * El adaptador del paginador de fragmentos controla la vista deslizable de la pestaña. Cada pestaña contiene  
    * un fragmento Ultimate que incluye una vista de menú de cuadrícula y una vista detallada.  
    * Según la orientación del dispositivo, la aplicación decide si  
    * mostrar ambas vistas o solo la de cuadrícula.
    */
    
    class PagerAdapter extends FragmentStatePagerAdapter {	  
        UltimateViewFragment ultimateViewFragment;
        FragmentActivity mFragmentActivity;
        UltimateViewFragment[] fragmentArray = new  
            UltimateViewFragment[mCategory.size()];
    		
    public PagerAdapter(FragmentManager fm, FragmentActivity fragmentActivity) {
         super(fm);		
         mFragmentActivity = fragmentActivity;
    }
    		
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        super.instantiateItem(container, position);
        UltimateViewFragment fragment = (UltimateViewFragment)     
            super.instantiateItem(container, position);
            fragment.setGridItemListener((GridItemListener) 
            mFragmentActivity);
            fragmentArray[position] = fragment;
            return fragment;
    } 
    		
    @Override
    public Fragment getItem(int position) {
        Bundle args = new Bundle();
        // Cada vista está asociada a una categoría de menú
        args.putString(MenuFactory.ARG_CATEGORY_NAME, 
        mCategory.get(position));
        ultimateViewFragment = new UltimateViewFragment();
        ultimateViewFragment.setArguments(args);
        // Registrarse como GridItemListener para recibir notificación de  
        // clic en elemento de cuadrícula
        ultimateViewFragment.setGridItemListener((GridItemListener) 
        mFragmentActivity);
        fragmentArray[position] = ultimateViewFragment;
        return ultimateViewFragment;
    }
    
    @Override
        public int getCount() {
            // Devolución de la cantidad de pestañas
            return mCategory.size();
     }
    
    @Override 
        public CharSequence getPageTitle(int position) {
           //Devolución del título de cada pestaña
            return mCategory.get(position);
        }
    }
    
    
  5. Ahora se recupera ActionBar (mActionBar) de Activity y se establece el modo de navegación como NAVIGATION_MODE_TABS
  6. Se agregan pestañas a la barra de acciones con mActionBar.addTab y se inicializa el título de la pestaña con el texto especificado.
    // Configuración de la barra de acciones y las pestañas
    mActionBar = getActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);		
    for (int i = 0; i < mCategory.size(); i++) {
        mActionBar.addTab(mActionBar.newTab().setText(
        mCategory.get(i)).setTabListener(this));
        // Se inicializan los elementos seleccionados en la tabla hash  
        // con el primer elemento de cada categoría
        if (savedInstanceState == null) {
             mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuWithCategory(
                   mCategory.get(i)).get(0));
        } else {
             //Se actualiza mSelectedItems desde la última instancia guardada
             String[] selectedItems = savedInstanceState.getStringArray("selectedItems");
             mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuItem(
             mCategory.get(i),selectedItems[i]));	    	    
         }
     }
    
    

Consejos

La API ActionBar se introdujo por primera vez en Android 3.0 (nivel de API 11), pero también está en la biblioteca de soporte para compatibilidad con Android 2.1 (nivel de API 7) y superiores. En el código de ejemplo se usa la biblioteca android-support-v4.jar. Este jar se encuentra en la carpeta de bibliotecas en el directorio root de la aplicación. Si se usa Eclipse como entorno de desarrollo integrado (IDE), hay que agregar la ruta a la biblioteca en Project Properties->Java Build Path->Libraries.

Cómo agregar elementos de acción a la barra de acciones

La barra de acciones contiene elementos de acción que el usuario usa con frecuencia. Estos elementos se sitúan en la parte superior de la barra para que sea fácil acceder a ellos. En el código de ejemplo, definimos algunos elementos de acción, tales como cámara, buscar, llamar, salir y configuración, como se indica en la captura de pantalla de abajo.



Figura 2: Barra de acciones con elementos de acción

A continuación mostramos cómo agregar elementos de acción a la barra.

  1. Hay que definir los elementos de acción en xml bajo res/menu (p. ej., action_bar.xml)
    <menu xmlns:android="http://schemas.android.com/apk/res/android" >
        
        <item android:id="@+id/action_camera"
              android:icon="@drawable/ic_action_camera"
              android:title="@string/action_camera"
              android:showAsAction="always" />
        <item android:id="@+id/action_search"
              android:title="@string/action_search_str"
              android:icon="@drawable/ic_action_search"
              android:showAsAction="always"
              android:actionViewClass="android.widget.SearchView" />
        <item android:id="@+id/action_call"
              android:icon="@drawable/ic_action_phone"
              android:title="@string/action_call"
              android:showAsAction="always" />
        <item android:id="@+id/action_checkout"
              android:icon="@drawable/ic_action_shoppingcart_checkout"
              android:title="@string/action_checkout"
              android:showAsAction="always"/>
        <item android:id="@+id/action_settings"
            android:orderInCategory="100"
            android:showAsAction="never"
            android:title="@string/action_settings"/>
      
    </menu>
    
    
  2. Ahora se debe inflar el menú de elementos de acción en el código (MainActivity.java)
    /**
    * Inicialización del menú de acciones en la barra
    */
    public boolean onCreateOptionsMenu(Menu menu) {
    	getMenuInflater().inflate(R.menu.action_bar, menu);
    		
    	//Configuración de la función de búsqueda
    	SearchManager searchManager =
    		(SearchManager) getSystemService(Context.SEARCH_SERVICE);
    	SearchView searchView =
    		(SearchView) menu.findItem(R.id.action_search).getActionView();
    		searchView.setSearchableInfo(
    		           searchManager.getSearchableInfo(getComponentName()));
    		return super.onCreateOptionsMenu(menu);
    }
    
    
  3. A continuación, mostramos cómo controlar los clics en los elementos de acción en el código.
    /**
    * Se llama a este método cuando el usuario hace clic en una acción de la barra
    */
    public boolean onOptionsItemSelected(MenuItem item) {
    	if (mDrawerToggle.onOptionsItemSelected(item)) {
    	          return true;
    	 }
    
    	 // Control de cuando se presionan elementos de la barra de acciones
    	 switch (item.getItemId()) {
    	    // Control de la acción de navegación hacia arriba o al inicio
    	    case android.R.id.home:
    	    	NavUtils.navigateUpFromSameTask(this);
    	    	return true;
    	    // Manejo de búsqueda
    	    case R.id.action_search:
    	            	return true;	            
    	    // Manejo de configuración
    	    case R.id.action_settings:
    	            	return true;	            
    	    // Manejo de cámara
    	    case R.id.action_camera:
    	            	return true;
    	    //Control de la función de salir
    	    case R.id.action_checkout:
    	            return true;
    	        default:
    	            return super.onOptionsItemSelected(item);
    }
    
    

Consejos

Como se muestra en la Figura 1, la barra de acciones contiene elementos de acción que los usuarios utilizan con frecuencia, como la cámara, las búsquedas y la salida de la aplicación. Para que el aspecto y la funcionalidad sean coherentes en el sistema, se puede descargar el paquete de íconos Action Bar Icon Pack desde https://developer.android.com/design/downloads/index.html y usarlos en el área de recursos de la aplicación.

Estilo de la barra de acciones

Android presenta la barra de acciones con un color definido por el sistema que puede no siempre combinar con los colores de la aplicación. A veces, hay que darle a la barra un estilo y un color diseñados para el tema de la aplicación (o que respondan a necesidades comerciales).

Por ejemplo, la barra de acciones de esta aplicación de ejemplo tiene un color granate que coincide con el del ícono de la aplicación.


Figura 3: Ejemplo de estilo de la barra de acciones

En esta sección, veremos cómo mejorar el aspecto de la barra de acciones con el Generador de Estilos para la Barra de Acciones de Android.

  1. La herramienta se encuentra en http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html
  2. Se debe especificar el color que uno prefiera y descargar desde el enlace los archivos de recurso generados. Lo siguiente es lo que uso en la aplicación de ejemplo.



    Figura 4: Generador de estilos para la barra de acciones

  3. Esta herramienta genera un xml de estilo, imágenes, íconos para recursos de muestra, como los siguientes. Hay que agregar todos los archivos de recursos al área de dibujo de recursos de la aplicación y actualizar AndroidManifest.xml application:theme para usar el xml de estilo generado con la herramienta.



    Figura 5: Ejemplo de recursos de barra de acciones

    <activity
                android:theme="@style/Theme.Example"
                android:name="com.example.restaurant.MainActivity"       
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
    </activity>
    
    

Control de datos de aplicaciones dinámicas desde la UI

Es posible que la UI de la aplicación no siempre sea estática, en especial cuando tiene la necesidad de presentar datos que podrían cambiar durante el tiempo que se use la aplicación. Por ejemplo, las aplicaciones de álbumes de fotos permiten a los usuarios ver y modificar varias imágenes que podrían cambiar en cualquier momento mientras el dispositivo está encendido. Las aplicaciones de correo electrónico necesitan manejar los mensajes que se actualizan cada vez que transcurre un intervalo configurable desde el servidor. En la aplicación de ejemplo del restaurante, el menú de comidas es dinámico. El contenido de la pestaña de la barra de acciones y la cuadrícula del menú podrían cambiar según la categoría de comida y el menú.

Una manera de lidiar con los datos dinámicos es crear una fábrica de datos que actúe como traductora entre los datos sin procesar y la UI. La fábrica de datos extrae datos de la lógica de manipulación de datos de la UI, la cual permite que los cambios varíen sin cambiar la lógica de la UI. En el nivel alto, la fábrica de datos se comunica con las fuentes de datos para obtener datos sin procesar de la aplicación, procesa los datos sin procesar de diversas fuentes (red, sistema de archivos local, base de datos o caché) y los transforma en objetos de datos que pueden usar los componentes de la UI.

La siguiente imagen muestra un flujo simple entre la UI, la fábrica de datos y las fuentes de datos.



Figura 6: Flujos generales para controlar contenido de aplicaciones dinámicas

Consejos

Pasos para controlar el contenido de aplicaciones dinámicas.

  • Se identifican las posibles fuentes de datos, tales como red, sistema de archivos local, base de datos o caché del dispositivo.
  • Se crea una fábrica de datos que "escuche" los cambios en la fuente de datos o consulte datos periódicamente según las necesidades de la aplicación.
  • La fábrica de datos maneja la solicitud de actualización de datos en un subproceso diferente para evitar bloquear el subproceso de la UI, porque la solicitud y el procesamiento de datos podría tardar.
  • Los componentes de la UI se registran como proceso de escucha de cambios de datos para la fábrica de datos, y los componentes de la UI se actualizan cuando se recibe una notificación de cambio de datos.
  • La fábrica de datos administra los procesos de escucha del evento de cambio de datos y proporciona métodos convenientes para que los procesos de llamada consulten datos sin conocimiento del formato de los datos sin procesar.

Para simplificar la implementación en la aplicación de ejemplo, los datos del menú de comidas se almacenan en una matriz de cadenas en strings.xml.de Android. Cada elemento de la matriz contiene un registro del elemento de comida. En realidad, estos registros de datos se podrían definir y guardar en un servidor para permitir que los cambios se realicen dinámicamente. Independientemente del origen de los datos, se puede volver a usar el formato de datos definido en string.xml. A continuación se muestra cómo se procesan los datos de la aplicación en MenuFactory.java y cómo se inicializan componentes de la UI con los datos de la aplicación.

  1. Se deben definir los datos del menú en strings.xml. Cada artículo de comida tiene una cadena con el nombre de la categoría, el nombre en el menú, la descripción, datos nutricionales, el precio y el nombre de la imagen. Los campos de datos están separados por el delimitador ",,,".
    <string-array name="menu_array">
    
        <item>Appetizer,,,Angels on horseback,,,Oysters wrapped in bacon, served hot. In the United Kingdom they can also be a savoury, the final course of a traditional British ,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg; Carbohydrate 19g; Fiber 3g; Protein 31g,,,6.99,,,Angels on horseback.jpg</item>   
         
        <item>Appetizer,,,Batata vada,,,A popular Indian vegetarian fast food in Maharashtra, India. It literally means potato fritters. The name "Batata" means potato in English. It consists of a potato mash patty coated with chick pea flour, then deep-fried and served hot with savory condiments called chutney. The vada is a sphere, around two or three inches in diameter.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg; Carbohydrate 19g; Fiber 3g; Protein 31g,,,7.99,,,Batata vada.jpg</item>
    
        <item>Appetizer,,,Barbajuan,,,An appetizer mainly found in the eastern part of French Riviera and Northern Italy.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg; Carbohydrate 19g; Fiber 3g; Protein 31g,,,8.99,,,Barbajuan.jpg</item>
    
         <item>Appetizer,,,Blooming onion,,,Typically consists of one large onion which is cut to resemble a flower, battered and deep-fried. It is served as an appetizer at some restaurants.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg; Carbohydrate 19g; Fiber 3g; Protein 31g,,,9.99,,,Blooming onion.jpg</item>
    
    </string-array>
    
    
  2. Durante el inicio de la aplicación, MainActivity.java crea una referencia hacia MenuFactory como singleton y carga los datos desde el área de recursos de Android.
    mMenuFactory = MenuFactory.getInstance(res);
    mMenuFactory.loadDataFromAndroidResource();
    
  3. MenuFactory procesa los datos de strings.xml y los transforma en objetos MenuItem que usan las vistas de la UI.
    /* Permite al proceso de llamada cargar los datos de la aplicación desde el área de recursos de Android */
    public void loadDataFromAndroidResource() {	
        if (mMenuItems != null && mMenuItems.size() > 0) {
            clear();
        }		
        mMenuItems = new ArrayList<MenuItem>();
        mCategoryList = new ArrayList<String>();
    		
        String[] menuList = mResources.getStringArray(R.array.menu_array);
        MenuItem menuItem;
        String[] currentMenu;
        String currentCategory = "";
    		
        for (int i = 0; i<menuList.length; i++) {
    	currentMenu = menuList[i].split(",,,");
    	menuItem = new MenuItem();
    	for (int j = 0; j< currentMenu.length; j++) {
    	    switch (j) {
    		case 0:
    			menuItem.setCategory(currentMenu[j]);
    			if (!currentMenu[j].equals(currentCategory)) {
    				currentCategory = currentMenu[j];
    				mCategoryList.add(currentMenu[j]);
    			}
    			break;
    		case 1:
    			menuItem.setName(currentMenu[j]);						                break;
    		case 2:
    			menuItem.setDescription(currentMenu[j]);
    			break;
    		case 3:
    			menuItem.setNutrition(currentMenu[j]);						                break;
    		case 4:
    			menuItem.setPrice(currentMenu[j]);						                break;	
    		case 5:
    			menuItem.setImageName(currentMenu[j]);						                break;
    	    }
                   }
                   menuItem.setId(Integer.toString(i));		
                   mMenuItems.add(menuItem);
        }	
    }
    
  4. MenuFactory.java proporciona métodos convenientes para que los procesos de llamada pidan datos relacionados con las actualizaciones de la UI.
    /* Permite al proceso de llamada recuperar la lista de categorías basada en los artículos del menú */
    public ArrayList<String> getCategoryList() {
    	return mCategoryList;
    }
    	
    /* Permite al proceso de llamada recuperar una lista de menú según la categoría pasada */
    public ArrayList<MenuItem> getMenuWithCategory (String category) {
    	ArrayList<MenuItem> result = new ArrayList<MenuItem>();
    	for (int i = 0; i<mMenuItems.size(); i++) {
    		MenuItem item = mMenuItems.get(i);
    		if (item.category.equals(category)) {
    			result.add(item);
    		}			
    	}
    		
    	return result;
    }
    	
    /* Permite al proceso de llamada recuperar un artículo de menú según la categoría pasada y el índice */
    public MenuItem getMenuItem (String category, int index) {
    	ArrayList<MenuItem> menuList = getMenuWithCategory(category);
    	if (menuList.size() == 0) {
    		return null;
    	}
    	return menuList.get(index);
    }
    	
    /* Permite al proceso de llamada recuperar un artículo de menú según la categoría pasada y el nombre */
    public MenuItem getMenuItem (String category, String name) {
    	MenuItem result = null;
    	for (int i = 0; i<mMenuItems.size(); i++) {
    		MenuItem item = mMenuItems.get(i);
    		if (item.category.equals(category) && item.name.equals(name)) {
    			result = item;
    		}			
    	}	
    	return result;
    }
    
    /* Data structure for menu item */
    class MenuItem {
    	String category;
    	String name;
    	String price;
    	String description;
    	String nutrition;
    	ImageView image;
    	String imageName;
    	String id;
    		
    	public void setCategory(String str) {
    		category = str;
    	}
    		
    	public void setName(String str) {
    		name = str;
    	}
    	
    	public void setDescription(String str) {
    		description = str;
    	}
    		
    	public void setNutrition(String str) {
    		nutrition = str;
    	}
    		
    	public void setImageName(String str) {
    		imageName = str;
    	}
    		
    	public void setId(String str) {
    		id = str;
    	}
    		
    	public void setPrice(String str) {
    		price = str;
    	}
    		
    	public void setImageView(ImageView imageView) {
    		image = imageView;
    	}
    }
    
    
  5. Ahora se inicializa la vista de menú de cuadrícula con artículos de comida de MenuFactory. La implementación se realiza en MenuGridFragment.java. En la aplicación, los artículos de comida de cada categoría se muestran en una vista de cuadrícula GridView incrustada en un fragmento. Durante la inicialización del fragmento, se llama a MenuFactory para recuperar los artículos de comida de cada categoría. El adaptador de datos (ImageAdapter) de la vista de cuadrícula crea y representa cada artículo de comida durante la inicialización.
    /* Inflado del elemento de vista para la vista de cuadrícula e
    * inicialización del adaptador de imagen para la vista de cuadrícula. 
    */
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    	Bundle savedInstanceState) {
    		
    	View rootView = inflater.inflate(
    		R.layout.fragment_menu_grid, container, false);
    
    	mMenuList = mMenuFactory.getMenuWithCategory(mCategory);	
    	mGridView = (GridView) rootView.findViewById(R.id.gridview);
    	mGridView.setAdapter(mImageAdapter);
    	mGridView.setOnItemClickListener(this);
    	return rootView;
    }
    
    
        /* Adaptador de imagen para la vista de cuadrícula */
        class ImageAdapter extends BaseAdapter {
    	    private LayoutInflater mInflater;
    
    	    public ImageAdapter(Context c) {
    	        mInflater = LayoutInflater.from(c);
    	    }
    
    	    public int getCount() {
    	    	return mMenuList.size();
    	    }
    
    	    public Object getItem(int position) {
    	    	return mMenuList.get(position);
    	    }
    
    	    public long getItemId(int position) {
    	        return position;
    	    }
    	        
    	    // creación de un nuevo ImageView para cada elemento a que hace referencia Adapter
    	    public View getView(int position, View convertView, ViewGroup parent) {
    
    	        View v = convertView;
    	        ImageView picture;
    	        TextView name;
    	        TextView price;
    
    	        if(v == null) {
    	            v = mInflater.inflate(R.layout.view_grid_item, parent, false);
    	            v.setTag(R.id.picture, v.findViewById(R.id.picture));
    	            v.setTag(R.id.grid_name, v.findViewById(R.id.grid_name));
    	            v.setTag(R.id.grid_price, v.findViewById(R.id.grid_price));
    	        }
             
    	        picture = (ImageView)v.getTag(R.id.picture);
    	        name = (TextView)v.getTag(R.id.grid_name);
    	        price = (TextView) v.getTag(R.id.grid_price);
    
    	        MenuItem item = (MenuItem) mMenuList.get(position);
    	        
    	        InputStream inputStream = null;
    	        AssetManager assetManager = null;
    	        try {
    	        	assetManager = getActivity().getAssets();
    	        	inputStream =  assetManager.open(item.imageName);
    	        	picture.setImageBitmap(BitmapFactory.decodeStream(inputStream));
    	        } catch (Exception e) {
    	        } finally {	  
    	        }
    	    
    	        name.setText(item.name);
    	        price.setText(item.price);
    	        
    	        //Resaltado del elemento seleccionado      
    	        if (mSelectedPosition == position){
                    	updateGridItemColor(v, true);         
    	        } else {
    	            	updateGridItemColor(v, false);    
    	        }
    
    	        return v;
    	    }    
    	}
    
    
  6. Inicialización de las pestañas de la barra de acciones con categorías de comida de MenuFactory.
    // Recuperación de la lista de categorías de MenuFactory
    mCategory = mMenuFactory.getCategoryList();
    // Configuración de la barra de acciones y las pestañas
    mActionBar = getActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);		
    for (int i = 0; i < mCategory.size(); i++) {
    mActionBar.addTab(mActionBar.newTab().setText(
    	mCategory.get(i)).setTabListener(this));
    	 // Se inicializan los elementos seleccionados en la tabla hash
    	 // con el primer elemento de cada categoría
    	 if (savedInstanceState == null) {
    	  	mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuWithCategory(
    	    	            mCategory.get(i)).get(0));
    	    } else {
    	    	 //Se actualiza mSelectedItems desde la última instancia guardada
    	    	 String[] selectedItems = savedInstanceState.getStringArray("selectedItems");
    	    	 mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuItem(
    	    	          mCategory.get(i),selectedItems[i]));	    	    
    	    }
    }
    
    

Fragmentos de Android, diseño multipanel y vista maestra/detallada

Otro aspecto de las UI dinámicas es cómo diseñar la UI para la aplicación Android de manera que la misma aplicación se ajuste y funcione fluidamente en dispositivos de diferentes tamaños (como tabletas y teléfonos). En esta sección, comentaremos el uso de un fragmento de Android para crear diseños multipanel que se ajusten a dispositivos con pantallas de diferentes tamaños.

Android introdujo el concepto de "fragmento" en la versión 3.0. Se puede pensar en este concepto como una manera de "dividir en componentes" el diseño de la pantalla. La pantalla se divide en múltiples grupos o vistas de la UI. Cada grupo de la UI se implementa como fragmento. La aplicación decide qué grupos o vistas y qué flujo de navegación están disponibles para los usuarios durante el tiempo de ejecución en la pantalla del dispositivo en el cual se está ejecutando.

Un uso común que se da a los fragmentos de Android son los diseños multipanel, en los cuales las pantalla se presenta como una combinación de varias vistas. La interacción con una vista en la pantalla podría hacer que se actualice otra vista en la pantalla. La vista maestra/detallada es uno de los patrones de diseño de UI importantes para este concepto. La aplicación presenta un resumen del contenido en una vista maestra con un widget de vista de cuadrícula o lista. Si se selecciona un elemento de la cuadrícula o la lista, se muestra la vista detallada de ese elemento en la misma pantalla o en una distinta. En dispositivos con pantallas grandes (tabletas), tanto la vista maestra como la detallada pueden caber en la misma pantalla. En dispositivos más pequeños (teléfonos), la vista maestra y la detallada podrían presentarse en pantallas distintas.

La aplicación del menú de restaurante presenta información de menú para cada categoría de comida en una vista de cuadrícula. Cuando se selecciona el elemento de cuadrícula, se muestra el detalle del artículo en la misma pantalla si el dispositivo tiene pantalla grande, o en una diferente si la pantalla es pequeña. Este diseño se implementa en tres fragmentos:

  1. UltimateVIewFragment: fragmento que contiene un fragmento de vista de cuadrícula y uno de vista detallada; la visibilidad de los fragmentos interiores se determina durante el tiempo de ejecución a partir del tamaño de la pantalla (p. ej., la vista detallada solo se muestra si la pantalla del dispositivo es grande).
  2. GridViewFragment: fragmento que presenta los datos de menú para cada categoría de comida en una vista de cuadrícula.
  3. DetailViewFragment: fragmento que presenta la vista detallada de cada artículo seleccionado en la vista de cuadrícula.

Consejos

La mayoría de los ejemplos de código del sitio para desarrolladores de Android muestran la implementación de la vista maestra/detallada con dos fragmentos incrustados dentro de una actividad, pero no con dos fragmentos incrustados dentro de un fragmento. En la aplicación de ejemplo, implementamos esta última variante. La vista deslizable de las pestañas de la barra de acciones exige el uso de un fragmento, en lugar de una actividad. El código para el menú de restaurante muestra detalladamente cómo hacer esto con la vista deslizable de pestañas.



Figura 7: Vista maestra/detallada multipanel para diferentes tamaños de pantallas.

Creación de fragmentos

En esta sección se describen los pasos necesarios para crear los fragmentos que se usan en el ejemplo.

  1. Definir el diseño de la pantalla con un fragmento en xml. Los siguientes son diseños de pantalla para UltimateVIewFragment, GridViewFragment y DetailViewFragment. Se puede observar abajo que la visibilidad de la vista detallada está establecida inicialmente en "gone"; se la debería cambiar en el archivo de diseño respectivo para dispositivos con diferentes tamaños de pantalla. Por ejemplo, si el dispositivo tiene pantalla grande, la visibilidad se establece como "visible". Los detalles de esto se ven en la sección posterior "Diseños multipanel y de un solo panel según el tamaño de la pantalla".

    En esta sección solo se muestra el diseño de pantalla para UltimateViewFragment. Se recomienda ver el código de ejemplo completo del diseño de pantalla para GridViewFragment (layout/fragment_menu_grid.xml) y DetailViewFragment (layout/fragment_disch_detail.xml).

    <!--Diseño de pantalla de UltimateViewFragment  -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        
        <FrameLayout 
        	android:id="@+id/grid_fragment"
    		android:layout_width="wrap_content"
    		android:layout_height="wrap_content"
    		android:layout_weight="1"/>
    		
    	<FrameLayout 
    		android:id="@+id/detail_fragment"
    		android:layout_width="wrap_content"
    		android:layout_height="wrap_content"
    		android:layout_weight="1"
    		android:visibility="gone"
    		android:orientation="vertical"/>
    	
     </LinearLayout>
    
    
  2. Ahora se crean e inicializan fragmentos programáticamente y se usa FragmentManager para controlar las transacciones de los fragmentos. El siguiente fragmento de código muestra la implementación de UltimateViewFragment, que crea MenuGridFragment y DetailViewFragment durante el tiempo de ejecución. Aquellos que deseen conocer los detalles de la implementación de MenuGridFragment y DetailViewFragment pueden consultar el código de ejemplo completo.
    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        	       
            if (savedInstanceState != null) {  
                String tmp = savedInstanceState.getString("selectedIndex");
                if (tmp != null) {
                    mSelectedIndex = Integer.parseInt(tmp);
                }
                mCategory = savedInstanceState.getString("currentCategory", mCategory);
            } else {
                mCategory = (String) getArguments().getString(MenuFactory.ARG_CATEGORY_NAME);
            }
            mDetailViewFragment = new DetailViewFragment();
            mGridViewFragment = new MenuGridFragment();
                    
            mGridViewFragment.setCategory(mCategory);
            mGridViewFragment.setOnGridItemClickedListener(this);   
                
            FragmentManager fragmentManager = this.getChildFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            
            if (savedInstanceState != null) {      
                transaction.replace(R.id.detail_fragment, mDetailViewFragment);
                transaction.replace(R.id.grid_fragment, mGridViewFragment);
            } else {
                transaction.add(R.id.detail_fragment, mDetailViewFragment, "detail_view");
                transaction.add(R.id.grid_fragment, mGridViewFragment, "grid_view");
            }
            transaction.commit();  
    }
    

Consejos

Los fragmentos se deben crear durante el tiempo de ejecución de una actividad, en especial si se planea que haya un intercambio dinámico de salida y entrada de fragmentos de una pantalla. Con FragmentManager, se pueden agregar, reemplazar y quitar fragmentos de la pantalla. Como se muestra en el código, FragmentManager se puede recuperar de getChildFragmentManager, no de getFragmentManager, porque el contenedor de los fragmentos secundarios es un fragmento, no una actividad. Además, durante los cambios de orientación, para evitar agregar los mismos fragmentos uno encima del otro en UltimateViewFragment, el código debe reemplazar los fragmentos existentes con el nuevo por medio del uso de "replace", no "add" de FragmentTransaction.

Comunicación entre fragmentos y actividades

La comunicación entre fragmentos se puede controlar mediante el uso del patrón del proceso de escucha, que implica dos pasos sencillos:

  1. Definir una interfaz de proceso de escucha que puedan implementar los componentes interesados en recibir la notificación de otros componentes.
  2. En el caso de los fragmentos de vista maestra/detallada y multipanel, si un fragmento secundario envía la notificación, el contenedor de los fragmentos secundarios se registrará como proceso de escucha para el fragmento secundario. Luego de recibir la notificación, la actividad o el fragmento primarios pueden realizar las acciones apropiadas según la información recibida.

Así se hace en la aplicación del restaurante:

  1. Se define una interfaz GridItemListener. El contenedor del fragmento de cuadrícula implementa la interfaz. El fragmento de cuadrícula notifica al contenedor primario cuando se produce una selección de cuadrícula.
    /**
     * Interfaz implementada por clases que quieren recibir notificación
     * cuando se hace clic en un elemento de menú de la grilla. UltimateViewFragment,
     * ActionBarActivity y DetailView usan esta interfaz para comunicar el
     * elemento de menú seleccionado.
    */
    public interface GridItemListener {
    	public void onGridItemClick(com.example.restaurant.MenuFactory.MenuItem itemSelected, int position);
    }
    
    
  2. UltimateViewFragment proporciona un método para que el proceso de llamada se registre como GridItemListener.
    /* Permitir al proceso de llamada establecer el proceso de escucha del elemento de cuadrícula */
    public void setGridItemListener(GridItemListener gridItemListener) {
    	mGridItemListener = gridItemListener;
    }
    
    
  3. UltimateViewFragment notifica a su proceso de escucha que se produjo un cambio en la selección de cuadrícula.
    /* Control de evento de clic en elemento desde la cuadrícula de menú  */
    public void onGridItemClick(MenuItem itemSelected, int position) {
    	mGridItemListener.onGridItemClick(itemSelected, position);
    	mSelectedIndex = position;
    	View detail = getActivity().findViewById(R.id.detail_fragment);
    	//modo vertical
    	if (detail != null && detail.getVisibility() == View.GONE) {
    		Intent intent = new Intent(this.getActivity(), DetailActivity.class);
               		 intent.setAction("View");
                		intent.putExtra("category", itemSelected.category);
              		intent.putExtra("entree_name", itemSelected.name);
                		Activity activity = getActivity();
                		activity.startActivity(intent);		
                
           	 //modo horizontal
    	} else {
    		mDetailViewFragment.update(itemSelected);
    	}
    }
    
    
  4. En MainActivity, cada vista de pestaña es el contenedor primario del UltimateViewFragment. MainActivity se registra como GridItemListener para llevar cuenta del último artículo de comida seleccionado de cada categoría.
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
    super.instantiateItem(container, position);
    UltimateViewFragment fragment = (UltimateViewFragment) 
    super.instantiateItem(container, position);
    	fragment.setGridItemListener((GridItemListener) 
    	mFragmentActivity);
    fragmentArray[position] = fragment;
    return fragment;
    }
    
    
  5. MainActivity realiza las acciones apropiadas en la llamada de respuesta del proceso de escucha cuando se recibe una notificación.
    /**
     * A este método se lo llama cuando se hace clic en un elemento del menú de cuadrícula
    */
    public void onGridItemClick(com.example.restaurant.MenuFactory.MenuItem 
    	itemSelected, int position) {
    mSelectedItems.put(itemSelected.category, itemSelected);
    }
    
    

Diseños multipanel y de un solo panel dependientes del tamaño de la pantalla

Como se explicó anteriormente, con fragmentos de Android se pueden definir diseños multipanel para las pantallas. ¿Pero cómo usamos el tamaño de la pantalla para determinar si el diseño debe ser multipanel o de un panel, y cómo proporcionamos los diferentes flujos de pantalla según si el diseño es de un panel o de varios? El sistema de recursos de Android proporciona calificadores de configuración para que la aplicación maneje varios diseños de pantalla.

Se proporcionan diferentes archivos de diseño en el sistema de recursos según los tamaños de pantalla. Durante el inicio de la aplicación, Android escoge los archivos de diseño apropiados de acuerdo con el tamaño de la pantalla. En versiones anteriores a Android 3.2, es posible definir el diseño para pantallas pequeñas, normales, grandes o extragrandes. Los archivos de diseño se pueden definir en res/layout-small para dispositivos con tamaños de pantalla menores que 426 dp x 320 dp, o res/layout-xlarge para tamaños de pantalla mayores que 960 dp x 720 dp. Lo que sigue es la definición de estos tamaños de pantalla:

  • Las pantallas extragrandes (xlarge) son de al menos 960 dp x 720 dp.
  • Las pantallas grandes (large) son de al menos 640 dp x 480 dp.
  • Las pantallas normales (normal) son de al menos 470 dp x 320 dp.
  • Las pantallas pequeñas (small) son de 426 dp x 320 dp como máximo.

En esta figura se muestra cómo se mapea cada tamaño al tamaño del dispositivo real en pulgadas.



Figura 8: Mapeo del calificador de tamaño de pantalla al tamaño real en pulgadas.

A partir de Android 3.2, se reemplaza el calificador de tamaño de pantalla explicado más arriba por el calificador sw<N>dp donde N es la definición en píxeles del ancho de la pantalla. Por ejemplo, para pantallas "grandes", se pueden proporcionar los archivos de diseño en el directorio layout-sw600dp.

También se puede definir si la es orientación vertical ("portrait") u horizontal ("landscape") en el calificador del recurso. Por ejemplo, podría querer tener un diseño especial para panatallas de 600 dp y con orientación vertical solamente. En este caso, se creará un layout-sw600dp-port para almacenar el diseño.

La siguiente es la estructura de diseño basada en el tamaño y la orientación de la pantalla del dispositivo que se usó en la aplicación de ejemplo. Para tabletas medianas, quiero un diseño de un solo panel para la orientación vertical porque la UI podría quedar comprimida si usamos en diseño multipanel con este tamaño de pantalla (7 u 8 pulgadas).



Figura 9: Múltiples archivos de diseño en el sistema de recursos para diferentes tamaños de pantalla.

El diseño fragment_ultimate_view.xml para diferentes tamaños de pantalla es esencialmente igual. Lo único que cambia es la visibilidad del fragmento secundario. Para tabletas medianas, el diseño de vista Ultimate se verá así:

<!--Diseño de pantalla de UltimateViewFragment-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    
    <FrameLayout 
    	android:id="@+id/grid_fragment"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_weight="1"/>
		
	<FrameLayout 
		android:id="@+id/detail_fragment"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_weight="1"
		android:visibility="gone"
		android:orientation="vertical"/>
	
 </LinearLayout>

¿Cómo controlamos el flujo de navegación para diferentes diseños? Durante el tiempo de ejecución de la aplicación, y según la visibilidad de la vista, la aplicación puede decidir si actualizar otras vistas de la misma pantalla (multipanel) o iniciar una nueva pantalla (un solo panel). El siguiente fragmento de código muestra cómo hacer esto:

/*Control de evento de clic en elemento desde la cuadrícula de menú */
public void onGridItemClick(MenuItem itemSelected, int position) {
	mGridItemListener.onGridItemClick(itemSelected, position);
	mSelectedIndex = position;
	View detail = getActivity().findViewById(R.id.detail_fragment);
	//modo vertical
	if (detail != null && detail.getVisibility() == View.GONE) {
		Intent intent = new Intent(this.getActivity(), DetailActivity.class);
           		 intent.setAction("View");
            		intent.putExtra("category", itemSelected.category);
          		intent.putExtra("entree_name", itemSelected.name);
            		Activity activity = getActivity();
            		activity.startActivity(intent);		
            
       	 //modo horizontal
	} else {
		mDetailViewFragment.update(itemSelected);
	}
}

Control del ciclo de vida de los fragmentos

Tal como ocurre con Android Activity, los fragmentos implementan las llamadas de respuesta del ciclo de vida y realizan las acciones apropiadas durante los estados de inicio, pausa, reanudación, detención y destrucción de la aplicación. El ciclo de vida de los fragmentos se maneja de manera similar a como se maneja una actividad.

Consejos

Además de las llamadas de respuesta regulares de ciclo de vida de una actividad, como es el caso de onCreate, onStart, onResume, onPause, onStop y onDestroy, los fragmentos tienen algunas llamadas de respuesta de respuesta adicionales:

onAttach(): se la llama cuando se ha asociado el fragmento con la actividad.

onCreateView(): se la llama para crear la jerarquía de vistas asociadas con el fragmento.

onActivityCreated(): se la llama cuando se ha devuelto onCreate() de la actividad.

onDestroyView(): se la llama cuando se está quitando la jerarquía de vistas asociada con el fragmento.

onDetach(): se la llama cuando se está asociando el fragmento con la actividad.

A continuación se muestra el efecto del ciclo de vida de una actividad en llamadas de respuesta de ciclo de vida de un fragmento.


Figura 10: Mapeo del ciclo de vida de una aplicación a llamadas de respuesta de ciclo de vida de un fragmento.

De manera similar a Activity, es posible que los fragmentos necesiten guardar y restaurar los estados de la aplicación durante cambios de configuración del dispositivo, como podría ser un cambio de orientación, o la destrucción o pausa inesperada de una actividad. El guardado de estados de la aplicación se manejará en la llamada de respuesta onSaveInstanceState (), a la cual se llama antes de que se destruya la actividad, y la restauración de estados de la aplicación se manejará en onCreate (), onCreateView () o onActivityCreated (). El siguiente fragmento de código muestra cómo la aplicación para el restaurante guarda el índice del elemento de cuadrícula seleccionado en onSaveInstanceState () y lo restaura en onCreate ().

public void onCreate (Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
		
	if (savedInstanceState != null) {
            		String selectedIndex = savedInstanceState.getString("selectedIndex");
           		if(selectedIndex != null) {            
                		mSelectedPosition = Integer.parseInt(selectedIndex);
           		 }
       	 }      
		
	mImageAdapter = new ImageAdapter(getActivity());
}
	
/* Se guardó el último índice seleccionado antes del cambio de orientación */
public void  onSaveInstanceState (Bundle outState) {
        	//solo se guarda la posición seleccionada si el usuario ha hecho clic en el elemento
       	 if (mSelectedPosition != -1) {
            		outState.putString("selectedIndex", Integer.valueOf(mSelectedPosition).toString());
        	}
}

Consejos

A veces, puede que uno no quiera que se recreen los fragmentos durante el cambio de configuración (cambio de orientación del dispositivo), quizá debido a la complejidad de los datos de estado de la aplicación. Llamar a setRetainInstance (true) en la clase contenedora de fragmentos puede prevenir la recreación del fragmento durante el cambio de configuración.

Sistema de recursos de Android, gráficos y texto, resolución y densidad de la pantalla

¿Y qué ocurre con la presentación de texto y gráficos en pantalla? Un tamaño de letra que se eligió para un teléfono puede ser demasiado pequeño para una tableta con pantalla grande. Un ícono gráfico puede verse perfecto en una tableta, pero demasiado grande en un teléfono pequeño. Otra cuestión importante es prevenir el estiramiento de imágenes en dispositivos con diferentes resoluciones de pantalla.

En esta sección, conoceremos algunas técnicas para asegurarse de que el texto y los gráficos se vean bien en pantallas de diferentes resoluciones y densidades de píxeles.

Imágenes y densidad de píxeles

La densidad de píxeles de la pantalla es un factor que influye mucho en cómo se ven los gráficos. Las imágenes se ven más grandes en pantallas con menor densidad de píxeles porque en ellas el tamaño de cada píxel es mayor que en una de alta densidad. A su vez, las imágenes se ven más pequeñas en las pantallas con alta densidad de píxeles. Cuando se diseña la UI, se busca preservar todo lo posible el aspecto de los gráficos entre dispositivos con diferentes densidades de pantalla.

En la mayoría de los casos, para resolver esto se usa una unidad independiente de los píxeles, como "dp" y "wrap_content", para especificar la dimensión y la distribución de los íconos gráficos en el sistema de recursos. Con una unidad independiente de los píxeles, Android ajusta los gráficos a partir de la densidad de píxeles de la pantalla. En el siguiente ejemplo se ve el uso de "dp" y "wrap_content" para mostrar la imagen de artículos del restaurante en una vista detallada.

<ImageView
	android:id="@+id/dish_image"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_below="@+id/cart_area"
	android:minWidth="600dp"
	android:minHeight="400dp"
android:scaleType="centerCrop"/>

A veces esto no es suficiente. Por ejemplo, las imágenes pequeñas podrían verse pixeladas debido a cambios de escala. En este caso, para evitar el problema, se deben incluir otras opciones de imagen en el área res/drawable. Se pueden incluir imágenes de diferente tamaño en las carpetas res/drawable-<xxxxxx>, donde xxxxxx es la categoría de densidad generalizada. El siguiente cuadro es útil para interpretar la densidad generalizada en densidad de pantalla real.



Figura 11: Diferentes áreas de dibujo con recursos gráficos para dispositivos con distintos tamaños de pantalla.



Figura 12: Mapeo de densidad de pantalla generalizada a valor real en dpi

Tamaño del texto y de la pantalla

Para que el texto sea más fácil de leer, a veces es necesario ajustar el tamaño de la letra al de la pantalla. Por ejemplo, en la aplicación del restaurante, uso una letra de menor tamaño para el nombre y el precio de la comida en el menú de cuadrícula cuando el dispositivo tiene una pantalla de menos de 600 píxeles de ancho (como podría ser el caso de un teléfono). Creé un estilo de texto con diferentes tamaños de letra para pantallas grandes y pequeñas. Los estilos de texto relativos al tamaño de pantalla se guardan en res/values-sw<N>dp.



Figura 13: Diferentes archivos de estilo para pantallas de distinto tamaño

El siguiente archivo de estilo especifica el tamaño de letra del texto usado en el elemento de menú de cuadrícula.

<!--estilo de texto para pantalla con dp menor que 600 -->   
 <style name="GridItemText">        
        <item name="android:textColor">@color/grid_item_unselected_text</item>
        <item name="android:textStyle">italic</item>
        <item name="android:textSize">14sp</item>
    </style>
<!--estilo de texto para pantalla con dp mayor que 600 -->   
<style name="GridItemText">        
        <item name="android:textColor">@color/grid_item_unselected_text</item>
        <item name="android:textStyle">italic</item>
        <item name="android:textSize">20sp</item>
    </style> 

El archivo de diseño para la referencia del elemento de cuadrícula al estilo de texto definido arriba (view_grid_item.xml).

<TextView
            android:id="@+id/grid_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="5dp"
            android:paddingRight="5dp"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            android:layout_gravity="right"
 style="@style/GridItemText"/>

Conclusión

La UI es una de las áreas de programación más interesantes de Android. Hay muchas cosas para tener en cuenta cuando se diseñan y programan UI de Android. En este artículo se tratan cuatro conceptos esenciales que ayudan a crear UI dinámicas:

  1. El uso de los elementos recomendados para UI más recientes, como la barra de acciones, pestañas y vistas deslizables para navegar la pantalla.
  2. Métodos de programación para controlar los datos de aplicaciones dinámicas y cómo se emplean con la barra de acciones, las pestañas y las vistas deslizables.
  3. El uso de fragmentos de Android para implementar diseños multipanel y de un único panel para dispositivos con diferentes tamaños de pantalla.
  4. El uso del sistema de recursos de Android para mejorar el aspecto de los gráficos y el texto en pantallas con diferentes resoluciones y densidades de píxeles.

A media que continúa la evolución de Android, es recomendable incorporar las técnicas más recientes y mantenerse al día con los últimos conceptos para UI. Tener estas técnicas en mente será útil en el diseño de UI dinámicas para los muchos dispositivos Android que aparecerán en el futuro.

Enlaces de consulta

  1. Creación de la barra de acciones: https://developer.android.com/training/basics/actionbar/index.html
  2. Estilo de la barra de acciones: https://developer.android.com/training/basics/actionbar/styling.html
  3. Diseños multipanel: https://developer.android.com/design/patterns/multi-pane-layouts.html
  4. Guía de programación con fragmentos: https://developer.android.com/guide/components/fragments.html
  5. Diseño para pantallas diferentes: https://developer.android.com/guide/practices/screens_support.html
  6. Índice de consulta para el SDK de Android: https://developer.android.com/reference/packages.html

Acerca del autor

Mei-Lin Hsieh es ingeniera de software y tiene 15 años de experiencia en desarrollo para dispositivos móviles en Intel y otras empresas. Actualmente forma parte del Software Solutions Group y trabaja en proyectos de escala para aplicaciones Android con tabletas electrónicas y teléfonos.

Related Articles and References:


Los productos descritos en este documento podrían contener defectos de diseño o errores conocidos como erratas, por los que el producto puede apartarse de las especificaciones publicadas. Las erratas actuales están disponibles a solicitud.

Antes de hacer el pedido de un producto, comuníquese con la oficina de ventas o el distribuidor local de Intel para obtener las especificaciones más recientes.

Para obtener los documentos que tienen un número de orden y a los cuales se hace referencia en este artículo, o en otros textos de Intel, llame al 1-800-548-4725 o vaya a:

http://www.intel.com/design/literature.htm

El software y las cargas de trabajo que se usen en la pruebas de rendimiento puede que hayan sido optimizadas para rendimiento solamente en microprocesadores Intel. Las pruebas de rendimiento, tales como SYSmark* y

MobileMark*, se miden con sistemas informáticos, componentes, software, operaciones y funciones específicos. Todo cambio en cualquiera de esos factores puede hacer que varíen los resultados.

Debe consultar más información y otras pruebas de rendimiento que lo ayuden a evaluar íntegramente las compras que contemple hacer, incluido del rendimiento del producto al combinarlo con otros.

** Este código fuente de ejemplo se publica de acuerdo con lo estipulado en el "Contrato de licencia de código fuente de ejemplo de Intel".