manual inicio android (1).pdf
TRANSCRIPT
Manual Inicio Android Pasos básicos para crear una aplicación Android con ejemplos. El presente documento trata de introducir al alumno al desarrollo de aplicaciones Android, guiándolo en el desarrollo de una aplicación con varios ejemplos básicos que abarca en gran medida los conceptos básicos en el desarrollo de aplicaciones Android utilizando el ADT (Android Developer Tools) de Eclipse.
2014
NELSON CROZBY PADILLA ALVAREZ UTM
12/02/2014
1
Tabla de contenido ¿Qué es Android? .................................................................................................................................. 5 El sistema operativo Android ....................................................................................................................... 5 Task (tarea) .................................................................................................................................................. 5 Componentes de la plataforma Android ...................................................................................................... 5 Google Play .................................................................................................................................................. 7
Android Development Tools ............................................................................................................. 7 Android SDK ................................................................................................................................................. 7 Android debug bridge (adb) ......................................................................................................................... 7 Android Developer Tools y Android Studio .................................................................................................. 7 Dalvik Virtual Machine ................................................................................................................................. 8 Android Runtime .......................................................................................................................................... 8 Como desarrollar aplicaciones Android ....................................................................................................... 8 Proceso de conversión desde código fuente hasta una aplicación Android ................................................ 9
Seguridad y permisos .......................................................................................................................... 9 Concepto de seguridad en Android ............................................................................................................. 9 Concepto de permiso en Android ................................................................................................................ 9
Instalación ........................................................................................................................................... 10 Consideraciones previas y de instalación ................................................................................................... 10 Preparar IDE ............................................................................................................................................... 11
Emulador de dispositivo Android y Dispositivo Virtual Android (Android Virtual Devices, AVD) ...................................................................................................................................... 13 Emulador Android y Android Virtual Device .............................................................................................. 13 Atajos para el emulador de dispositivos Android ...................................................................................... 14 Google vs. Android AVD ............................................................................................................................. 15 Optimización de Velocidad ........................................................................................................................ 15 Emulador Intel ............................................................................................................................................ 16 Emulador Alternativo ................................................................................................................................. 17
Ejercicio: Crear e iniciar Android Virtual Device ................................................................... 17 Target (objetivo) ........................................................................................................................................ 17 Crear el AVD ............................................................................................................................................... 17 Iniciar tu AVD ............................................................................................................................................. 19
Ejercicio: Crear aplicación Android en Eclipse ....................................................................... 20 Asistente de proyectos Android ................................................................................................................. 20 Crear proyecto Android ............................................................................................................................. 20
Ejercicio: Inicie la aplicación Android generada .................................................................... 25 Iniciar AVD .................................................................................................................................................. 25 Inicicar la aplicación ................................................................................................................................... 26
2
Integración de ADT dentro de Eclipse ........................................................................................ 27 Integración de Android dentro de la perspectiva Java ............................................................................... 27 Asistentes de Android ................................................................................................................................ 27
Perspectiva DDMS ............................................................................................................................. 28 Perspectiva Android ................................................................................................................................... 28 File Explorer ............................................................................................................................................... 29
Partes de una aplicación Android ................................................................................................ 29 Aplicación Android ..................................................................................................................................... 29 Componentes de software Android ........................................................................................................... 29 Contexto ..................................................................................................................................................... 30
Vistazo a los componentes de una aplicación Android ........................................................ 30 Activity (Actividad) ..................................................................................................................................... 30 BroadcastReceiver ..................................................................................................................................... 30 Service (Servicio) ........................................................................................................................................ 30 ContentProvider ......................................................................................................................................... 31
Componentes base en las interfaces de usuario en Android .............................................. 31 Acitivity (Actividad) .................................................................................................................................... 31 Fragments (Fragmentos) ............................................................................................................................ 31 Views y control de distribución .................................................................................................................. 32 Diseños específicos de configuración de dispositivos ................................................................................ 32
Otros elementos importantes de Android ................................................................................ 32 Pantalla principal y la pantalla de bloqueo de widgets .............................................................................. 32 Live Wallpapers .......................................................................................................................................... 33
El Android Manifest .......................................................................................................................... 33 La configuración de la aplicación para Android ......................................................................................... 33 Declarar los componentes en el archivo de manifiesto ............................................................................. 33 Permisos ..................................................................................................................................................... 33 Ejemplo del archivo AndroidManifes.xml .................................................................................................. 33
El manifiesto de Android ................................................................................................................ 34 Versión y paquete ...................................................................................................................................... 34
Ciclo de vida de una Activity .......................................................................................................... 36 Entendiendo el flujo del ciclo de vida ........................................................................................................ 36
Creación y manejo de Base de Datos ........................................................................................... 37 Crear la BD con SQLite en Navicat. ............................................................................................................. 37 Cargar la base de datos en el proyecto de Android ................................................................................... 42 Cargar la base de datos en la aplicación para poder usarla ....................................................................... 43 Creación de la Base de Datos con código desde la aplicación ................................................................... 50 Manejo de SQLite en Android .................................................................................................................... 54
3
SQLiteOpenHelper ..................................................................................................................................... 54 SQLDatabase y Cursor ................................................................................................................................ 54
Creando un Navigation Drawer .................................................................................................... 58 Abrir y Cerrar menú desde un ícono. ......................................................................................................... 70 Asignar acción a la pulsación de un Item del menú de navegación ........................................................... 72
Interactuar con una Base de Datos .............................................................................................. 79 Agregar recursos a la aplicación ................................................................................................................. 79 Diseño de layouts ....................................................................................................................................... 81 Agregar clases para manejo de interacción con la base de datos .............................................................. 82
Trazar una ruta .................................................................................................................................. 92 Utils.java ................................................................................................................................................ 96 ConnectionDetecter.java ........................................................................................................................ 98 GMapsDirection.java .............................................................................................................................. 99 Creación de elementos visuales ........................................................................................................... 105
Adapters ............................................................................................................................................. 114 Entender el comportamiento interno de un Android Adapter ................................................................ 114
Mapas en Android (Google Maps Android API v2) ............................................................... 115 Pasos para instalar y/o configurar la API de Google Maps Android. ........................................................ 115 Pasos para construir una aplicación básica usando la API de Google Maps ............................................ 125 Interactuando con el Mapa (pasar coordenadas) .................................................................................... 127
Localización geográfica en Android .......................................................................................... 128 ¿Qué mecanismos de localización tenemos disponibles? ....................................................................... 128 ¿Qué proveedor de localización es mejor para mi aplicación? ................................................................ 129 ¿Está disponible y activado un proveedor determinado? ........................................................................ 130 El GPS ya está activado, ¿y ahora qué? .................................................................................................... 131
AsyncTask .......................................................................................................................................... 139 Tipos genéricos ........................................................................................................................................ 139 Etapas del ASyncTask ............................................................................................................................... 139 Las reglas del juego .................................................................................................................................. 140
Navegación ......................................................................................................................................... 140 Navegación con Back y Up ....................................................................................................................... 140 Up vs. Back ............................................................................................................................................... 141 Navegación dentro de su aplicación ........................................................................................................ 142 Navegando dentro de la aplicación vía Widgets en la pantalla Home y las Notificaciones. .................... 145 Notificaciones Indirectas .......................................................................................................................... 146 Notificaciones pop-‐up .............................................................................................................................. 147 Navegando entre aplicaciones ................................................................................................................. 148
Estilos en Android ........................................................................................................................... 152
4
Dispositivos y Pantallas ............................................................................................................................ 152 Temas ....................................................................................................................................................... 153 Retroalimentación al Toque ..................................................................................................................... 155
Estados ................................................................................................................................................. 156 Comunicación ........................................................................................................................................... 156
Límites .................................................................................................................................................. 157 Métricas y Rejillas .................................................................................................................................... 157 48dp Rhythm ............................................................................................................................................ 158
¿Por qué 48dp? .................................................................................................................................... 158 Cuidado con el hueco ........................................................................................................................... 159
Tipografía ................................................................................................................................................. 160 Colores de tipo predeterminado ........................................................................................................... 161 Escala Tipográfica ................................................................................................................................ 161
Color ......................................................................................................................................................... 161 Paleta ................................................................................................................................................... 162
Iconografía ............................................................................................................................................... 162 Lanzador ............................................................................................................................................... 163 Action Bar ............................................................................................................................................. 165 Iconos pequeños/contextuales ............................................................................................................. 166 Iconos de notificación ........................................................................................................................... 167 Consejos de Diseño ............................................................................................................................... 168
Su imagen de marca ................................................................................................................................. 171 Color ..................................................................................................................................................... 171 Logo ...................................................................................................................................................... 172 Iconos ................................................................................................................................................... 173
Estilos de Escritura. .................................................................................................................................. 174 La voz de Android. ................................................................................................................................ 174
5
¿Qué es Android? El sistema operativo Android
Android es un sistema operativo basado en el kernel de Linux. El proyecto responsable del desarrollo del sistema Android es llamado Android Open Source Project (AOSP) y es principalmente liderado por Google.
El sistema operativo Android soporta procesamiento en background; proporciona una librería de interfaces de usuario enriquecida, soporta gráficos 2D y 3D usando el estándar OpenGL-‐ES y garantiza acceso a los archivos del sistema así como la base de datos embebida SQLite
Una aplicación Android típicamente consiste de diferentes componentes visuales y no visuales y se pueden volver a utilizar componentes de otras aplicaciones.
Task (tarea)
La reutilización de los componentes de otras aplicaciones nos lleva al concepto de task (tarea) en Android. Una aplicación puede acceder a otros componentes de Android para lograr una task. Por ejemplo, a partir de un componente de nuestra aplicación, puede desencadenar otro componente en el sistema Android, el cual gestiona sus fotos, aunque este componente no es parte de su aplicación. En este componente se selecciona una foto y volver a la aplicación para utilizar la foto seleccionada.
Tal flujo de eventos se representa en el siguiente gráfico.
Componentes de la plataforma Android
1.-‐ Usuario dispara “Recoger foto” vía botón
2.-‐ inicia galería
3.-‐ Usuario selecciona una foto
4.-‐ Regresa la foto seleccionada
6
El sistema Android es una completa pila de software, que suele estar dividido en las cuatro áreas como se muestra en el siguiente gráfico.
Los niveles pueden ser descritos como:
• Applications (Aplicaciones) -‐ El Proyecto Open Source Android contiene varias aplicaciones por defecto, como el Navegador, Cámara, Galería, Música, teléfono y mucho más.
• Application framework (Marco de aplicación) -‐ API que permite la interacción de alto nivel con el sistema Android desde las aplicaciones de Android.
• Libraries and runtime (Librerías y tiempo de ejecución) -‐ Bibliotecas para Application framework con muchas funciones comunes (renderizado gráfico, almacenamiento de datos, navegación web, etc), así como el runtime Dalvik y las bibliotecas fundamentales de Java para la ejecución de aplicaciones Android.
• Kernel Linux -‐ capa de la Comunicación para el hardware subyacente.
El kernel de Linux, las bibliotecas y el runtime se encapsulan en el Application framework. El desarrollador de aplicaciones Android normalmente trabaja con las dos capas en la parte superior para crear nuevas aplicaciones de Android.
7
Google Play
Google ofrece el servicio de Google Play, un mercado en el que los programadores pueden ofrecer sus aplicaciones Android para los usuarios de Android. Los clientes utilizan la aplicación Google Play lo que les permite comprar e instalar las aplicaciones del servicio Google Play.
Google Play también ofrece un servicio de actualización. Si un programador carga una nueva versión de su aplicación a Google Play, este servicio avisa a los usuarios existentes que hay una actualización disponible y permite que se instalen la actualización.
Google Play permite el acceso a los servicios y bibliotecas para los programadores de aplicaciones para Android, también. Por ejemplo, se ofrece un servicio para usar y mostrar Google Maps y otro para sincronizar el estado de la aplicación entre las diferentes instalaciones Android. La provisión de estos servicios a través de Google Play tiene la ventaja de que están disponibles para las versiones de Android más viejas y pueden ser actualizados por Google sin la necesidad de una actualización de la versión de Android en el teléfono.
Android Development Tools Android SDK
El Kit de desarrollo de software Android (Android SDK) contiene las herramientas necesarias para crear, compilar y empaquetar aplicaciones de Android. La mayoría de estas herramientas están basadas en línea de comandos. La principal manera de desarrollar aplicaciones de Android se basa en el lenguaje de programación Java.
Android debug bridge (adb)
El SDK de Android contiene el Android debug bridge (adb), que es una herramienta que le permite conectarse a un dispositivo Android virtual o real, con la finalidad de gestionar el dispositivo o la depuración de la aplicación.
Android Developer Tools y Android Studio
Google proporciona dos entornos de desarrollo integrado (IDE) para desarrollar nuevas aplicaciones.
Las herramientas de desarrollo de Android (ADT) se basan en el IDE de Eclipse. ADT es un conjunto de componentes (plug-‐ins), que amplían el IDE Eclipse con capacidades de desarrollo de Android.
Google también es compatible con un IDE llamado Android Estudio para la creación de aplicaciones de Android. Este IDE se basa en la IDE IntelliJ.
Ambas herramientas proporcionan editores especializados para archivos específicos Android. La mayoría de los archivos de configuración de Android están basados en XML. En este caso, estos editores
8
permiten alternar entre la representación XML del archivo y una interfaz de usuario estructurado para introducir los datos.
Ambos IDEs contienen toda la funcionalidad necesaria para crear, compilar, depurar y desplegar aplicaciones de Android. También permiten a los desarrolladores crear e iniciar los dispositivos Android virtuales para pruebas.
Dalvik Virtual Machine
El sistema Android utiliza una máquina virtual especial, es decir, El sistema Android utiliza una máquina virtual especial, es decir, la máquina virtual de Dalvik (Dalvik) para ejecutar aplicaciones basadas en Java. Dalvik utiliza un formato de bytecode a medida que es diferente del bytecode de Java.
Por lo tanto no se puede ejecutar archivos de clase Java en Android directamente, sino que necesitan ser convertidos en el formato de bytecode de Dalvik.
Dalvik funciona de forma similar a la máquina virtual de Java en cuanto a la optimización de aplicaciones. Optimiza la aplicación en tiempo de ejecución. Esto se conoce como Just In Time (JIT). Si una parte de la aplicación se llama con frecuencia, Dalvik optimizará esta parte del código y lo compila a código máquina que ejecuta mucho más rápido.
Android Runtime
Aplicaciones de Android están escritas principalmente en el lenguaje de programación Java.
Con Android 4.4, Google presentó el Android Runtime (ART) como un runtime opcional para Android 4.4. Se espera que las versiones posteriores 4.4 usarán ART como runtime predeterminado.
ART utiliza Ahead Of Time compilation. Durante el despliegue de una aplicación en un dispositivo Android, el código de la aplicación es traducida a código máquina. Esto da lugar a aprox. 30% de código de compilación más grande, pero permite una ejecución más rápida desde el principio de la aplicación.
Recordemos que, para efectos de este curso estaremos usando Android 4.0 (API 14), esto es sólo para información.
Como desarrollar aplicaciones Android
Durante el desarrollo el desarrollador crea los archivos de configuración específicos para Android y escribe la lógica de la aplicación en el lenguaje de programación Java.
Las herramientas ADT o el Studio Android convierten esos archivos de la aplicación de forma transparente para el usuario en una aplicación Android. Cuando los desarrolladores inician el despliegue en su IDE, toda la aplicación Android es compilada, empaquetada, desplegada e iniciada.
9
Proceso de conversión desde código fuente hasta una aplicación Android
Los archivos de código fuente de Java son convertidos en archivos de clases de Java por el compilador Java.
El SDK de Android contiene una herramienta llamada dx que convierte archivos de clase Java en un archivo .dex (Dalvik ejecutable). Todos los archivos de clase de la aplicación se colocan en el archivo .dex. Durante este proceso de conversión la información redundante en los archivos de clase serán optimizados en el archivo .dex.
Por ejemplo, si la misma String se encuentra en archivos de clase diferentes, el archivo .dex contiene sólo una referencia a esta String.
Estos archivos .dex son por lo tanto mucho más pequeño en tamaño que los archivos de clases correspondientes.
El archivo de .dex y los recursos de un proyecto Android, esto es, las imágenes y los archivos XML, se empaquetan en un archivo .apk (Android Package). El programa aapt (Android Asset Packaging Tool) realiza este paso.
El archivo .apk resultante contiene todos los datos necesarios para ejecutar la aplicación Android y se puede implementar en un dispositivo Android a través de la herramienta adb.
Seguridad y permisos Concepto de seguridad en Android
El sistema Android instala cada aplicación Android con un único usuario y ID de grupo. Cada archivo de aplicación es privado para este usuario generado, esto es, otras aplicaciones no pueden acceder a estos archivos. Además, cada aplicación Android se inicia en su propio proceso.
Por lo tanto por medio del kernel de Linux subyacente, cada aplicación Android se aísla de otras aplicaciones en ejecución.
Si se deben compartir los datos, la aplicación debe hacer esto de manera explícita a través de un componente de Android que se encarga de la distribución de los datos, por ejemplo, a través de un servicio o de un proveedor de contenido.
Concepto de permiso en Android
Android contiene un sistema de permisos y predefine permisos para ciertas tareas. Cada aplicación puede solicitar permisos necesarios, así como definir nuevos permisos. Por ejemplo, una aplicación puede declarar que requiere acceso a Internet.
10
Los permisos tienen diferentes niveles. Algunos permisos se conceden automáticamente por el sistema Android, algunos serán rechazados automáticamente. En la mayoría de los casos los permisos solicitados se presentan al usuario antes de instalar la aplicación. El usuario tiene que decidir si estos permisos serán otorgados a la aplicación.
Si el usuario deniega una autorización necesaria, la aplicación relacionada no se puede instalar. La comprobación de la autorización sólo se realiza durante la instalación, los permisos no pueden ser negados u otorgados después de la instalación.
Una aplicación Android declara los permisos necesarios en su archivo de configuración AndroidManifest.xml. También puede definir los permisos adicionales que puede utilizar para restringir el acceso a determinados componentes.
Nota
No todos los usuarios presten atención a los permisos requeridos durante la instalación. Sin embargo, algunos usuarios hacen y escriben comentarios negativos en Google Play si creen que la aplicación es demasiado invasiva.
Instalación Consideraciones previas y de instalación
1. Para los fines de este curso se considera que se debe de tener instalado java con la versión jdk1.7.0_51, en caso de no tenerlo instalado ir al siguiente enlace http://www.oracle.com/technetwork/java/javase/downloads/jdk7-‐downloads-‐1880260.html y escoger la opción que más se apegue a su sistema.
2. Verificar tener instalado el ADT (Android Developer Tool), que es la herramienta (IDE) de desarrollo basada en Eclipse que nos servirá para el desarrollo de nuestra aplicación, en este punto también es bueno verificar que se tenga instalado el SDK de Android. En caso de no tenerla instalado nada de esto, esta es la url que nos lleva a la página de descarga oficial del ADT http://developer.android.com/sdk/index.html. a. En esta página hacer click en el botón que se muestra en la Imagen 1.
Imagen 1
b. Aceptar los términos y/o condiciones además de escoger la versión de 32-‐bit o de 64-‐bit, como se muestra en la Imagen 2 esto se define basándose en la versión de java que
11
tengan instalado en su sistema. Es importante que escoger bien porque si no coinciden las versiones del jdk con el ADT no se podrá ni instalar.
Imagen 2
c. Ahora hacer click en el botón mostrado en la Imagen 3, y esperar a descargar para instalarlo
Imagen 3
Preparar IDE
1. Para consideraciones de este manual se trabajará con la versión de Android 4.0 “Ice Cream Sandwich” (API 14).
2. Lo primero será ejecutar la aplicación ADT (Imagen 4).
Imagen 4
12
3. Ya en marcha podremos comprobar si tenemos instalado el SDK adecuado haciendo click en la herramienta Android SDK Manager que se puede ejecutar haciendo click en el ícono de la Imagen 5.
Imagen 5
4. La ventana mostrada en la Imagen 6 nos muestra todos los SDK´s existentes y algunas herramientas disponibles, pero lo que nos interesa es lo que está encerrado en el cuadro rojo, hay dos opciones una que ya esté instalado el SDK que ocupamos (y diga “Installed”), y la otra es que aún no lo esté que es el caso que muy probablemente tengamos por ser de reciente instalación, para tal caso debemos de marcar las casillas de los checkboxes en las opciones SDK Platform, ARM EABI v7a System Image y Google APIs, seleccionadas estas opciones se procede a seleccionar el botón Install que ahora debe de decir Install 2 packages…,
Imagen 6
13
a. Aparecerá en este momento una ventana que nos pide que aceptemos las condiciones de uso de los paquetes a instalar y seleccionamos Accept All, y luego click al botón Install, como se muestra en la Imagen 7
Imagen 7
b. Esperamos un tiempo mientras descarga el sdk de internet y se instala.
Terminada la descarga e instalación cerramos el Android SDK Manager.
Emulador de dispositivo Android y Dispositivo Virtual Android (Android Virtual Devices, AVD) Emulador Android y Android Virtual Device
El SDK de Android contiene un emulador de dispositivos Android. Este emulador se puede utilizar para ejecutar un dispositivo virtual de Android (AVD), que emula un teléfono Android real. Este emulador se muestra en la siguiente captura de pantalla.
14
AVDs le permiten probar sus aplicaciones de Android en diferentes versiones y configuraciones de Android sin acceso al hardware real.
Durante la creación de su AVD se define la configuración para el dispositivo virtual. Esto incluye, por ejemplo, la resolución, la versión de la API de Android y la densidad de la pantalla.
Se pueden definir varios AVDs con diferentes configuraciones y comenzar en paralelo. Esto le permite probar diferentes configuraciones de los dispositivos a la vez.
Atajos para el emulador de dispositivos Android
La siguiente tabla muestra los accesos directos útiles para trabajar con una AVD.
15
Tabla 1. Atajos para el emulador de dispositivos de Android
ATAJO DESCRIPCIÓN
ALT+ENTER MAXIMIZA EL EMULADOR.
CTRL+F11 CAMBIA LA ORIENTACIÓN DEL EMULADOR DE HORIZONTAL A VERTICAL Y VICEVERSA.
F8 ENCIENDE Y APAGA LA RED
Google vs. Android AVD
Durante la creación de una AVD usted decide si desea crear un dispositivo Android o un dispositivo de Google.
Un AVD creado para Android contiene los programas del proyecto de Android Open Source. Un AVD creada para la API de Google contiene código específico adicional de Google.
AVDs creadas con la API de Google le permiten probar aplicaciones que utilizan Google Play Services, por ejemplo, la nueva API de Google Maps o los nuevos servicios de localización.
Optimización de Velocidad
Durante la creación de un emulador se puede elegir si se desea, ya sea Snapshot o habilitar Use Host GPU. Si selecciona la opción de Snapshot, la segunda vez que se inicia el dispositivo se inicia muy rápido, debido a que la AVD almacena su estado en caso que lo cierre. Si selecciona Usar Host GPU la AVD utiliza la tarjeta gráfica de su ordenador directamente, hace el renderizado en el dispositivo emulado mucho más rápido.
16
Emulador Intel
El emulador de Intel que puede ser instalado a través del Administrador SDK de Android es mucho más rápido en la ejecución en el hardware de Intel / AMD. Si lo instala a través del SDK, es necesario instalar también el controlador que se encuentra en el directorio de instalación del dispositivo.
17
Emulador Alternativo
Hay alternativas disponibles al emulador por defecto de Android. Por ejemplo, el emulador Genymotion es relativamente rápido en la puesta en marcha y ejecución de proyectos de Android
Ejercicio: Crear e iniciar Android Virtual Device Target (objetivo)
En este ejercicio se creará e iniciará un AVD. Aunque si se tiene un dispositivo Android real disponible, te debería de ser familiar la creación y uso de los AVDs. Los dispositivos virtuales dan la posibilidad para probar su aplicación en otras versiones de Android y especificar configuraciones.
Crear el AVD
Definir un nuevo Android Virtual Device (AVD) abriendo el AVD Manager via Window>Android Virtual Device Manager y presionar el botón New
Ingresar valores similares a los de la siguiente toma de pantalla.
18
Nota
Asegurarse que la opción Use Host GPU este seleccionada. Esto permite que el AVD use la unidad de procesamiento gráfico (GPU) de su computadora y hace el renderizado mucho más rápido.
Después presionar el botón OK. Esto creará la configuración del AVD y lo desplegará bajo la lista de dispositivos virtuales disponibles.
19
Iniciar tu AVD
Seleccionar la nueva entrada y presionar el botón Start. Seleccionar Launch en el siguiente dialogo.
Advertencia
No interrumpa el proceso de inicio, esto podría corrompir el AVD. El primer inicio puede tomar arriba de 10 minutos en máquinas viejas. En una maquina moderna típicamente toma 1 – 3 minutos para un nuevo AVD al iniciar.
Después de iniciado el AVD, puedes controlar la GUI con el mouse. El emulador puede proveer acceso a los botones del teléfono vía un menú del lado derecho del emulador.
20
Tip
Una vez inicializado, no detener el AVD durante el desarrollo. Si se cambia algo en la aplicación y quiere probar una nueva versión, simplemente hay que correr o ejecutar de nuevo la aplicación en el AVD.
Ejercicio: Crear aplicación Android en Eclipse Asistente de proyectos Android
Las herramientas de Android en Eclipse proporciona asistentes para aplicaciones de Android. En este ejercicio se utiliza el asistente de creación de proyecto para crear una aplicación de Android basada en una plantilla.
Nota
El propósito de este ejercicio es demostrar el proceso de desarrollo. Los elemnots creados se explicarán más adelante.
Crear proyecto Android
Para crear un nuevo proyecto Android seleccione File → New → Other... → Android → Android Project en el menú. Introduzca los datos de ajuste de la siguiente tabla en la primera página del asistente.
21
Tabla 2. Ajuste para su proyecto Android
Property Value
Application Name Test App
Project Name com.utm.primero
Package Name com.utm.primero API (Minimum, Target, Compile with) 14
Pulse el botón Siguiente y asegúrese de habilitar la opción Create a launcher icon (Crear un icono de lanzador ) y Create activity (Crear actividad).
22
Pulse el botón Next y seleccione la plantilla BlankActivity. Pulse el botón Next para proceder.
23
Introduce los siguientes datos en el cuadro de diálogo para la plantilla BlankActivity. La selección se representa en la pantalla después de la tabla.
Tabla 3. Valores para la plantilla
Parameter Value
Activity MainActivity
Layout activity_main
Navigation Type none
24
Pulse el botón Finish. El asistente le puede pedir que instale la biblioteca de compatibilidad. Si es así, seleccione para instalarla.
25
Ejercicio: Inicie la aplicación Android generada Iniciar AVD
Si aún no lo ha hecho, cree e inicie un dispositivo virtual de Android (AVD). La versión de Android que usted seleccione debe adaptarse a la versión mínima API de la aplicación Android.
Después del arranque aparece la pantalla de bienvenida de su AVD como se muestra en la siguiente captura de pantalla.
Una vez que esté listo, desbloqueé su emulador.
26
Inicicar la aplicación
Seleccione el proyecto Android, haga clic derecho sobre él y seleccione Run-‐As → Android Aplication.
27
Se le puede pedir si el Android Developer Tools deben supervisar los mensajes. Seleccione Yes en este caso y pulse el botón OK.
Así se inicia la aplicación en la AVD. La aplicación iniciada es una aplicación muy simple que sólo muestra la cadena ¡Hola, mundo! .
Integración de ADT dentro de Eclipse Integración de Android dentro de la perspectiva Java
La herramienta Android se integra con la perspectiva Java. Incluye botones en la barra de herramientas para administrar la configuración de Android con el gestor Android SDK, crea nuevos AVDs y archivos de configuración, así como contiene asistentes para crear nuevos proyectos Android.
La siguiente captura de pantalla resalta la barra de herramientas de Android
1. Android SDK Manager 2. Manejo e inicio de AVDs
Asistentes de Android
1 2
28
Se encontrarán los asistentes específicos de Android bajo File → New → Other... → Android como se muestra en la siguiente captura de pantalla. Estos asistentes le permiten crear proyectos para Android.
Perspectiva DDMS Perspectiva Android
ADT agrega la perspectiva DDMS (Dalvik Device Monitoring Service) para interactuar con tu dispositivo Android (virtual) y tu aplicación Android. Selecciona Window → Open Perspective → Other... → DDMS para abrir esta perspectiva. Aquí se agrupan varias vistas que pueden ser usadas de forma independiente.
En la parte izquierda se muestran los dispositivos Android conectados y los procesos en ejecución en el dispositivo. El lado derecho es un conjunto de vistas con diferentes propósitos. Puede seleccionar procesos y desencadenar acciones de la barra de herramientas, por ejemplo, iniciar una traza o detener el proceso.
29
File Explorer
El explorar de archivos permite navegar el sistema de archivos del dispositivo de Android ya sea virtual o real conectado.
Partes de una aplicación Android Aplicación Android
Una aplicación para Android es una sola unidad instalable que se puede iniciar y utilizar de forma independiente de otras aplicaciones de Android.
Una aplicación Android puede tener una clase de la aplicación la cual puede ser instanciada tan pronto como se inicia la aplicación y es el último componente que se detiene si la aplicación se detiene.
Una aplicación Android consta de componentes de software de Android y de archivos de recursos.
Los componentes de una aplicación Android pueden conectar a otras aplicaciones Android. De esta manera se puede crear tareas entre aplicaciones.
Componentes de software Android
Los siguientes componentes de software pueden ser definidos en las aplicaciones Android
• Activities (Actividades) • Services (Servicios) • Broadcast Receivers (receivers)
30
• Content providers (providers)
Contexto
Instancia de la clase android.content.Context que proporciona la conexión con el sistema Android el cual ejecuta la aplicación. Por ejemplo, se puede revisar el tamaño de la pantalla del dispositivo actual via el Context.
También da acceso a los recursos del proyecto. Es la interfaz a la información global sobre el entorno de la aplicación.
La clase Context también proporciona acceso a los servicios, esto es, el manejador de alarmas para disparar eventos basados en el tiempo.
Las actividades y servicios extienden de la clase Context. Por lo tanto, se pueden utilizar directamente para acceder al Context.
Vistazo a los componentes de una aplicación Android Activity (Actividad)
Una activity es la representación visual de una aplicación Android. Una aplicación Android ouede tener varias actividades.
Las actividades usan views (vistas) y fragments (fragmentos) para crear la interfaz de usuario e interactuar con el usuario.
BroadcastReceiver
Un broadcast receiver (receiver) puede ser registrado para escuchar los mensajes del sistema y los intents. Un receiver será notificado por el sistema Android si el evento especificado ocurre
Por ejemplo, se puede registrar un receiver para el vento en el que el sistema android finaliza el proceso de arranque. O se puede registrar para el evento en el que el estado del telefono cambia, esto es, que alguien llama.
Service (Servicio)
Un servicio realiza tareas sin proporcionar una interfaz de usuario. Se pueden comunicar con otros componentes Android, por ejemplo, a través de los receptores de radiodifusión y notificará al usuario a través del marco de la notificación de Android.
31
ContentProvider
Un content provider (provider) define una interfaz estructurada de datos de la aplicación. Un proveedor puede ser utilizado para acceder a datos dentro de una aplicación, pero también se puede utilizar para compartir datos con otras aplicaciones.
Componentes base en las interfaces de usuario en Android La siguiente descripción da una visión general de la interfaz de usuario relacionada con los componentes más importantes y partes de una aplicación Android.
Acitivity (Actividad)
Las activities son la base para las aplicaciones de usuario en Android. Estas ya fueron vistas en la sección anterior.
Fragments (Fragmentos)
Los Fragments son componentes los cuales corren en el contexto de una activity. Un fragment encapsula código de aplicaciones así qué es más fácil reutilizar y soportar dispositivos de diferentes tamaños.
La siguiente imagen muestra una activity llamada MainActivity. En una pantalla más pequeña que muestra sólo un fragmento y permite que el usuario se desplaza a otro fragmento. En una pantalla panorámica que muestra dos fragmentos.
32
Los fragmentos son componentes opcionales que le permiten reutilizar los componentes de interfaz de usuario para diferentes configuraciones de dispositivos.
Views y control de distribución
Las views son widgets de interfaz de usuario, por ejemplo, botones o campos de texto. Las views tienen atributos que pueden utilizarse para configurar su apariencia y comportamiento.
A ViewGroup es responsable de la organización de otras vistas. También se conoce como controlador de distribución (layout manager). La clase base para estos controladores de distribución es la clase android.view.ViewGroup que extiende (hereda) de la clase android.view.View que es la clase base para las vistas.
Los layout manager pueden etar anidados para crear layout más complejos.
Diseños específicos de configuración de dispositivos
La interfaz de usuario para las activities suelen definirse a través de archivos XML (archivos de diseño). Es posible definir archivos de diseño para la configuración de dispositivos diferentes, por ejemplo, basados en la anchura disponible del dispositivo real que ejecuta la aplicación.
Otros elementos importantes de Android Pantalla principal y la pantalla de bloqueo de widgets
Los widgets son componentes interactivos que se utilizan principalmente en la pantalla de inicio de Android. Ellos exhiben típicamente algún tipo de datos y permiten al usuario realizar acciones con ellos.
33
Por ejemplo, un control puede mostrar un breve resumen de los nuevos mensajes de correo electrónico y, si el usuario selecciona un correo electrónico, puede iniciar la aplicación de correo electrónico con el correo electrónico seleccionado.
Para evitar confusiones con los views (que también se llaman widgets), este texto utiliza el termino de los widgets en la pantalla de inicio, si se habla de widgets.
Live Wallpapers
Los Live Wallpapers permiten crear fondos animados para la pantalla de inicio de Android.
El Android Manifest La configuración de la aplicación para Android
Los componentes y configuración de una aplicación para Android, se describen en el archivo AndroidManifest.xml. Este archivo se conoce como el archivo de manifiesto o el manifiesto.
El manifiesto también especifica metadatos adicionales para la aplicación, por ejemplo, iconos y el número de versión de la aplicación.
Este archivo es leído por el sistema Android durante la instalación de la aplicación. El sistema Android evalúa este archivo de configuración y determina las capacidades de la aplicación.
Declarar los componentes en el archivo de manifiesto
Todas las activities, servicios y contenido de los content provider de la aplicación se deben declarar de forma estática en este archivo. Los broadcast receivers pueden ser definidos estáticamente en el archivo de manifiesto o dinámicamente al tiempo de ejecución de la aplicación.
Permisos
El archivo de manifiesto de Android también debe contener los permisos necesarios para la aplicación. Por ejemplo, si la aplicación requiere acceso a la red, se debe especificar aquí.
Ejemplo del archivo AndroidManifes.xml
El siguiente listado muestra un ejemplo para un simple archivo AndroidManifest.xml.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.rssfeed" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="19" />
34
<uses-permission android:name="android.permission.INTERNET" /> <application android:name="RssApplication" android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="RssfeedActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".DetailActivity" android:label="Details" > </activity> <activity android:name="MyPreferenceActivity" > </activity> <service android:name="RssDownloadService" > </service> </application> </manifest>
El manifiesto de Android Versión y paquete
El atributo de package define el paquete base para los objetos Java que se hace referencia en este archivo. Si un objeto de Java se encuentra dentro de un paquete diferente, se debe declarar con el nombre completo del paquete calificado.
Google Play requiere que cada aplicación Android utilice su propio nombre único de paquete. Por lo tanto es un buen hábito el usar su nombre de dominio inverso aquí. Esto evitará colisiones con otras aplicaciones de Android.
android:versionName y android:versionCode especifican la version de su aplicación. versionName es lo que ve el usuario y puede ser cualquier cadena.
versionCode debe ser un entero. El Android Market determina si se debe llevar a cabo una actualización de las aplicaciones para la instalación existente basado en el versionCode. Por lo general, comienza con "1" y aumenta este valor por uno si sale una nueva versión de su aplicación.
35
36
Ciclo de vida de una Activity A medida que el usuario navega a través de, fuera de, y de regreso a su aplicación, las instancias de las Activities cambian entre los diferentes estados de su ciclo de vida en las transiciones de la aplicación. Por ejemplo, cuando su actividad se inicia por primera vez, se trata de un primer plano del sistema y recibe la atención del usuario. Durante este proceso, el sistema Android llama a una serie de métodos de ciclo de vida sobre la actividad en la que se configura la interfaz de usuario y otros componentes. Si el usuario realiza una acción que inicia otra actividad o cambia a otra aplicación, el sistema llama a otro conjunto de métodos de ciclo de vida en su activity a medida que avanza hacia el fondo (donde la actividad ya no es visible, pero la instancia y su estado permanecen intactos).
Dentro de los métodos de llamadas del ciclo de vida, puedes declarar cómo se comportará tu activity cuando el usuario abandone o reingrese a la activity. Por ejemplo, si se está construyendo un reproductor de vídeo con streaming, se podría hacer una pausa al vídeo y terminar la conexión de red cuando el usuario cambia a otra aplicación. Cuando el usuario vuelve, puede volver a conectarse a la red y permitir al usuario reanudar el vídeo desde el mismo punto.
Entendiendo el flujo del ciclo de vida
Durante la vida de una actividad, el sistema llama a un conjunto básico de los métodos de ciclo de vida en una secuencia similar a una pirámide escalonada. Es decir, cada etapa del ciclo de actividad es un paso separado en la pirámide. A medida que el sistema crea una nueva instancia de una activity, cada método de devolución de llamada mueve el estado de activity un paso hacia la cima. La parte superior de la pirámide es el punto en el que la actividad se ejecuta en el primer plano y el usuario puede interactuar con él (Imagen 8).
Imagen 8
37
Dependiendo de la complejidad de la actividad, probablemente no es necesario implementar todos los métodos del ciclo de vida. Sin embargo, es importante que se entienda cada uno y ponerlos en práctica garantiza que la aplicación se comporte de la forma en que los usuarios esperan. La implementación de los métodos de ciclo de vida de la activity apropiadamente asegura que la aplicación se comporte bien en varias situaciones, incluyendo que:
• No bloquee si el usuario recibe una llamada telefónica o cambia a otra aplicación mientras se utiliza la aplicación.
• No consume valiosos recursos del sistema cuando el usuario no está usando activamente. • No pierde el progreso del usuario si salen de su aplicación y volver a ella más adelante. • No bloquee o perder el progreso del usuario cuando la pantalla cambia entre orientación
vertical u horizontal.
Hay varias situaciones en las que las transiciones de una activity entre los diferentes estados ocurren como se ilustran en la Imagen 8. Sin embargo, sólo tres de estos estados puede ser estático. Es decir, la activity puede existir en uno de los tres estados para un período de tiempo prolongado:
• Resumed: En este estado, la activity está en el primer plano y el usuario puede interactuar con él. (También se refiere a veces como el estado "corriendo").
• Paused: En este estado, la activity está parcialmente oculta por otra activity, la otra activity que está en el primer plano es semi-‐transparente o no cubre toda la pantalla. La activity que se detuvo no recibe la entrada del usuario y no se puede ejecutar ningún código.
• Stopped: En este estado, la activity está completamente oculta y no visible para el usuario, sino que se considera que está en el fondo. Mientras está detenida, la instancia de la activity y toda la información de su estado como variables de miembros se mantiene, pero no se puede ejecutar ningún código.
Los otros estados (creado e iniciado) son transitorios y el sistema rápidamente cambia de ellos al siguiente estado llamando al siguiente ciclo de vida. Es decir, después de que el sistema llama onCreate (), rápidamente se llama OnStart (), que rápidamente es seguido por onResume ().
Creación y manejo de Base de Datos Existen dos formas para la creación o utilización de bases de datos en Android:
1. Diseñar y crear la base de datos de manera externa usando alguna aplicación (en este caso Navicat).
2. Utilizacón de la clase SQLiteOpenHelper para crear la estructura de la base de datos desde el código.
Crear la BD con SQLite en Navicat.
38
1. Lo primero que hay que hacer es abrir el Navicat y crear una nueva conexión a una base de datos SQLite, como vemos en la Imagen 9
Imagen 9
2. Escoger un nombre a la conexión en nuestro caso se llamará bd_alumnos, en la sección Type seleccionar la opción New SQLite 3, y en la sección de Database File debemos de especificar un lugar donde guardaremos nuestra base de datos recientemente creada así mismo como el nombre que le queremos dar al archivo en la Imagen 10 se muestra como queda configurada mi base de datos.
Imagen 10
39
3. Para crear la tabla que requerimos hacemos doble-‐click en la nueva conexión recién creada, después aparece una opción que dice “main”, también a esta le damos doble-‐click y se despliegan varias opciones en la que dice Tables hacemos click con el botón derecho y del menú desplegable seleccionamos la opción New Table (Ver Imagen 11).
Imagen 11
4. Crearemos los campos que llevará la BD, debe de quedar igual a como se muestra en la Imagen 12.
Imagen 12
40
5. Ahora crearemos una índice, esto es necesario que sea realizado para todas las llaves primarias que sean creadas con auto incremento, para realizar esto seleccionamos la pestaña Indexes, en la columna Name pondremos un nombre al índice en este caso será id_alumno, en la columna Fields al seleccionarla cambia a como se muestra en la Imagen 13 y damos un click al botón encerrado en el cuadro rojo.
Imagen 13
6. En la ventana que es mostrada ahora, ver Imagen 14, marcamos el checkbox de la llave primaria y en la columna Sort Order, seleccionamos la opción ASC y aceptamos los cambios dando click en el botón encerrado en el cuadro rojo.
Imagen 14
7. Al guardar las modificaciones si no existía previamente la tabla entonces nos pide que le pongamos un nombre a la tabla así como se muestra en la Imagen 15.
41
Imagen 15
8. En este punto al crear la nueva tabla ctl_alumnos se crea una tabla dinámica sqlite_sequence, esta tabla lleva el control de la secuencia de las llaves principales, por favor de no modificar esta tabla (Imagen 16).
Imagen 16
9. Android solicita que la base de datos incluya una tabla que se llama android_metadata, esta tabla debe de contener un solo campo de nombre locale y en la Imagen 17 se muestra cómo debe de quedar configurada dicha tabla.
42
Imagen 17
Cargar la base de datos en el proyecto de Android
1. En este punto es necesario recordar la ubicación en la que guardamos nuestra base de datos, porque necesitamos copiar dicho archivo a la carpeta /res/assets del proyecto android que estemos trabajando, esto lo podemos realizar desde el navegador de archivos del sistema operativo que estemos usando. Ya copiado el archivo, que para casos práctico nuestro archivo se debería de llamar bd_alumnos.bd, en la Imagen 18 vemos un ejemplo de cómo debería de verse la estructura de archivos de nuestro proyecto después de copiar el archivo.
Imagen 18
43
Cargar la base de datos en la aplicación para poder usarla
1. Lo primero será crear un paquete nuevo para separar los archivos relacionados con el manejo de base de datos de los del manejo de las activities, el nombre del nuevo paquete será com.demo.proyectodemo.db.
2. Ahora crearemos la clase java DataBaseManager.java la cual tendrá varias funciones y/o métodos:
Método Funcionalidad DataBaseManager(Context context)
Constructor
createDataBase() Crea una base de datos vacía en el sistema y la reescribe con tu propia base de datos
checkDataBase() Verifica si la base de datos ya existe para evitar re-‐copiar el archivo cada vez que abres la aplicación
copyDataBase() Copia tu base de datos desde la carpeta local assets a la base de datos vacía recientemente creada en el sistema de carpetas, para que pueda ser accesada y manipulada. Esto es hecho por transferencia de flujo de bytes
openDataBase() Abre la base de datos close() Cierra la base de datos
Tabla 4
3. Lo primero que haremos será declarar unas variables globales que serán utilizadas en diferentes partes de la clase, como se muestra en la Imagen 19.
Imagen 19
4. Lo siguiente que haremos será declarar el constructor de la clase como lo vemos en la Imagen 20.
44
Imagen 20
5. Ahora crearemos una método que verificará si la base de datos ya existe, esta clase solo regresa true si ya existe o false en caso de no existir, dicho método se llamará checkDataBase(), el código necesario en esta clase se muestra en la Imagen 21.
Imagen 21
6. El siguiente método necesario es uno que nos ayude a copiar la base de datos que tenemos en la carpeta assets al sistema de archivos del proyecto para que pueda ser usada y manipulada nuestra base de datos, este método tendrá el nombre copyDataBase(), y la copia del archivo de la base de datos desde assets hacía el sistema de archivos de la aplicación se realiza a través de un flujo de bytes, el código de dicho método lo podemos ver en la Imagen 22.
45
Imagen 22
7. Los dos métodos anteriores son necesarios puesto que son utilizados por el método createDataBase() ya que primero valida si la base de datos ya existe y en caso de que no exista entonces copia la base de datos. Esto lo podemos ver en la Imagen 23.
Imagen 23
46
8. Por último falta agregar dos métodos muy importantes para abrir y cerrar la base de datos, estos métodos son openDataBase() y close(), en la Imagen 24 podemos observar el código de estos dos métodos.
Imagen 24
9. Ahora que ya tenemos todo lo necesario para agregar nuestra base de datos a nuestra aplicación será necesario que creemos un métoodo que inicie todo el proceso de revisión y creación de la base de datos, una buena opción es hacerlo en el momento en el que se pone en marcha la aplicación, digamos que podemos crear un Splash y en este momento hacer lo relacionado con la base de datos, esto con ayuda de la clase AsyncTask
10. Lo primero será crear una activity llamada SplashScreen.java, y declararla como la pantalla de inicio de nuestra aplicación dentron del Manifest de nuestra aplicación (en realidad el Asynctask lo podemos usar en el momento que lo deseemos, podríamos no usar una pantalla de Splash, y hacerlo directamente en nuestra pantalla de inicio, esto es a decisión suya y de las necesidades del proyecto).
11. Ahora es cuestión y decisión de su proyecto como diseñar la interfaz del layout del Splash que se a creado, activity_splash_screen.xml, por cuestiones de economía de tiempo yo lo diseñaré muy simple:
47
12. Y quedando el layout activity_splash_screen.xml como se muestra a continuación:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:background="#CECECE" tools:context=".SplashScreen" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:gravity="center" android:text="@string/hello_world" android:textSize="40sp" /> </RelativeLayout>
48
13. El código de la clase SplashScreen.java es muy simple, en el método onCreate()se carga el layout que se va a utilizar y se manda llamar a la subclase interna priva que se creeo dentro de esta actividad.
14. Ya en la sublcase sobreescribimos los métodos doInBackground y onPostExecute, en el primero método es donde se crea un hilo que en este caso será el que se encargue de ejecutar el método de comprobar si ya existe la base de datos o hay que crearla, el segundo método indica que es lo que se hará en el momento que se haya terminado de ejecutar el hilo, en nuestro caso sólo carga la siguiente activity.
15. Y este es el código final que tenemos:
import java.io.IOException; import com.utm.primero.database.DataBaseManager; import android.os.AsyncTask; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.Menu; public class SplashScreen extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash_screen); /* * Se manda llamar o ejecutar la subclase que hereda * de la clase AsynTask * */ new VerificarBaseDatos().execute(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.splash_screen, menu); return true; } /* * Creamos la subclase que hereda de AsyncTask * */ private class VerificarBaseDatos extends AsyncTask<Void, Void,
49
Void> { /* * No realizamos nada en el método onPreExecute * */ @Override protected void onPreExecute() { super.onPreExecute(); } /* * En el método doInBackground es el momento * donde verificaremos la existencia de la base de datos * */ @Override protected Void doInBackground(Void... params) { DataBaseManager myDbHelper = new DataBaseManager(SplashScreen.this); try { myDbHelper.createDataBase(); } catch (IOException e) { throw new Error("Imposible crear la base de datos"); } return null; } /* * Al momento de terminar la ejecución del método * doInBackground será momento de ejecutar al método * onPostExecute*/ @Override protected void onPostExecute(Void result) { super.onPostExecute(result); // Después de ejecutar la verificación de la base de datos // cerrarremos esta actividad y lanzaremos la nnueva actividad Intent i = new Intent(SplashScreen.this, MainActivity.class); startActivity(i); // cerrar definitivamente esta actividad // para evitar que en el momento de dar Back se ejecute de nuevo } } }
16. Cabe recordar o hacer notar algunas cosas:
50
a. Esta es sólo una forma de poder verificar si ya existe la base de datos instalada en nuestro teléfono, no basta con que la base de datos este en el proyecto o en la aplicación, tienen que estar instalada en el teléfono y esto es lo que hace todo este procedimiento.
b. La clase AsyncTask es una forma mucho más fácil de hacer tareas asíncronas, y puede ser utiizada en cualquier parte de nuestro sistema, no solo en el splash o para verificar que la base de datos está ya instalada.
Creación de la Base de Datos con código desde la aplicación
Al tener ya creado algún proyecto que pueda utilizar o necesitar una base de datos de SQLite será necesario que creemos un nuevo paquete para guardar las clases que manejarán la base de datos. Como caso de ejemplo se creará el paquete com.example.tareasqlite.database (claro, siempre y cuando ya tengamos hecho un proyecto, en caso de no tenerlo debemos de iniciar un proyecto nuevo). En este caso se crearán dos clases DataBaseHelper.java y DBAdapter.java como se muestra en la siguiente imagen
Veamos que contienen las clases que acabamos de crear, la primera en analizar es la clase DataBaseHelper.java dicha clase extiende (hereda) de la clase SQLiteOpenHelper. A continuación tenemos el contenido completo de la clase la cuál vamos a ir explicando líneas abajo:
package com.example.tareasqlite.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; public class DataBaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "applicationdata"; private static final int DATABASE_VERSION = 1; //Sentencia para crear la BD private static final String DATABASE_CREATE = "create table todo
51
(_id integer primary key autoincrement, " + "categoria text not null, resumen text not null, descripcion text not null);"; public DataBaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } //Este método es llamado cuando se crea la BD @Override public void onCreate(SQLiteDatabase database) { database.execSQL(DATABASE_CREATE); } //Método que se llama cada vez que se actualiza la BD //Incrementa la versión @Override public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { Log.w(DataBaseHelper.class.getName(), "Actualizando la BD desde la versión " + oldVersion + " a "+ newVersion + ", la cual destruirá todos los datos viejos"); database.execSQL("DROP TABLE IF EXISTS todo"); onCreate(database); } }
Esta clase básicamente nos ayudará a definir la estructura de la base de datos. Al inicio de la clase creamos tres constantes: un String llamado DATABASE_NAME que define el nombre de la base de datos, un int llamado DATABASE_VERSION que pasará el número de versión de la base de datos cuando es creada, y la consulta que nos servirá para crear la base de datos.
Todo esto se define desde el constructor que se encuentra líneas abajo en esta clase.
El método onCreate() es el que ejecuta la consulta CREATE para la base de datos. Por otro lado, el método onUpgrade() manejaremos todas las acciones que necesitemos cada vez que se actualice la base de datos; en este caso, únicamente estamos mandando mensajes en el Log.
Ahora, pasemos a la clase DBAdapter.java que es la que contendrá toda la funcionalidad para realizar consultas, crear y editar cada uno de los items que manejará la aplicación.
import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase;
52
public class DBAdapter { //Campos de la BD public static final String KEY_ROWID = "_id"; public static final String KEY_CATEGORY = "categoria"; public static final String KEY_SUMMARY = "resumen"; public static final String KEY_DESCRIPTION = "descripcion"; private static final String DATABASE_TABLE = "todo"; private Context context; private SQLiteDatabase database; private DataBaseHelper dbHelper; public DBAdapter(Context context) { this.context = context; } public DBAdapter open() throws SQLException{ dbHelper = new DataBaseHelper(context); database = dbHelper.getWritableDatabase(); return this; } public void close(){ dbHelper.close(); } /** * * Crea una nueva tarea, si esto va bien retorna * la rowId de la tarea, de lo contrario retorna -1 * * */ public long crearNuevaTarea(String categoria, String resumen, String descripcion){ ContentValues inicialValues = crearContentValues(categoria, resumen, descripcion); return database.insert(DATABASE_TABLE, null, inicialValues); } //Actualiza la tarea public boolean updateTarea(long rowId, String categoria, String resumen, String descripcion){ ContentValues actulizaValues = crearContentValues(categoria, resumen, descripcion); return database.update(DATABASE_TABLE,actulizaValues, KEY_ROWID + "=" +rowId, null) > 0; }
53
//Borrar la tarea public boolean deleteTarea(long rowId){ return database.delete(DATABASE_TABLE, KEY_ROWID + "=" +rowId, null) > 0; } // Retorna un cursor que contiene todos los items public Cursor recuperaTodos(){ String[] columnas = {KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION}; return database.query(DATABASE_TABLE, columnas, null, null, null, null, null); } // Retorna un Cursor que contiene la info de una tarea public Cursor recuperaTarea(long rowId) throws SQLException{ String[] columnas = {KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION}; Cursor mCursor = database.query(true, DATABASE_TABLE, columnas, KEY_ROWID + "=" +rowId, null, null, null, null, null); if(mCursor != null){ mCursor.moveToFirst(); } return mCursor; } private ContentValues crearContentValues(String categoria, String resumen, String descripcion) { ContentValues values = new ContentValues(); values.put(KEY_CATEGORY, categoria); values.put(KEY_DESCRIPTION, descripcion); values.put(KEY_SUMMARY, resumen); return values; } }
Vamos a explicar cada uno de los métodos que estamos creando. La primera parte corresponde a la creación de las constantes y variables que vamos a utilizar. Las constantes son cada una de las columnas de la tabla que almacenará la información de la aplicación. Después tenemos tres variables que corresponden al contexto de la aplicación, a la base de datos y al helper para manipular las consultas y funciones con SQLite.
En el constructor únicamente definiremos el contexto de la aplicación. Después, tenemos el método open() que sirve para preparar la base de datos a modo que podamos escribir datos en ella, para ello
54
se abre la base de datos a través del objeto de tipo DBHelper. En este método ocupamos una SQLException pues es probable que a veces ocurran errores en esta parte y esto nos servirá para conocerlos. El método close() sirve para cerrar el helper. Si has trabajado con Java y MySQL, verás que es algo muy parecido a lo que tenemos que hacer para manejar bases de datos a través de un conector.
Posteriormente, tenemos los métodos para crear, actualizar, borrar y consultar los elementos de la base de datos a través de la clase ContentValues nos permitirán crear y actualizar valores dentro de la base de datos. A través de esta clase podemos manipular la correspondencia entre identificador/valor, utilizando el nombre de la columna como el identificador al cuál únicamente le definiremos el valor para actualizar o crear un nuevo elemento dentro de la base de datos.
Manejo de SQLite en Android
SQLite es un motor de bases de datos muy popular en la actualidad por ofrecer características tan interesantes como su pequeño tamaño, no necesitar servidor, precisar poca configuración, ser transaccional y por supuesto ser de código libre.
Android incorpora de serie todas las herramientas necesarias para la creación y gestión de bases de datos SQLite, y entre ellas una completa API para llevar a cabo de manera sencilla todas las tareas necesarias. Por el momento nos limitaremos a ver el código necesario para crear una base de datos, insertaremos algún dato de prueba, y veremos cómo podemos comprobar que todo funciona correctamente.
Por el momento hemos visto dos formas de crear y/o utilizar una base de datos SQLite en Android, más adelante en este manual veremos la forma de utilizar el segundo método (Creación de la Base de Datos con código desde la aplicación) en un proyecto de ejemplo.
SQLiteOpenHelper
Para crear y trabajar con bases de datos en Android, es necesario hacer uso de la clase SQLiteOpenHelper. En esta clase es necesario sobrescribir los métodos onCreate() para crear la base de datos, y onUpgrade() para actualizar la base de datos en caso de que existan cambios en el esquema de la misma. Ambos métodos reciben como parámetro un objeto SQLiteDatabase.
SQLiteOpenHelper ofrece los métodos getReadableDatabase() y getWriteableDatabase() para trabajar con un objeto SQLiteDatabase y poder tener acceso de lectura y escritura sobre una base de datos.
SQLDatabase y Cursor
La clase SQLiteDatabase provee los métodos insert(), update() y delete() y execSQL() que nos ayuda a ejecutar sentencias SQL directamente. El objeto ContentValues permite definir claves y valores en las sentencias Insert y Update. La clave (key) corresponde a la columna y el valor es el valor para la columna.
55
Las consultas se pueden crear a través del método rawQuery() que acepta como parámetro una sentencia en SQL o el método query() que proporciona una interfaz para especificar los datos dinámicos o un objeto de tipo SQLiteQueryBuilder. SQLiteBuilder es similar a la interfaz de un proveedor de contenidos por lo que suele utilizarse con Content Providers. Hay que saber también que toda consulta que realicemos nos retornará un objeto de tipo Cursor.
Para definir la llave primaria de una base de datos es indispensable hacer uso del identificador _id ya que muchas de las funciones con las que trabajaremos toman en cuenta este estándar.
En Android, la forma típica para crear, actualizar, y conectar con una base de datos SQLite será a través de una clase auxiliar llamada SQLiteOpenHelper, o para ser más exactos, de una clase propia que derive de ella y que debemos personalizar para adaptarnos a las necesidades concretas de nuestra aplicación.
La API de SQLite de Android proporciona dos alternativas para realizar operaciones sobre la base de datos que no devuelven resultados (entre ellas la inserción/actualización/eliminación de registros, pero también la creación de tablas, de índices, etc).
El primero de ellos, es el método execSQL() de la clase SQLiteDatabase. Este método permite ejecutar cualquier sentencia SQL sobre la base de datos, siempre que ésta no devuelva resultados. Para ello, simplemente aportaremos como parámetro de entrada de este método la cadena de texto correspondiente con la sentencia SQL.
La segunda de las alternativas disponibles en la API de Android es utilizar los métodos insert(), update() y delete() proporcionados también con la clase SQLiteDatabase. Estos métodos permiten realizar las tareas de inserción, actualización y eliminación de registros de una forma algo más paramétrica que execSQL(), separando tablas, valores y condiciones en parámetros independientes de estos métodos.
Empecemos por el método insert() para insertar nuevos registros en la base de datos. Este método recibe tres parámetros, el primero de ellos será el nombre de la tabla, el tercero serán los valores del registro a insertar, y el segundo lo obviaremos por el momento ya que tan sólo se hace necesario en casos muy puntuales (por ejemplo para poder insertar registros completamente vacíos), en cualquier otro caso pasaremos con valor null este segundo parámetro.
Los valores a insertar los pasaremos como elementos de una colección de tipo ContentValues. Esta colección es de tipo diccionario, donde almacenaremos parejas de clave-‐valor, donde la clave será el nombre de cada campo y el valor será el dato correspondiente a insertar en dicho campo. Veamos la Imagen 26:
Imagen 25
56
Los métodos update() y delete() se utilizarán de forma muy parecida a ésta, con la salvedad de que recibirán un parámetro adicional con la condición WHERE de la sentencia SQL. Por ejemplo, para actualizar el email del usuario de nombre ‘usu1’ haríamos lo siguiente:
Imagen 26
Como podemos ver, como tercer parámetro del método update() pasamos directamente la condición del UPDATE tal como lo haríamos en la cláusula WHERE en una sentencia SQL normal. El método delete() se utilizaría de forma análoga. Por ejemplo para eliminar el registro del usuario ‘usu2’ haríamos lo siguiente:
Imagen 27
Como vemos, volvemos a pasar como primer parámetro el nombre de la tabla y en segundo lugar la condición WHERE. Por supuesto, si no necesitáramos ninguna condición, podríamos dejar como null en este parámetro.
Un último detalle sobre estos métodos. Tanto en el caso de execSQL() como en los casos de update() o delete() podemos utilizar argumentos dentro de las condiciones de la sentencia SQL. Esto no es más que partes variables de la sentencia SQL que aportaremos en un array de valores aparte, lo que nos evitará pasar por la situación típica en la que tenemos que construir una sentencia SQL concatenando cadenas de texto y variables para formar el comando SQL final. Estos argumentos SQL se indicarán con el símbolo ‘?’, y los valores de dichos argumentos deben pasarse en el array en el mismo orden que aparecen en la sentencia SQL. Así, por ejemplo, podemos escribir instrucciones como la siguiente:
Imagen 28
Esta forma de pasar a la sentencia SQL determinados datos variables puede ayudarnos además a escribir código más limpio y evitar posibles errores.
57
De forma análoga a lo que vimos para las sentencias de modificación de datos, vamos a tener dos opciones principales para recuperar registros de una base de datos SQLite en Android. La primera de ellas utilizando directamente un comando de selección SQL, y como segunda opción utilizando un método específico donde parametrizáremos la consulta a la base de datos.
Para la primera opción utilizaremos el método rawQuery() de la clase SQLiteDatabase. Este método recibe directamente como parámetro un comando SQL completo, donde indicamos los campos a recuperar y los criterios de selección. El resultado de la consulta lo obtendremos en forma de cursor, que posteriormente podremos recorrer para procesar los registros recuperados. Sirva la siguiente consulta a modo de ejemplo:
Imagen 29
Como en el caso de los métodos de modificación de datos, también podemos añadir a este método una lista de argumentos variables que hayamos indicado en el comando SQL con el símbolo ‘?‘, por ejemplo así:
Imagen 30
Como segunda opción para recuperar datos podemos utilizar el método query() de la clase SQLiteDatabase. Este método recibe varios parámetros: el nombre de la tabla, un array con los nombre de campos a recuperar, la cláusula WHERE, un array con los argumentos variables incluidos en el WHERE (si los hay, null en caso contrario), la cláusula GROUP BY si existe, la cláusula HAVING si existe, y por último la cláusula ORDER BY si existe. Opcionalmente, se puede incluir un parámetro al final más indicando el número máximo de registros que queremos que nos devuelva la consulta. Veamos el mismo ejemplo anterior utilizando el método query():
Imagen 31
Como vemos, los resultados se devuelven nuevamente en un objeto Cursor que deberemos recorrer para procesar los datos obtenidos.
Para recorrer y manipular el cursor devuelto por cualquiera de los dos métodos mencionados tenemos a nuestra disposición varios métodos de la clase Cursor, entre los que destacamos dos de los dedicados a recorrer el cursor de forma secuencial y en orden natural:
• moveToFirst(): mueve el puntero del cursor al primer registro devuelto.
58
• moveToNext(): mueve el puntero del cursor al siguiente registro devuelto.
Los métodos moveToFirst() y moveToNext() devuelven TRUE en caso de haber realizado el movimiento correspondiente del puntero sin errores, es decir, siempre que exista un primer registro o un registro siguiente, respectivamente.
Una vez posicionados en cada registro podremos utilizar cualquiera de los métodos getXXX(índice_columna) existentes para cada tipo de dato para recuperar el dato de cada campo del registro actual del cursor. Así, si queremos recuperar por ejemplo la segunda columna del registro actual, y ésta contiene un campo alfanumérico, haremos la llamada getString(1) [NOTA: los índices comienzan por 0, por lo que la segunda columna tiene índice 1], en caso de contener un dato de tipo real llamaríamos a getDouble(1), y de forma análoga para todos los tipos de datos existentes.
Con todo esto en cuenta, veamos cómo podríamos recorrer el cursor devuelto por el ejemplo anterior:
Imagen 32
Además de los métodos comentados de la clase Cursor existen muchos más que nos pueden ser útiles en muchas ocasiones. Por ejemplo, getCount() te dirá el número total de registros devueltos en el cursor, getColumnName(i) devuelve el nombre de la columna con índice i, moveToPosition(i) mueve el puntero del cursor al registro con índice i, etc. Podemos consultar la lista completa de métodos disponibles en la clase Cursor en la documentación oficial de Android.
Creando un Navigation Drawer No hace mucho para poder implementar este en nuestras aplicaciones de Android uno mismo debía implementar toda la funcionalidad por su cuenta o utilizar alguna librería de alguien ya lo halla hecho. Pero por suerte en el ultimo Google I/O 2013 se anuncio oficialmente que se incluiría dentro de la support library una funcionalidad llamada Navigation Drawer que es la que justamente nos permite implementar esto, gracias a esto podemos dotar a nuestras apps desde versiones antiguas de Android esta funcionalidad.
59
Lo primero que debemos de hacer es asegurarnos de que tenemos la versión r19.0.1 de Android Support Library en el SDK Manager, en el caso de no tenerlo debemos de instalarla desde el SDK Manager.
Nota
Para casos de este manual supondremos que ya tenemos un proyecto creado, en el caso de que no sea así será necesario que se cree uno nuevo y seguir el manual desde este punto.
Después de verificar de que tenemos la Support Library instalada y disponible para nuestro proyecto vamos a crear nuestro layout que será el esqueleto de nuestra UI.
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- Vista para el contenido principal --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff"/> <!-- Navigation Drawer --> <ListView android:id="@+id/drawerIzquierdo" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#E5E5E5" > </ListView> </android.support.v4.widget.DrawerLayout>
El contenedor principal es el DrawerLayout este básicamente es el que permite deslizar la vista en este caso será el ListView.
60
La posición de deslizamiento dentro del layout es controlado por la sig. propiedad android:layout_gravity este puede ser: left , right o start y end.
Para darnos una idea al finalizar el tutorial seremos capaces de implementar una UI de este tipo.
Como ven dentro del FrameLayout ira el contenido principal mientras que el Listview será el que se desplace horizontalmente ya sea clickeando en el botón del ActionBar o arrastrándolo tocando la pantalla.
Ahora vamos a la parte de la Activity principal
import android.app.Activity; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; import android.view.Menu; import android.widget.ListView; public class MainActivity extends Activity { private ListView mDrawerList; private DrawerLayout mDrawerLayout; @Override
FrameLayout ListView
61
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Drawer Layout
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
//Lista mDrawerList = (ListView) findViewById(R.id.drawerIzquierdo); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
Es muy simple, con solamente declarar el layout ya funcionaria, en este caso declaramos el DrawerLayout y ListView.
Si corremos la app en este punto y deslizamos el dedo de forma horizontal desde el extremo izquierdo de la pantalla hacia dentro veremos como se desplaza el menú, en este punto todavía no se incluyo el contenido del ListView ni el ActionBar.
62
A continuación veremos el diseño del Menú Vertical y como agregar opciones, el manejo de esto es igual que en cualquier aplicación común definiendo su layout, implementar un Adaptador especifico para este en el caso que fuese necesario, etc..
Diseño:
La idea es que por mas que sea un ejemplo puedan adquirir conocimientos para dotar a sus aplicaciones con una interfaz moderna similar a las que ya todos conocemos, obviamente no llegaremos a tanto ya que se necesita mucho diseño pero podemos tratar de inspirarnos de ellas, en este caso está basado en la de Google+ como lo vemos en la siguiente imagen.
63
Para nuestro ejemplo lo que tendremos es lo siguiente:
encabezado_drawer.xml
item_drawer.xml
64
Como se puede ver al principio se incluye un header que solamente cumple una función estética pero podría incluirse la foto de perfil del usuario, nombre, etc. Mas adelante veremos como integrarlos dentro del ListView.
/res/layout/encabezado_drawer.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <ImageView android:id="@+id/headerBackground" android:layout_width="fill_parent" android:layout_height="150dp" android:scaleType="centerCrop" android:src="@drawable/header" /> </FrameLayout>
Como podemos ver nada del otro mundo un FrameLayout con una imagen dentro.
/res/layout/ítem_drawer.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/icon" android:layout_width="25dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="12dp" android:layout_marginRight="12dp" android:layout_centerVertical="true" /> <TextView android:id="@+id/title_item" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toRightOf="@+id/icon" android:textColor="#111" android:gravity="center_vertical" android:paddingRight="40dp" android:paddingTop="15dp" android:paddingBottom="15dp"
65
android:text="@string/app_name" /> </RelativeLayout>
Para pode agregar opciones al menú se agregan tanto las opciones como la imagen que cada botón debe tener dentro del /res/values/string.xml.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">ToDoSQLite</string> <string name="hello_world">Hello world!</string> <string name="title_activity_tareas">TareasActivity</string> <!-- Lista de elementos que aparecen en la navegación --> <string-array name="nav_options"> <item >ToDo</item> <item >Conversor</item> <item >Perfil</item> <item >Configuración</item> <item >Conversor</item> </string-array> <!-- Lista de iconos de navegacion --> <string-array name="nav_iconos"> <item >@drawable/ic_action_go_to_today</item> <item >@drawable/ic_action_merge</item> <item >@drawable/ic_action_person</item> <item >@drawable/ic_action_good</item> <item >@drawable/ic_action_cloud</item> </string-array> </resources>
En la primera sección he declarado un array de Strings nav_options luego otro de drawables nav_iconos, esta es una forma fácil y rápida de poder declarar estos aunque también pudo haberse declarado mediante java en la actividad principal, en el caso que el contenido fuese dinámico esta opción del xml no seria viable.
Para poder facilitar el uso del menú en el adaptador que veremos luego he creado una clase llamada Items.java en esta básicamente se declaran los métodos para asignarle un nombre y una imagen.
public class Items { private String titulo; private int icono; public Items(String titulo, int icono) { this.titulo = titulo; this.icono = icono; }
66
public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getIcono() { return icono; } public void setIcono(int icono) { this.icono = icono; } }
Volviendo a nuestra activity prinicipal añadiremos el resto de cogido para obtener los datos desde el archivos strings.xml:
import java.util.ArrayList; import android.app.Activity; import android.content.res.TypedArray; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; import android.view.Menu; import android.view.View; import android.widget.ListView; public class MainActivity extends Activity { private String[] titulos; private ListView mDrawerList; private ArrayList<Items> NavItems; private TypedArray NavIconos; NavigationAdapter NavAdapter; private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Drawer Layout mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); //Lista mDrawerList = (ListView) findViewById(R.id.drawerIzquierdo);
67
//Declaramos el header el cual será el layout de encabezado_drawer.xml View encabezado = getLayoutInflater().inflate(R.layout.encabezado_drawer, null); //Establecer el encabezado mDrawerList.addHeaderView(encabezado); //Tomamos listado de imgs desde drawable NavIconos = getResources().obtainTypedArray(R.array.nav_iconos); //Tomamos listado de titulos desde el string-array de los recursos @string/nav_options titulos = getResources().getStringArray(R.array.nav_options); //Listado de titulos de barra de navegación NavItems = new ArrayList<Items>(); //Agregamos objetos Item_objct al array //ToDo NavItems.add(new Items(titulos[0], NavIconos.getResourceId(0, -1))); //Conversor NavItems.add(new Items(titulos[1], NavIconos.getResourceId(1, -1))); //Eventos NavItems.add(new Items(titulos[2], NavIconos.getResourceId(2, -1))); //Lugares NavItems.add(new Items(titulos[3], NavIconos.getResourceId(3, -1))); //Etiquetas NavItems.add(new Items(titulos[4], NavIconos.getResourceId(4, -1))); //Declaramos y seteamos nuestro adaptador al cual le pasamos el array con los titulos NavAdapter= new NavigationAdapter(this,NavItems); mDrawerList.setAdapter(NavAdapter); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } } Aquí podemos observar que ya estamos integrando encabezado_drawer.xml dentro del ListView (mDrawerList.addHeaderView(encabezado);), luego tomamos desde los resources el array de drawables, añadimos uno a uno los elementos del menú al arraylist NavItems en cada uno de estos agregamos un objeto Items en donde le pasamos la posición dentro del array de títulos y la imagen. Al final declaramos el adaptador NavAdapter al cual le pasamos el arraylist. Una parte muy importante es el Adapter, por lo cual lo veremos en una sección de Adapaters, en el
68
listado siguiente podemos ver el Adapter que se diseña para nuestro ejemplo. Primero habrá crear una nueva clase que herede de la clase BaseAdapter, dicha clase la llamaremos NavigationAdapter.java import java.util.ArrayList; import android.app.Activity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class NavigationAdapter extends BaseAdapter { private Activity mActivity; ArrayList<Items> arrayItems; public NavigationAdapter(Activity mActivity, ArrayList<Items> arrayItems) { super(); this.mActivity = mActivity; this.arrayItems = arrayItems; } @Override public int getCount() { // TODO Auto-generated method stub return arrayItems.size(); } //Retorna objeto Items del array list @Override public Object getItem(int position) { // TODO Auto-generated method stub return arrayItems.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } //Declaramos clase estática, la cual representa la fila public static class Fila{ TextView tituloItem; ImageView icono; }
69
@Override public View getView(int position, View convertView, ViewGroup arg2) { Fila view; LayoutInflater inflator = mActivity.getLayoutInflater(); if(convertView == null){ view = new Fila(); //Crear objeto item y obtenerlo del array Items item = arrayItems.get(position); convertView = inflator.inflate(R.layout.item_drawer, null); //Titulo view.tituloItem = (TextView) convertView.findViewById(R.id.title_item); //Establecer en el campo titulo el nombre correspondiente obtenido del objeto item view.tituloItem.setText(item.getTitulo()); //Icono view.icono = (ImageView) convertView.findViewById(R.id.icon); //Seteo del icono view.icono.setImageResource(item.getIcono()); convertView.setTag(view); }else { view = (Fila) convertView.getTag(); } return convertView; } } Lo que simplemente hace es tomar del objeto extraído del Arraylist el texto y la imagen para luego asignársela a su correspondiente elemento. En este punto nuestra aplicación permite utilizar la navegación deslizable de momento solo funcionara con versiones de Android superiores o iguales a 4.0, en el caso de que se necesite o se deseé implementar este control para versiones anteriores será necesario seguir otro procedimiento para agregar librerías para hacerla compatible con versiones anteriores de Android. Al ejecutar nuestro proyecto esto es lo que veríamos:
70
El siguiente paso será el de incorporar el botón para abrir y cerrar el menú de navegación y como asignarle una acción a la pulsación sobre un item del menú. Abrir y Cerrar menú desde un ícono.
Ahora veremos como implementar el clasico botón para abrir y cerrar el menú, el botón se mostrará en la esquina superior izquierda junto al título.
Para poder realizar esto utilizaremos una clase llamada ActionBarDrawerToggle, su uso es muy simple como veremos a continuación,
import android.app.Activity; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.util.Log;
71
import android.view.Menu; import android.view.View; public class MainActivity extends Activity { NavigationAdapter NavAdapter; private ActionBarDrawerToggle mDrawerToggle; private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Aquí va el resto del código previamente escrito... //Declaramos mDrawableToggle y las imgs a utilizar mDrawerToggle = new ActionBarDrawerToggle( this, /* Activity huesped*/ mDrawerLayout, /* objeto DrawerLayout */ R.drawable.ic_drawer, /* imagen del nav drawer */ R.string.app_name, /* "abrir drawer" descripción para accesibilidad */ R.string.hello_world /* "cerrar drawer" descripción para accesibilidad */ ) { public void onDrawerClosed(View view) { Log.e("Cerrado completo", "!!"); } public void onDrawerOpened(View drawerView) { Log.e("Apertura completa", "!!"); } }; //Establecemos que mDrawerToggle declarado anteriormente sea el DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); //Establecemos que el ActionBar muestre el botón home getActionBar().setDisplayHomeAsUpEnabled(true); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; }
72
}
En el método onCreate() se declara la variable mDrawerToggle a la cual hay que especificar el contexto, el drawerLayout, el icono que se mostrará en el botón (disponible en el pack de iconos para Actionbar ), y 2 Strings de descripción (que en realidad no se usan en este ejemplo pero que hay que especificarlos). También podemos especificar una acción al abrir y cerrar el menú en onDrawerClosed() y onDrawerOpened(), dar un vistazo al Logcat durante el funcionamiento de la aplicación.
Mediante mDrawerLayout.setDrawerListener(mDrawerToggle; asignamos cual será el DrawerListener que utilizara en este caso será nuestro mDrawerToggle .
Luego en getActionBar().setDisplayHomeAsUpEnabled(true); Establecemos que el ActionBar muestre el botón del Home de nuestra aplicación.
Si corremos la aplicación en esta parte podemos ver como al presionar el icono el menú se cierra y abre también podrán observar que este se desplaza según este el menú abierto o no.
Asignar acción a la pulsación de un Item del menú de navegación
Lo primero que debemos de hacer es asignar un evento a cada uno de los ítems de la lista, para poder especificar cada opción de la lista, en este caso lo que haremos es según la opción del menú que pulse mostrará un fragment especifico, por lo que para cada pantalla habrá que definir un fragment y un layout para esa opción. Para efectos del presente tutorial crearemos una clase nueva que reaccionará al toque del primer ítem de la lista, dicha clase la llamaremos TareasFragment.java y hereda de la clase Fragment. A continuación podemos ver el código de dicha clase:
import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class TareasFragment extends Fragment { public TareasFragment(){} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.activity_tareas, container, false); return rootView; }
73
}
Ya cuando se tiene listo el fragment el código que necesitamos que traiga nuestra clase principal será el siguiente:
//Establecemos la accion al clickear sobre cualquier item del menu. //De la misma forma que hariamos en una app comun con un listview. mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { MostrarFragment(position); } });
//Cuando la aplicacion cargue por defecto mostrar la opción Home MostrarFragment(1);
Dentro de OnCreate() al mDrawerList le asignamos el método OnItemClickListener y dentro hacemos una llamada al método MostrarFragment(position) al cual hay que pasarle como parámetro la posición de la opción seleccionada en el menú, en base a esto mostrara el fragment especifico.
Luego fuera de esto indico que cuando la aplicación cargue muestre la opción “ToDo” por defecto, para eso le paso el numero 1 el cual equivale a la primer opción del menú.
//Cuando la aplicacion cargue por defecto mostrar la opción Home MostrarFragment(1);
Ver los comentarios en cada línea par entender como funciona el método MostrarFragment(int position)que a continuación se muestra, este método también va en la clase MainActivity.java de nuestro proyecto:
/*Pasando la posicion de la opcion en el menu nos mostrara el Fragment correspondiente*/ private void MostrarFragment(int position) { // update the main content by replacing fragments Fragment fragment = null; switch (position) { case 1: fragment = new TareasFragment(); break; /*case 2: fragment = new ProfileFragment(); break;*/
74
default: //si no esta la opcion mostrara un toast y nos mandara a Home Toast.makeText(getApplicationContext(),"Opcion "+titulos[position-1]+"no disponible!", Toast.LENGTH_SHORT).show(); fragment = new TareasFragment(); position=1; break; } //Validamos si el fragment no es nulo if (fragment != null) { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // Actualizamos el contenido segun la opcion elegida mDrawerList.setItemChecked(position, true); mDrawerList.setSelection(position); //Cambiamos el titulo en donde decia " setTitle(titulos[position-1]); //Cerramos el menu deslizable mDrawerLayout.closeDrawer(mDrawerList); } else //Si el fragment es nulo mostramos un mensaje de error. Log.e("Error ", "MostrarFragment"+position); }
Con esto básicamente ya podemos dotar a nuestra aplicación de la lógica necesaria para poder mostrar cada opción del menú, luego depende de cada uno lo que desee mostrar en sus aplicaciones.
Por último hay que agregar los siguientes métodos: onPostCreate,
onConfigurationChanged, onOptionsItemSelected para sobrescribirlos.
@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync el estado del toggle despues de onRestoreInstanceState haya ocurrido. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Pasar el evento al ActionBarDrawerToggle, si este
75
// regresa true, entonces este manejara el evento del touch // en el icono de la app if (mDrawerToggle.onOptionsItemSelected(item)) { Log.e("mDrawerToggle presionado", "x"); return true; } // Maneja los otros items de la action bar... return super.onOptionsItemSelected(item); }
En este punto nuestra clase MainActivity.java se vería como se muestra a continuación.
import java.util.ArrayList; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; public class MainActivity extends Activity {
private String[] titulos; private ListView mDrawerList; private ArrayList<Items> NavItems; private TypedArray NavIconos; NavigationAdapter NavAdapter; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Drawer Layout mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
76
//Lista mDrawerList = (ListView) findViewById(R.id.drawerIzquierdo); //Declaramos el header el cual será el layout de encabezado_drawer.xml View encabezado = getLayoutInflater().inflate(R.layout.encabezado_drawer, null); //Establecer el encabezado mDrawerList.addHeaderView(encabezado); //Tomamos listado de imgs desde drawable NavIconos = getResources().obtainTypedArray(R.array.nav_iconos); //Tomamos listado de titulos desde el string-array de los recursos @string/nav_options titulos = getResources().getStringArray(R.array.nav_options); //Listado de titulos de barra de navegacion NavItems = new ArrayList<Items>(); //Agregamos objetos Item_objct al array //ToDo NavItems.add(new Items(titulos[0], NavIconos.getResourceId(0, -1))); //Conversor NavItems.add(new Items(titulos[1], NavIconos.getResourceId(1, -1))); //Eventos NavItems.add(new Items(titulos[2], NavIconos.getResourceId(2, -1))); //Lugares NavItems.add(new Items(titulos[3], NavIconos.getResourceId(3, -1))); //Etiquetas NavItems.add(new Items(titulos[4], NavIconos.getResourceId(4, -1))); //Declaramos y seteamos nuestrp adaptador al cual le pasamos el array con los titulos NavAdapter= new NavigationAdapter(this,NavItems); mDrawerList.setAdapter(NavAdapter); //Siempre vamos a mostrar el mismo titulo mTitle = mDrawerTitle = getTitle(); //Declaramos mDrawableToggle y las imgs a utilizar mDrawerToggle = new ActionBarDrawerToggle( this, /* Activity huesped*/ mDrawerLayout, /* objeto DrawerLayout */ R.drawable.ic_drawer, /* imagen del nav drawer */ R.string.app_name, /* "abrir drawer" descripción para accesibilidad */ R.string.hello_world /* "cerrar drawer" descripción para accesibilidad */ ) { public void onDrawerClosed(View view) {
77
Log.e("Cerrado completo", "!!"); } public void onDrawerOpened(View drawerView) { Log.e("Apertura completa", "!!"); } }; //Establecemos que mDrawerToggle declarado anteriormente sea el DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); //Establecemos que el ActionBar muestre el botón home getActionBar().setDisplayHomeAsUpEnabled(true); //Establecemos la accion al clickear sobre cualquier item del menu. //De la misma forma que hariamos en una app comun con un listview. mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { MostrarFragment(position); } }); //Cuando la aplicacion cargue por defecto mostrar la opcion Home MostrarFragment(1); } /*Pasando la posicion de la opcion en el menu nos mostrara el Fragment correspondiente*/ private void MostrarFragment(int position) { // update the main content by replacing fragments Fragment fragment = null; switch (position) { case 1: fragment = new TareasFragment(); break; /*case 2: fragment = new ProfileFragment(); break;*/ default: //si no esta la opcion mostrara un toast y nos mandara a Home Toast.makeText(getApplicationContext(),"Opcion
78
"+titulos[position-1]+"no disponible!", Toast.LENGTH_SHORT).show(); fragment = new TareasFragment(); position=1; break; } //Validamos si el fragment no es nulo if (fragment != null) { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // Actualizamos el contenido segun la opcion elegida mDrawerList.setItemChecked(position, true); mDrawerList.setSelection(position); //Cambiamos el titulo en donde decia " setTitle(titulos[position-1]); //Cerramos el menu deslizable mDrawerLayout.closeDrawer(mDrawerList); } else //Si el fragment es nulo mostramos un mensaje de error. Log.e("Error ", "MostrarFragment"+position); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync el estado del toggle despues de onRestoreInstanceState haya ocurrido. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Pasar el evento al ActionBarDrawerToggle, si este // regresa true, entonces este manejara el evento del touch // en el icono de la app if (mDrawerToggle.onOptionsItemSelected(item)) { Log.e("mDrawerToggle presionado", "x"); return true; } // Maneja los otros items de la action bar... return super.onOptionsItemSelected(item); } @Override
79
public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
Interactuar con una Base de Datos En la sección "Creación y manejo de Base de DatosCreación y manejo de Base de Datos" vimos y aprendimos la forma de cómo crear y conectarse con una base de datos, además de haber creado algunos métodos para realizar consultas, actualizaciones y borrado de datos en una tabla.
En esta parte del manual procederemos a utilizar estos métodos y clases para utilizarlas en un ejemplo como parte del proyecto que venimos desarrollando hasta ahora, para lo cual vamos a modificar algunas clases y crear algunas nuevas, así como crear nuevos layouts y algunos nuevos archivos de recursos, manos a la obra.
Lo primero que haremos es definir lo que necesitamos para esto necesitamos la siguiente estructura:
1. Una actividad para desplegar la lista de tareas que el usuario vaya agregando. En realidad esta ya la tenemos creada (ver sección "Asignar acción a la pulsación de un Item del menú de navegación"), pero vamos a modificarla.
2. Una actividad para agregar estas tareas. 3. Una clase para manejar la creación de la base de datos y las versiones de la misma (ver
SQLiteOpenHelper.java ). 4. Una clase para manejar la lógica del uso de consultas en SQLite de la aplicación DBAdapter.java
A esta altura del manual ya tenemos solventados los puntos 3 y 4, por lo tanto vamos a empezar a ver el punto 1 y posteriormente el 2.
Agregar recursos a la aplicación
Ahora, vamos a diseñar los recursos extras como cadenas de texto y otro tipo de información que necesitará desplegar la aplicación.
Primero, vamos a crear la estructura del menú de opciones de la aplicación. Definimos un archivo XML llamado tareas.xml dentro del directorio res > menu. A continuación vemos el código que deberá contener este archivo:
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/insertar"
80
android:orderInCategory="100" android:showAsAction="never" android:title="@string/action_insertar"/> </menu>
El archivo strings.xml que ya tenemos como parte de nuestro proyecto en el directorio res > values deberá de lucir y/o contener todo el siguiente código:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">ToDoSQLite</string> <string name="action_insertar">Insertar</string> <string name="hello_world">Hello world!</string> <string name="title_activity_tareas">TareasActivity</string> <string-array name="prioridades"> <item>Urgente</item> <item>Recordatorio</item> </string-array> <!-- Lista de elementos que aparecen en la navegación --> <string-array name="nav_options"> <item>ToDo</item> <item>Conversor</item> <item>Perfil</item> <item>Configuración</item> <item>Conversor</item> </string-array> <!-- Lista de iconos de navegacion --> <string-array name="nav_iconos"> <item>@drawable/ic_action_go_to_today</item> <item>@drawable/ic_action_merge</item> <item>@drawable/ic_action_person</item> <item>@drawable/ic_action_good</item> <item>@drawable/ic_action_cloud</item> </string-array> <string name="menu_insert">Agregar Tarea</string> <string name="menu_delete">Borrar Tarea</string> <string name="tarea_resumen">Nombre</string> <string name="tarea_descripcion">Borrar</string> <string name="tarea_editar_resumen">Nombre</string> <string name="tarea_editar_descripcion">Descripción</string> <string name="tarea_editar_confirmar">Aceptar</string> <string name="no_todos">Aún no existen elementos en la lista</string> </resources>
81
Diseño de layouts
Vamos a definir dos archivos de layout, uno para la lista, y uno para las filas. A continuación veremos el código del archivo activity_tareas.xml del directorio res > layout que define la apariencia de la lista que desplegará la información de la base de datos.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/list_color" android:orientation="vertical" > <ListView android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content" > </ListView> <TextView android:id="@android:id/empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/no_todos" /> </LinearLayout>
En el siguiente layout vamos a hacer uso de un icono, se puede utilizar el que deseen incluso se puede omitir y no hay ningún problema. El siguiente archivo de layout se llama row.xml y se usará para darle estilo a cada una de las filas de la lista:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <ImageView android:id="@+id/icon" android:src="@drawable/reminder" android:layout_marginLeft="4dp" android:layout_marginTop="8dp" android:layout_marginRight="8dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/label" android:text="@string/hello_world" android:layout_height="wrap_content" android:textSize="20sp"
82
android:layout_marginTop="6dp" android:layout_width="wrap_content" android:textColor="@color/negro" /> </LinearLayout>
Ahora que ya hemos definido los recursos, layouts y las clases que nos ayudarán a manipular la parte de SQLite en Android. Estamos listos para crear las clases que nos ayudarán a juntar cada una de estas piezas.
Agregar clases para manejo de interacción con la base de datos
En este parte vamos a dar solución a los puntos 1 y 2 que vimos al inicio de esta sección (ver Interactuar con una Base de Datos), para lo cual primero crearemos una nueva activity que se llamará Details.java en dónde vamos a presentar el formulario de creación y edición de items para cada uno de los elementos que seleccionemos en la lista principal.
En el momento de crear la nueva actividad nos creará un nuevo layout de nombre activity_details.xml, vamos a editarlo para que se vea gráficamente de la siguiente manera:
Si vemos el código del xml veremos esto:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
83
android:layout_height="fill_parent" android:background="@color/list_color" android:orientation="vertical" > <Spinner android:id="@+id/category" android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/prioridades" /> <LinearLayout android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <EditText android:id="@+id/todo_edit_summary" android:layout_width="wrap_content" android:layout_height="0dip" android:layout_weight="1" android:hint="Nombre" > </EditText> </LinearLayout> <EditText android:id="@+id/todo_edit_description" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="top" android:hint="Descripción" > </EditText> <Button android:id="@+id/todo_edit_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tarea_editar_confirmar" /> </LinearLayout>
Ya tenemos diseñada la parte del cómo se verá la activity, es momento de pasar a la etapa en la que escribiremos el código que controlará el proceso de consultas a la base de datos, de inserción y modificación de datos así como el de llenado del formulario en el momento que hagamos alguna edición de una “tarea” previamente creada.
84
En todo el siguiente listado se muestra el código de la clase DetailsActivity.java, para su mayor comprensión se recomienda que se lean los comentarios que es ahí donde se explica lo mejor posible la funcionalidad del código.
import com.android.utm.todosqlite.database.DBAdapter; import android.app.Activity; import android.database.Cursor; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; public class DetailsActivity extends Activity { private EditText mTitleText; private EditText mBodyText; private Long mRowId; private DBAdapter mDbHelper; private Spinner mCategory; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); // Cargamos el layout de nuestro fragment setContentView(R.layout.activity_details); /* * Inicializamos los objetos y los enlazamos con * los elementos (views) del layout * */ mCategory = (Spinner) findViewById(R.id.category); mTitleText = (EditText) findViewById(R.id.todo_edit_summary); mBodyText = (EditText) findViewById(R.id.todo_edit_description); Button confirmButton = (Button) findViewById(R.id.todo_edit_button); /* * Recuperamos los parámetros que enviamos a esta * activity * */ mRowId = null; Bundle extras = getIntent().getExtras(); mRowId = (bundle == null) ? null : (Long)
85
bundle.getSerializable(DBAdapter.KEY_ROWID); if (extras != null) { mRowId = extras.getLong(DBAdapter.KEY_ROWID); } // Mandamos llamar al método que se encargará de llenar los campos en caso de ser una edición populateFields(); /* * Le asignamos el evento de OnClickListener a nuetro botón * esto con la finalidad de que guarde los valores que tenemos * en nuestro formulario * */ confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { setResult(RESULT_OK); saveState(); finish(); } }); } /* * Método que permite llenar los campos del formulario * en el caso de que sea una edición, si no es una * edición los campos será dejados vacíos * */ private void populateFields() { /* * Confirmamos que a variable mRowId no está nula, * esto confirma que se trata de una edición * */ if (mRowId != null) { /*Recuperamos los datos de la tarea que coincide con la variable mRowId*/ Cursor todo = mDbHelper.recuperaTarea(mRowId); /* * El método startManagingCursor que se hereda de la clase Activity * nos sirve o nos ayuda para tener un mejor control del cursor, pues * este se adaptará al ciclo de vida de la activity, esto es que si * la activity es detenida el cursor automáticamente llamará al método * deactivate(), y cuando la activity se reinicie se llamrá al método
86
* requery() para reiniciar el cursor. También nos ayudará al momento * en que la activity sea destruida el Cursor será cerrado automáticamente */ startManagingCursor(todo); String category = todo.getString(todo.getColumnIndexOrThrow(DBAdapter.KEY_CATEGORY)); for (int i = 0; i < mCategory.getCount(); i++) { String s = (String) mCategory.getItemAtPosition(i); Log.e(null, s + " " + category); if (s.equalsIgnoreCase(category)) { /* * Dependiendo de la categoria que haya sido registrada en la * BD, será entonces que en el Spinner sea seleccionada la misma * opción que se tenga en la BD * */ mCategory.setSelection(i); } } // Rellenamos los otros valores con lo que tengamos en la BD mTitleText.setText(todo.getString(todo .getColumnIndexOrThrow(DBAdapter.KEY_SUMMARY))); mBodyText.setText(todo.getString(todo .getColumnIndexOrThrow(DBAdapter.KEY_DESCRIPTION))); } } /* * En este método lo que se busca es que el id de la tarea sea * guardado con la intención de recuperarlo para cuando la activity * sea recreado * */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //saveState(); outState.putSerializable(DBAdapter.KEY_ROWID, mRowId); } @Override
87
protected void onPause() { super.onPause(); //saveState(); } /* * Cuando la activity sea reiniciada mandará * ejecutar el método que rellena los campos * de la activity */ @Override protected void onResume() { super.onResume(); populateFields(); } /* * Sobreescribimos este método para que al presionar * el btón de back sea ejecutado el método de finish * */ @Override public void onBackPressed() { // TODO Auto-generated method stub super.onBackPressed(); finish(); } /* * Este método trata de guardar la información en la * BD * */ private void saveState() { String category = (String) mCategory.getSelectedItem(); String summary = mTitleText.getText().toString(); String description = mBodyText.getText().toString(); /* * Si la variable mRowId es nula entonces ejecutará el método crearNuevaTarea * para insertar los datos nuevos en la tabla, pero si no es null * entonces se ejecutará el método updateTarea para actualizar la información * de la tarea relacionada con el valor que tenga la variable mRowId * */ if (mRowId == null) { long id = mDbHelper.crearNuevaTarea(category, summary, description); if (id > 0) { mRowId = id;
88
} } else { mDbHelper.updateTarea(mRowId, category, summary, description); } } }
Es momento de realizar otras modificaciones, es turno de TareasFragment.java, el primero y que es muy importante es cambiar la clase de la cual hereda, en este momento debería de estar extendiendo de Fragment, pero ahora vamos a modificarlo por ListFragment esto se debe a que esta clase muestra una lista de elementos que son administrados por un adaptador (como un SimpleCursorAdapter), similar a ListActivity. Proporciona varios métodos para la gestión de una vista de lista, como la devolución de llamada onListItemClick () para gestionar clic eventos sobre la lista, esta es una de las casusas por la que en este caso heredaremos de esta clase (ListFragment). Será necesario que se revise el código que se muestra a continuación pues aquí ya se muestran todas las modificaciones y anexiones que se le hicieron a la clase, para su mayor entendimiento se le agregaron varios comentarios para que sean estudiados.
import android.app.ListFragment; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.AdapterView.AdapterContextMenuInfo; import com.android.utm.todosqlite.database.DBAdapter; public class TareasFragment extends ListFragment { private static final int ACTIVITY_CREATE = 0; private static final int ACTIVITY_EDIT = 1; private static final int DELETE_ID = Menu.FIRST + 1; private DBAdapter dbHelper; private Cursor cursor; public TareasFragment(){} @Override public View onCreateView(LayoutInflater inflater, ViewGroup
89
container, Bundle savedInstanceState) { // TODO Auto-generated method stub View rootView = inflater.inflate(R.layout.activity_tareas, container, false); /** * Se crea una instancia de la clase DBAdapter, * el constructor de la clase mandará a crear la BD si no existe, * en caso de ya existir solo creará la conexión a la BD * */ dbHelper = new DBAdapter(getActivity().getApplicationContext()); // Abre la conexión a la BD dbHelper.open(); return rootView; } /* * Este método nos ayuda a saber cuando la * activity de nuestro fragment ha terminado de * ejecutar su método onCreate()*/ @Override public void onActivityCreated(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onActivityCreated(savedInstanceState); fillData(); registerForContextMenu(getListView()); } /* * Se llama cada vez que se selecciona un elemento en un menú contextual * */ @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case DELETE_ID: AdapterContextMenuInfo info = (AdapterContextMenuInfo) item .getMenuInfo(); dbHelper.deleteTarea(info.id); fillData(); return true; } return super.onContextItemSelected(item); }
90
//ListView y la acción al seleccionar un item /* * Este método se encarga de manejar los eventos de los * "clicks" en cada uno de los items de la lista * */ @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); /* * Al ser seleccionado uno de los elementos de la lista * se procede a armar un Intent que abrirá la activity * DetailsActivity.java para que se editen los valores. * Se manda un parámetro que es el id de la tarea a * editar*/ Intent i = new Intent(getActivity().getApplicationContext(), DetailsActivity.class); i.putExtra(DBAdapter.KEY_ROWID, id); //La actividad retorna un resultado cuando se llama //startActivityForResult startActivityForResult(i, ACTIVITY_EDIT); } /* * Crea los elementos del menú contextual * */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, DELETE_ID, 0, R.string.menu_delete); } @Override public void onDestroy() { super.onDestroy(); if (dbHelper != null) { dbHelper.close(); } } /* * En este método se va a hacer una consulta a la BD en la * cual se van a recuperar todas las tareas que existan en * la BD, y estas luego serán agregadas al ListFragment. * */ private void fillData() { cursor = dbHelper.recuperaTodos(); getActivity().startManagingCursor(cursor);
91
String[] from = new String[] { DBAdapter.KEY_SUMMARY }; int[] to = new int[] { R.id.label }; //Creamos un array adapter para desplegar cada una de las filas SimpleCursorAdapter notes = new SimpleCursorAdapter(getActivity().getApplicationContext(), R.layout.row, cursor, from, to); setListAdapter(notes); } }
Por último hay que realizar algunas adecuaciones a la clase MainActivity.java, estas modificaciones van enfocadas principalmente en agregar la funcionalidad al menú “Insertar”, este menú es el que se encarga de llamar la activity DetailsActivity.java.
Sólo mostraré el método que hay que modificar y los métodos nuevos que irán en esta clase.
@Override public boolean onOptionsItemSelected(MenuItem item) { // Pasar el evento al ActionBarDrawerToggle, si este // regresa true, entonces este manejara el evento del touch // en el icono de la app if (mDrawerToggle.onOptionsItemSelected(item)) { Log.e("mDrawerToggle presionado", "x"); return true; }else{ switch (item.getItemId()) { case R.id.insertar: createTodo(); return true; } } // Maneja los otros items de la action bar... return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.tareas, menu); return true; } // Codificamos la accion al seleccionar el item del menu @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { switch (item.getItemId()) {
92
case R.id.insertar: createTodo(); return true; } return super.onMenuItemSelected(featureId, item); } private void createTodo() { Intent i = new Intent(this, DetailsActivity.class); startActivityForResult(i, ACTIVITY_CREATE); } //El siguiente metodo se llama con el resultado de otra actividad // requestCode es el codigo original que se manda a la actividad // resultCode es el codigo de retorno, 0 significa que todo saliÛ bien // intent es usado para obtener alguna informaciÛn del caller @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); //fillData(); //Cuando la aplicacion cargue por defecto mostrara la opcion Home MostrarFragment(1); }
Trazar una ruta En esta sección agregaremos una opción nueva a nuestro proyecto de ejemplo, en la cual aprovecharemos para explicar como crear pestañas y swipe en la aplicación, además de explicar algunas cosas relativas a los mapas (de Google). Este ejemplo mostrará dos opciones, una será la de mostrar el mapa con la ruta trazada dentro de la aplicación y la otra será lanzando la aplicación de “Maps “ de Google para que nos muestre la ruta, bueno manos a la obra lo primero que hay que hacer es lo que se indica en la sección Pasos para instalar y/o configurar la API de Google Maps Android.
Ahora será necesario agregar los permisos de para poder usar el GPS del celular (para una mayor referencia para el uso y/o configuración del GPS en Android, se debería de estudiar la sección Localización geográfica en Android) .
En este punto ya deberíamos de tener configurado en el Manifest del proyecto todos los permisos para la recepción de mapas, la conexión a internet, así como de tener ya configurada y obtenida nuestra API Key de Google Maps, por lo cual procederemos a solicitar los permisos para poder utilizar la localización
93
con ayuda del GPS o de la red WIFI, por lo que deberemos de agregar los siguientes permisos en la sección de permisos del Manifest:
<!-- permite a la aplicación conocer la ubicación de manera "menos" precisa. Por ejemplo, utilizando WIFI. --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- permite a la aplicación que conozca de manera "precisa" la ubicación. Por ejemplo, utilizando GPS. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
por lo que todo nuestro Manifest quedará como se muestra a continuación:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.utm.todosqlite" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="14" /> <permission android:name="com.android.utm.todosqlite.java.permission.MAPS_RECEIVE" android:protectionLevel="signature" /> <uses-permission android:name="com.android.utm.todosqlite.java.permission.MAPS_RECEIVE" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <!-- permitimos abrir conexiones de red. --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- permite a la aplicación conocer la ubicación de manera "menos" precisa. Por ejemplo, utilizando WIFI. --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
94
<!-- permite a la aplicación que conozca de manera "precisa" la ubicación. Por ejemplo, utilizando GPS. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!—Le indicamos a nuestro celular que vamos a usar la versión OpenGL ES 2.0, esto es necesario para que el celular muestre de forma adecuada los mapas --> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <!—Declaración de la API Key para los mapas--> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="AIzaSyA7h1--xnFvHT1--Y3lTTof2FrtIp7MofY" /> <activity android:name="com.android.utm.todosqlite.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.android.utm.todosqlite.TareasFragment" android:label="@string/title_activity_tareas" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.android.utm.todosqlite.MainActivity" /> </activity> <activity android:name="com.android.utm.todosqlite.DetailsActivity"
95
android:windowSoftInputMode="stateVisible|adjustResize" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.android.utm.todosqlite.TareasFragment" /> </activity> <activity android:name="com.android.utm.todosqlite.TrazarRutaFragment" android:label="@string/title_activity_trazar_ruta" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.android.utm.todosqlite.MainActivity" /> </activity> </application> </manifest>
Como se podrá notar en este momento ya hay varias actividades declaradas pero que aún no las hemos codificado, podemos ir adelantando esto para que no se nos olvide más adelante, o si lo desean al momento que sean declaradas y/o creadas las actividades podríamos venir a esta sección para confirmar la manera en la que se declaran,
Nota
Es momento de recordar que todas las actividades, fragments (pantallas) o cualquier tipo de View que se muestre en la pantalla de nuestro móvil debe de ser declarado en el Manifest de nuestro proyecto, de lo contrario en el momento que deseemos abrir o mostrarla en pantalla la aplicación completa fallará y se cerrará.
En muchas ocasiones es necesario que se declare un nuevo paquete en el cual se colocan ciertas clases que se pueden considerar individuales, puesto que realizan una función en específico, y esta función en muchos de los casos pueden ser usados por una gran cantidad de las otras clases, a este tipo de clases se les suele llamar que son utilerías, y lo único que comparten con algunas otras clases es su característica de ser solas o únicas por lo que se acostumbra crear un paquete con terminación util para nuestro caso será el siguiente: com.android.utm.todosqlite.util (recordemos que estos nombre son los que tengo para mi proyecto, deberían de usar los que tengan para el suyo, no tienen por qué ser iguales), para este proyecto vamos a tener tres clases en este paquete las cuales serán Utils.java, ConnectionDetecter.java y GMapsDirection.java a continuación explicaré a grandes rasgos de que tratan cada una, pero cabe mencionar que este tipo de clases pueden reutilizarse en diferentes proyectos prácticamente sin ningún tipo de modificación salvo pequeñas adecuaciones según las necesidades especificas de algún proyecto.
96
Utils.java De esta clase lo que nos interesa y será lo único que explique serán los métodos AdjustWidthScreen() y AdjustHeightScreen(), el primero de ellos lo que hace es recibir de parámetros el alto, ancho de un elemento view de un layout así como un valor que representa el ancho que deseamos que tenga el objeto con respecto a la pantalla, este valor representa el porcentaje del ancho de la pantalla y también se recibe como parámetro, el último parámetro recibido es el objeto al cual queremos calcular el ancho. El segundo hace lo mismo pero en el alto del elemento recibido como parámetro. Aquí presento el código que tendrá esta clase:
package com.android.utm.todosqlite.util; import android.annotation.TargetApi; import android.os.Build; import android.os.StrictMode; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /** * Class containing some static utility methods. */ public class Utils { public Utils() {}; @TargetApi(11) public static void enableStrictMode() { if (Utils.hasGingerbread()) { StrictMode.ThreadPolicy.Builder threadPolicyBuilder = new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog(); StrictMode.VmPolicy.Builder vmPolicyBuilder = new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog(); StrictMode.setThreadPolicy(threadPolicyBuilder.build()); StrictMode.setVmPolicy(vmPolicyBuilder.build()); } } public static boolean hasFroyo() { // Can use static final constants like FROYO, declared in later versions // of the OS since they are inlined at compile time. This is guaranteed behavior.
97
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; } public static boolean hasGingerbread() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; } public static boolean hasHoneycomb() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; } public static boolean hasHoneycombMR1() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; } /** * Este método nos ayuda a ajustar el ancho * de algunos tipos de elementos pero dado * un porcentaje con relación a la pantalla * */ public void AdjustWidthScreen(int width, int height, int percent, Object theObject) { if (theObject instanceof TextView) { ((TextView) theObject).setWidth((percent * width) / 100); } else if (theObject instanceof LinearLayout) { ((LinearLayout) theObject).setMinimumWidth((percent * width) / 100); } else if (theObject instanceof Button) { ((Button) theObject).setHeight((percent * width) / 100); } else if (theObject instanceof ImageView) { ((ImageView) theObject).getLayoutParams().width = ((percent * width) / 100); } else { return; } return; } /** * Este método nos ayuda a ajustar el alto * de algunos tipos de elementos pero dado * un porcentaje con relación a la pantalla * */ public void AdjustHeightScreen(int width, int height, int
98
percent, Object theObject){ if (theObject instanceof TextView) { ((TextView) theObject).setHeight((percent * height) / 100); } else if (theObject instanceof LinearLayout){ ((LinearLayout) theObject).setMinimumHeight((percent * height) / 100); } else if (theObject instanceof Button){ ((Button) theObject).setHeight((percent * height) / 100); } else if (theObject instanceof ImageView){ ((ImageView) theObject).getLayoutParams().height = (percent * height) / 100; } return; } }
ConnectionDetecter.java Esta clase es muy simple, de lo único que se encarga es de detectar si hay conexión a internet, regresa un valor boolean, si hay conexión retorna true, en caso contrario false, veamos el código de dicha clase.
package com.android.utm.todosqlite.util; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; public class ConnectionDetector { private Context context; public ConnectionDetector(Context context){ this.context = context; } /** * Revisa todos los posibles proveedores de internet * en pocas palabras se encarga de revisar si tenemos * conexión a internet en el dispositivo * **/ public boolean isConnectedToInternet(){ ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
99
NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); return isConnected; } }
GMapsDirection.java Esta clase es un poco más complicada, pues realiza más cosas, en este caso debemos de mencionar y tener mucho cuidado pues esta clase está enfocada en un mapa que está siendo renderizado sobre un Fragment, en el caso de que sea otro tipo de View en el cual se esté mostrando se deberan de tomar las medidas pertinentes para asegurarse que este funcione adecuadamente.
Empezaremos diciendo que en el constructor recibe como parámetro el fragment sobre el cual se está ejecutando o mostrando el mapa y sobre el cual se va a pintar la ruta de nuestro punto de origen a nuestro punto de destino.
En el método getDocument()armamos la url de petición a Google donde le pedimos que nos de las indicaciones de cómo llegar desde nuestro punto de origen hasta nuestro punto de destino, para lo cual necesitamos las coordenadas de inicio y de destino es aquí donde antes de iniciar el proceso asíncrono de consultar a Google, donde hacemos la verificación de conexión a internet con ayuda de la clase ConnectionDetector(), si tenemos conexión iniciamos la consulta, si no, no se realiza la consulta.
Dentro de esta clase tenemos una clase interna que hereda de la clase AsyncTask, la cual recibe la url de petición para Google y entrega un objeto del tipo Document el cual contiene los pasos y coordenadas de los puntos para llegar al destino especificado, ahora en esta otra clase tenemos tres métodos que sobreescribiremos el primero se llama onPreExecute en este método lo único que haremos será iniciar un PogressDialog, este estará visible mientras se ejecuta el proceso de preguntar a Google y de que de su respuesta y así mismo de trazar la ruta sobre el mapa. El segundo método es doInBackGroound, este método recibe como parámetro la url para realizar la consulta a Google, y como resultado de su ejecución dará un objeto del tipo Document el cual contendrá la estructura del documento XML que responde Google y que contiene la información de cómo llegar. Ya por último tenemos al método doPostExecute, este método recibe el objeto Document con la información del XML de respuesta de Google. Otro método que tenemos en esta clase es el método getDirection y este método recibe el obejto Document con toda la información del XML de Google y lo que hace es obtener la información contenida en él para obtener únicamente los puntos con sus coordenadas y que serán puestos en el mapa para representar la ruta, así mismo tenemos los métodos getNodeIndex y decodePoly pero estos métodos son auxiliares del getDirection. Y ya para terminar aquí m uestro la clase completa:
package com.android.utm.todosqlite.util; import java.io.InputStream; import java.util.ArrayList;
100
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import android.app.ProgressDialog; import android.os.AsyncTask; import com.android.utm.todosqlite.TrazarRutaInternaFragment; import com.google.android.gms.maps.model.LatLng; public class GMapsDirection { public final static String MODE_DRIVING = "driving"; public final static String MODE_WALKING = "walking"; private TrazarRutaInternaFragment fragment; public ProgressDialog progress; /** * El constructor recibe como parámetro * el fragment sobre el cual se está ejecutando * o mostrando el mapa y sobre el cual se va a * pintar la ruta de nuestro punto de origen a * nuestro punto de destino * */ public GMapsDirection(TrazarRutaInternaFragment fragment) { this.fragment = fragment; } public void getDocument(LatLng start, LatLng end, String mode) { /** * Armamos la url de petición a Google * donde le pedimos que nos de las indicaciones * de cómo llegar desde nuestro punto de origen * hasta nuestro punto de destino, para lo cual * necesitamos las coordenadas de inicio y de * destino * */ String url = "http://maps.googleapis.com/maps/api/directions/xml?" + "origin=" + start.latitude + "," + start.longitude + "&destination=" + end.latitude + "," + end.longitude
101
+ "&sensor=false&units=metric&mode="+MODE_DRIVING; // Usamos nuestra clase ConnectionDetector para verificar la conectividad a internet ConnectionDetector connectionDetector = new ConnectionDetector(fragment.getActivity()); if(connectionDetector.isConnectedToInternet()){ new RetrieveData().execute(url); } } /** * Clase interna que hereda de AsyncTask la cual recibe * la url de petición para Google y entrega un objeto del * tipo Document el cual contiene los pasos y coordenadas * de los puntos para llegar al destino especificado * */ class RetrieveData extends AsyncTask<String, Void, Document> { /** * En este método lo único que haremos será iniciar * un PogressDialog, este estará visible mientras * se ejecuta el proceso de preguntar a Google y de * que de su respuesta y así mismo de trazar la ruta * sobre el mapa * */ @Override protected void onPreExecute() { progress = new ProgressDialog(fragment.getActivity()); progress.setTitle("Cargando"); progress.setMessage("Por favor, espera..."); progress.setCanceledOnTouchOutside(false); progress.show(); } /** * Este método recibe como parámetro la url para realizar * la consulta a Google, y como resultado de su ejecución * dará un objeto del tipo Document el cual contendrá la * estructura del documento XML que responde Google y que * contiene la información de cómo llegar * */ protected Document doInBackground(String... urls) { try { // Interfaz para un cliente HTTP. HttpClient httpClient = new DefaultHttpClient();
102
// Un contexto para la ejecución de una solicitud. HttpContext localContext = new BasicHttpContext(); // El método POST se utiliza para solicitar que el servidor de origen acepte la entidad incluida en la solicitud HttpPost httpPost = new HttpPost(urls[0]); /** * Los clientes HTTP encapsulan una mezcla heterogénea de objetos * necesarios para ejecutar solicitudes HTTP al manipular las cookies, * autenticación, gestión de la conexión, y otras características. * */ HttpResponse response = httpClient.execute(httpPost, localContext); // Se genera un objeto InputStream donde se almacena la respuesta de Google InputStream in = response.getEntity().getContent(); //Instanciamos la fábrica para DOM y creamos el nuevo parser DOM DocumentBuilder builder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); // Realizamos la lectura completa del XML Document doc = builder.parse(in); return doc; } catch (Exception e) { e.printStackTrace(); } return null; } /** * Este método recibe el objeto Document con la información * del XML de respuesta de Google * */ protected void onPostExecute(Document document) { if(document != null){ /** * Ejecutamos el método de traceRoute * que pertenece a la clase TrazarRutaInternaFragment * y se pasa de parámetro el objeto Document*/ fragment.traceRoute(document); }
103
// Ocultamos el ProgressDialog progress.hide(); } } /** * Este método recibe el obejto Document con toda * la información del XML de Google * y lo que hace es obtener la información contenida * en él para obtener únicamente los puntos con sus * coordenadas y que serán puestos en el mapa para * representar la ruta * */ public ArrayList<LatLng> getDirection(Document doc) { NodeList nl1, nl2, nl3; ArrayList<LatLng> listGeopoints = new ArrayList<LatLng>(); nl1 = doc.getElementsByTagName("step"); if (nl1.getLength() > 0) { for (int i = 0; i < nl1.getLength(); i++) { Node node1 = nl1.item(i); nl2 = node1.getChildNodes(); Node locationNode = nl2 .item(getNodeIndex(nl2, "start_location")); nl3 = locationNode.getChildNodes(); Node latNode = nl3.item(getNodeIndex(nl3, "lat")); double lat = Double.parseDouble(latNode.getTextContent()); Node lngNode = nl3.item(getNodeIndex(nl3, "lng")); double lng = Double.parseDouble(lngNode.getTextContent()); listGeopoints.add(new LatLng(lat, lng)); locationNode = nl2.item(getNodeIndex(nl2, "polyline")); nl3 = locationNode.getChildNodes(); latNode = nl3.item(getNodeIndex(nl3, "points")); ArrayList<LatLng> arr = decodePoly(latNode.getTextContent()); for (int j = 0; j < arr.size(); j++) { listGeopoints.add(new LatLng(arr.get(j).latitude, arr .get(j).longitude)); } locationNode = nl2.item(getNodeIndex(nl2, "end_location")); nl3 = locationNode.getChildNodes();
104
latNode = nl3.item(getNodeIndex(nl3, "lat")); lat = Double.parseDouble(latNode.getTextContent()); lngNode = nl3.item(getNodeIndex(nl3, "lng")); lng = Double.parseDouble(lngNode.getTextContent()); listGeopoints.add(new LatLng(lat, lng)); } } return listGeopoints; } private int getNodeIndex(NodeList nl, String nodename) { for (int i = 0; i < nl.getLength(); i++) { if (nl.item(i).getNodeName().equals(nodename)) return i; } return -1; } private ArrayList<LatLng> decodePoly(String encoded) { ArrayList<LatLng> poly = new ArrayList<LatLng>(); int index = 0, len = encoded.length(); int lat = 0, lng = 0; while (index < len) { int b, shift = 0, result = 0; do { b = encoded.charAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lat += dlat; shift = 0; result = 0; do { b = encoded.charAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lng += dlng; LatLng position = new LatLng((double) lat / 1E5, (double) lng / 1E5); poly.add(position); } return poly;
105
} }
Ahora es momento ya de empezar a trabajar la parte que será visible en nuestra aplicación, empezaremos con el diseño de los layouts de las interfaces de cada uno de los fragments que aparecerán en nuestras pestañas, a continuación muestro una captura de pantalla de cómo se vería nuestra aplicación al terminar:
Lo que vemos es una pantalla que tiene dos tabs, en una tenemos un mapa con una ruta marcada sobre de él, y en la otra pestaña es un pequeño formulario en el cual vemos unos parametros con las posiciones actuales y la de destino y un botón, la pantalla se ve así por que en el momento de tomar la captura de pantalla se está haciendo un swipe entre las pestañas, es decir esta aplicación tiene un ViewPager.
Creación de elementos visuales En esta sección nos dedicaremos a sólo crear todo los layouts y/o archivos de configuración que necesitemos para la creación de nuestra aplicación
Procederemos ahora a crear los diferentes layouts, el primero que crearemos se llamará como_llegar_interna_fragment.xml, dicho layout pertenece al fragment que nos mostrará la ruta de cómo llegar entre dos puntos directamente en el mapa, es muy simple, se ocupa un view o elemento en el cual se muestra un mapa (con los datos obtenidos del servicio de Google Maps). Una vez enfocado, capturará las pulsaciones de teclas y gestos táctiles para mover el mapa. También
106
contiene un ImageView que tiene el atributo visibility en invisible esto es un truco que se necesita para poder hacer swipe entre las pestañas, de no ser así en algunas versiones nuevas de Android mostrará unas secciones en negro al momento de cambiar las pestañas, si su aplicación no ocupa hacer swipe con el mapa, entonces no es necesario que tenga el ImageView. Este es el código:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:map="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- Un view que muestra un mapa (con los datos obtenidos del servicio de Google Maps). Una vez enfocado, capturará las pulsaciones de teclas y gestos táctiles para mover el mapa. --> <com.google.android.gms.maps.MapView android:id="@+id/mapa" android:layout_width="match_parent" android:layout_height="match_parent" map:uiZoomControls="true" /> <!-- Este ImageView tiene el atributo visibility en invisible esto es un truco que se necesita para poder hacer swipe entre las pestañas, de no ser así en algunas versiones nuevas de Android mostrará unas secciones en negro al momento de cambiar las pestañas--> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:visibility="invisible" /> </RelativeLayout>
Para el layout de la otra pestaña que lo que hace es pedir a una aplicación externa que muestre el trazo de la ruta que se desea, para este caso se desea sea mostrada por a aplicación Maps de Google, pero en muchos casos podría pasar que existan varias aplicaciones que puedan abrir la ruta solicitida (es algo complicado especificar una aplicación en especial), en ese caso lo que hace android de forma automática es algo como lo que se muestra en la imagen de siguiente:
107
En este caso ya es decisión personal que opción tomar, pero yo aconsejo hacerlo con la aplicación de Maps, ya que nos dará como resultado lo que vemos en las siguientes imágenes:
108
El segundo layout será demasiado simple pues los elementos que utilizaremos son varios que ya hemos usado varias veces en otros ejemplos, pero aquí dejo el código como ejemplo, el layout se llamará como_llegar_externa_fragment.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="match_parent" android:background="#CECECE" > <TextView android:id="@+id/latitud_origen" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/longitud_origen" android:layout_below="@+id/longitud_origen" android:layout_marginTop="28dp" android:text="Latitud Origen" /> <TextView android:id="@+id/longitud_origen" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginLeft="18dp" android:layout_marginTop="16dp" android:text="Longitud Origen" /> <!-- Aquí se va a mostrar la longitud de origen obtenida del GPS del móvil --> <TextView android:id="@+id/longitud_origen_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/latitud_origen" android:layout_centerHorizontal="true" android:text="TextView" /> <!-- Aquí se va a mostrar la latitud de origen obtenida del GPS del móvil --> <TextView android:id="@+id/latitud_origen_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/latitud_origen" android:layout_alignLeft="@+id/longitud_origen_txt" android:text="TextView" />
109
<TextView android:id="@+id/longitud_destino" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/latitud_origen" android:layout_below="@+id/latitud_origen" android:layout_marginTop="27dp" android:text="Longitud Destino" /> <!-- Aquí se muestra la longitud de destino --> <TextView android:id="@+id/longitud_destino_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/longitud_destino" android:layout_alignBottom="@+id/longitud_destino" android:layout_alignLeft="@+id/latitud_origen_txt" android:text="-99.131111" /> <TextView android:id="@+id/latitud_destino" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/longitud_destino" android:layout_below="@+id/longitud_destino" android:layout_marginTop="24dp" android:text="Latitud Destino" /> <!-- Aquí se muestra la latitud de destino --> <TextView android:id="@+id/latitud_destino_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/latitud_destino" android:layout_alignBottom="@+id/latitud_destino" android:layout_alignLeft="@+id/longitud_destino_txt" android:text="19.4325" /> <!-- Botón que realizará el proceso de armar la URL para solicitar el trazado de la ruta de un punto de origen a uno de destino --> <Button android:id="@+id/btn_trazar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/latitud_destino_txt" android:layout_centerHorizontal="true" android:layout_marginTop="86dp" android:text="Trazar Ruta" /> </RelativeLayout>
110
En nuestro ejemplo las pestañas tendrán dos colores, una para cuando están seleccionados (#558e33), y otro para cuando no lo están (#adab21), es por esta razón que registraremos dos colores en el archivo colors.xml, además también registraremos un color para la línea a trazar de la ruta en el mapa, por lo que nuestro archivo de configuración de colores quedará cómo se muestra en el listado siguiente:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="list_color">#94A65D</color> <color name="negro">#000000</color> <!-- Estos son las tres nuevas definiciones de colores --> <color name="tab_trazar_on">#558e33</color>
<color name="tab_trazar_off">#adab21</color> <color name="route">#65b4f1</color> </resources> Android tiene una forma muy fácil para controlar el estilo de los elementos que deben de mostrar cuando sean tocados, seleccionados o enfocados entre otros. Para lograr que de forma automática se cambién los colores de las pestañas será necesario crear un nuevo archivo xml pero este estará en la carpeta drawable y llevará por nombre tab_selector_trazar.xml, en este archivo se declararán las reglas que se deben de cumplir cada vez que una de las pestañas es tocada o cada vez que su estado cambie ha seleccionado, a continuación se muestra el código de este archivo (en lugar de colores pueden ser definidas imágenes):
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <!-- Active tab --> <!-- Lo que se dice aquí es que cuando el estado de seleccionado de una tab sea verdadero se deberá de aplicar a la pestaña el color definido con el nombre tab_trazar_on --> <item android:drawable="@color/tab_trazar_on" android:state_selected="true"/> <!-- Inactive tab --> <!-- Lo que se dice aquí es que cuando una tab no esté seleccionado se deberá de aplicar a la pestaña el color definido con el nombre tab_trazar_off --> <item android:drawable="@color/tab_trazar_off"/> </selector>
Si no queremos que las pestañas de nuestra aplicación luzcan de la forma por default como en el estilo de Android será necesario que sea creado un layout nuevo en donde se especifique el nuevo estilo o apariencia, como ese es el caso del proyecto que estamos desarrollando entonces será necesario que sea creado un nuevo layout de nombre tab_layout_trazar.xml, este nuevo diseño es muy simple consta de un RelativeLayout al cual le aplicaremos como valor del atributo background el archivo
111
previamente creado, esto hará que si la pestaña está seleccionada tenga un color y si no lo está tenga otro color, el otro elemento que aparecerá es un TextView en el cual aparecerá el texto que deseemos en cada pestaña. Este layout será aplicado a cada una de las pestañas que tengamos en pantalla al momento, este es el código del layout:
<?xml version="1.0" encoding="utf-8"?> <!-- Lo interesante en este caso es la aplicación en el atributo background del archivo tab_selector_trazar.xml para cambiarle los colores según el status de la pestaña --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tab_selector_trazar" > <TextView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFF" android:gravity="center" android:layout_centerInParent="true" /> </RelativeLayout>
Ya definimos como se debe de ver cada una de las pestañas que deberá de tener nuestra aplicación, ahora vamos a definir la estructura completa, ¿esto qué significa?, pues definir mediante un layout la ubicación de donde deben de aparecer las pestañas (tabs), la cantidad de pestañas a mostrar, definir un espacio para que sean mostrados los contenidos de cada pestaña, y en el caso de este ejemplo agregar el elemento de ViePager para poder hacer el movimiento de swipe entre pestañas. Comenzaremos por definir un elemento TabHost el cual es un contenedor para las pestañas en una ventana, este contiene dos hijos, uno es el un conjunto de etiquetas para las pestañas que el usuario hace clic para seleccionar una pestaña específica, el otro hijo es un objeto FrameLayout el cual muestra el contenido de la página. El siguiente elemento importante es el TabWidget, y es aquí donde se muestran las pestañas de nuestro proyecto, en la sección donde salen el FrameLayout es en donde tendremos un FrameLayout para mostrar el contenido de cada layout que deseemos mostrar. A continuación se presenta el código que se utilizaría para nuestro caso, este layout sería un gran ejemplo para otras vistas con pestañas pues debe de llevar todos los elementos que se muestran en el código, el único que no es necesario es el ViewPager pues es un agregado para darle la funcioanlidad del Pager a nuestro ejemplo.
<?xml version="1.0" encoding="utf-8"?> <!-- Contenedor para las pestañas en una ventana, este contiene dos hijos, uno es el un conjunto de etiquetas para las pestañas que el usuario hace clic para seleccionar una pestaña específica, el otro hijo es un objeto FrameLayout el cual muestra el contenido de la página -->
112
<TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tabhost_trazar" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@null" > <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- Aquí será la sección de las pestañas y siempre el id deberá de tener el valor de android:id="@android:id/tabs" --> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="top" android:tabStripEnabled="false" > </TabWidget> <!-- contenedor para el contenido de las pestañas con el id obligatorio "@android:id/tabcontent" --> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="0dp" android:background="@null" > <!-- Por cada pestaña que contenga la vista se deberá de incluir un FrameLayout --> <FrameLayout android:id="@+id/tab_1_interna" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <FrameLayout android:id="@+id/tab_2_externa" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </FrameLayout> <!-- Controlador que que permite al usuario mover de un tirón a la izquierda y derecha a tráves de diferentes ventanas --> <android.support.v4.view.ViewPager android:id="@+id/pager_trazar_ruta" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" />
113
</LinearLayout> </TabHost>
Para terminar con la fase de diseño de interfaz será necesario tener un layout en el cual tengamos un concentrador de todos los demás layouts hechos pues son esos layouts una parte del diseño general dicho layout se llamará fragment_trazar.xml que en verdad es demasiado sencillo su diseño pues solo tiene un LinearLayout como contenedor principal pero es aquí en donde se usa un nuevo elemento, se llama include y de lo que se encarga o para lo que sirve es para incluir un layout dentro de otro sólo es necesario usar el atributo layout y especificarle el nombre del layout a incluir. Para caso de nuestro ejemplo mostramos el siguiente código que ilustrará de mejor manera lo que se dice:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#12ac56" android:orientation="vertical" android:baselineAligned="false" > <!-- El elemento include nos sirve para incluir otros layouts dentro de este sólo basta con indicar el nombre del layout a incluir --> <include android:layout_width="wrap_content" android:layout_height="0dip" android:layout_weight="1" layout="@layout/view_tabs_layout_trazar" /> </LinearLayout> Por fin se ha terminado la fase de diseño de la interfaz es necesario ahora entrar al código en java para poder agregar la funcionalidad necesaria para que nuestra aplicación funcione de la mejor manera posible.
114
Adapters Los Adapters de Android son un puente entre el Adapter View (por ejemplo ListView) y los datos subyacentes para esa vista. Imagínese lo que hubiera sido el mundo sin necesidad de adaptadores!
Sin Adapters, para implementar la funcionalidad del ListView, se habría necesitado de:
• Crear un TextView dentro de un ScrollView. • Tendriamos que poner en práctica el concepto de paginación de los contenidos del TextView. • Tendríamos que escribir código adicional para identificar el evento del “click” en una fila en
particular el TextView
A continuación se muestra un diagrama conceptual de alto nivel que muestra el trabajo del adaptador de Android:
Entender el comportamiento interno de un Android Adapter
Los Adapters llaman al método getView() que devuelve una vista para cada elemento dentro de la vista del adaptador. El método de formato del layout y los datos correspondientes a un elemento dentro de la vista del adaptador se encuentra en el getView(). Ahora, esto sería una pesadillacon el rendimiento si getView() devuelve una nueva vista cada vez que se le llama. Crear una nueva vista sería muy caro en Android, ya que se necesitaría para recorrer la jerarquía de vistas (usando el método ViewbyID() para encontrar) y luego inflar la vista para finalmente mostrarlo en la pantalla. Esto pone mucha presión sobre el recolector de basura. Así que lo que Android hace es que recicla y reutiliza las vistas que están fuera de foco. Abajo se muestra un representación visual de lo que es el proceso de reciclado:
115
En la figura anterior, asumimos que estamos viendo los meses en un año en un ListView. Para empezar, los meses de enero a mayo se muestran en la pantalla. Al desplazarse por la vista, el mes de enero se sale de la zona de visualización de la pantalla del móvil. Tan pronto como la vista de enero sale de la pantalla, el Adapter View (ListView en este caso) envía la vista a algo que se llama un reciclador. Así que cuando se desplaza hacia arriba, el método getView() se llama para obtener el siguiente punto de vista (que es de junio). Este método getView() tiene un parámetro llamado convertview que apunta a la vista sin utilizar en el reciclador. A través del convertview, el adaptador intenta controlar la vista sin usar y reutilizarla para mostrar la nueva vista (que es de junio, en este caso).
Mapas en Android (Google Maps Android API v2) Esta versión presenta muchas novedades interesantes, de las que cabe destacar las siguientes:
• Integración con los Servicios de Google Play (Google Play Services) y la Consola de APIs. • Utilización a través de un nuevo tipo específico de fragment (MapFragment), una mejora muy
esperada por muchos. • Utilización de mapas vectoriales, lo que repercute en una mayor velocidad de carga y una mayor
eficiencia en cuanto a uso de ancho de banda. • Mejoras en el sistema de caché, lo que reducirá en gran medida las famosas áreas en blanco que
tardan en cargar. • Los mapas son ahora 3D, es decir, podremos mover nuestro punto de vista de forma que lo
veamos en perspectiva.
Pasos para instalar y/o configurar la API de Google Maps Android.
1. En primer lugar, dado que la API v2 se proporciona como parte del SDK de Google Play Services, será necesario incorporar previamente a nuestro entorno de desarrollo dicho paquete. Haremos esto accediendo desde Eclipse al Android SDK Manager y descargando del apartado de extras el paquete llamado “Google Play Services”.
116
Imagen 33
Tras pulsar el botón de Install y aceptar la licencia correspondiente el paquete quedará instalado en nuestro sistema, concretamente en la ruta: <carpeta-sdk-
android>/extras/google/google_play_services/. Recordemos esto porque nos hará falta más adelante.
2. El siguiente paso será obtener una API Key para poder utilizar el servicio de mapas de Google en nuestra aplicación.
3. La nueva API de mapas de Android se ha integrado por fin en la Consola de APIs de Google, por lo que el primer paso será acceder a ella.
4. Es probable que si nunca hemos entrado a la consola de APIS de Google nos pida que una pantalla como la que se muestra a continuación, por lo que hacemos click en el botón “Create Proyect…”, en caso de no ser así continuamos con el punto 5 directamente.
117
Imagen 34
5. Una vez hemos accedido, tendremos que crear un nuevo proyecto desplegando el menú superior izquierdo y seleccionando la opción “Create…”.
Imagen 35
6. Aparecerá entonces una ventana que nos solicitará el nombre del proyecto. Introducimos algún nombre descriptivo y aceptamos sin más.
Imagen 36
7. Una vez creado el proyecto, accederemos a la opción “Services” del menú izquierdo. Desde esta ventana podemos activar o desactivar cada uno de los servicios de Google que queremos
118
utilizar. En este caso sólo activaremos el servicio llamado “Google Maps Android API v2″₺ pulsando sobre el botón ON/OFF situado justo a su derecha.
Imagen 37
8. Ahora aceptamos los términos de uso de Google.
Imagen 38
9. Una vez activado aparecerá una nueva opción en el menú izquierdo llamada “API Access”. Accediendo a dicha opción tendremos la posibilidad de obtener nuestra nueva API Key que nos permita utilizar el servicio de mapas desde nuestra aplicación particular.
119
Imagen 39
10. Para ello, pulsaremos el botón “Create new Android key…”. Esto nos llevará a un cuadro de diálogo donde tendremos que introducir algunos datos identificativos de nuestra aplicación. En concreto necesitaremos dos: la huella digital (SHA1) del certificado con el que firmamos la aplicación, y el paquete java utilizado. El segundo no tiene misterio, pero el primero requiere alguna explicación. Toda aplicación Android debe ir firmada para poder ejecutarse en un dispositivo, tanto físico como emulado. Este proceso de firma es uno de los pasos que tenemos que hacer siempre antes de distribuir públicamente una aplicación. Adicionalmentes, durante el desarrollo de la misma, para realizar pruebas y la depuración del código, aunque no seamos conscientes de ello también estamos firmado la aplicación con un “certificado de pruebas”. Podemos saber en qué carpeta de nuestro sistema está almacenado este certificado accediendo desde Eclipse al menú Window/Preferences y accediendo a la sección Android/Build.
120
Imagen 40
11. Como se puede observar, en mi caso el certificado de pruebas está en la ruta “C:\Users\NELSONCROZBY\.android\debug.keystore”. Pues bien, para obtener nuestra huella digital SHA1 deberemos acceder a dicha ruta desde la consola de comando de Windows y ejecutar los siguientes comandos:
C:\>cd C:\Users\NELSONCROZBY\.android\
C:\Users\NELSONCROZBY\.android>"C:\Program Files (x86) \Java \jdk1.7.0_07 \bin\keytool.exe" -list -v -keystore debug.keystore -alias androiddebugkey -storepass android -keypass android
12. Suponiendo que tu instalación de Java está en la ruta “C:\Program Files\Java\jdk1.7.0_07“. Si no es así sólo debes sustituir ésta por la correcta. Esto nos deberá devolver varios datos del certificado, entre ellos la huella SHA1.
121
Imagen 41
13. Pues bien, nos copiamos este dato y lo añadimos a la ventana de obtención de la API Key donde nos habíamos quedado antes, y a continuación separado por un punto y coma añadimos el paquete java que vayamos a utilizar en nuestra aplicación, que en mi caso será “com.demo.proyectodemo″₺.
Imagen 42
14. Pulsamos el botón “Create” y ya deberíamos tener nuestra API Key generada, podremos verla en la pantalla siguiente dentro del apartado “Key for Android Apps (with certificates)”. Apuntaremos también este dato para utilizarlo más tarde.
122
Imagen 43
15. Con esto ya habríamos concluido los preparativos iniciales necesarios para utilizar el servicio de mapas de Android en nuestras propias aplicaciones, por lo que modificaremos nuestro proyecto de ejemplo en Eclipse.
16. lo primero que haremos será añadir al fichero AndroidManifest.xml la API Key que acabamos de generar. Para ello añadiremos al fichero, dentro de la etiqueta <application>, un nuevo elemento <meta-‐data> con los siguientes datos:
Imagen 44
17. Como valor del parámetro android:value tendremos que poner nuestra API Key recién generada. En mi caso mi API Key generada es: AIzaSyDmw0rDncgMS1TbUkaXavswHdZRdMbyw6w (recuerda este valor es para el caso mío, aquí debe de ir valor que a ustedes les genera).
18. Siguiendo con el AndroidManifest, también tendremos que incluir una serie de permisos que nos permitan hacer uso de los mapas. En primer lugar tendremos que definir y utilizar un permiso llamado “tu.paquete.java.permission.MAPS_RECEIVE”, estas etiquetas deben de ser colocadas antes de la etiqueta <application, en mi caso quedaría de la siguiente forma (recordemos y tengamos mucho cuidado de usar el nombre del paquete principal de su proyect:
Imagen 45
19. Además, tendremos que añadir permisos adicionales que nos permitan acceder a los servicios web de Google, a Internet, y al almacenamiento externo del dispositivo (utilizado para la caché de los mapas), estos permisos son colocados inmediatamente después de los anteriores:
123
Imagen 46
20. Por último, dado que la API v2 de Google Maps Android utiliza OpenGL ES versión 2, deberemos especificar también dicho requisito en nuestro AndroidManifest añadiendo un nuevo elemento <uses-feature> inmediatamente después de los permisos anteriores:
Imagen 47
21. Una vez hemos configurado todo lo necesario en el AndroidManifest, y antes de escribir nuestro código, tenemos que seguir añadiendo elementos externos a nuestro proyecto. El primero de ellos será referenciar desde nuestro proyecto la librería con el SDK de Google Play Services que nos descargamos al principio de este tutorial. Para ello, desde Eclipse podemos importar la librería a nuestro conjunto de proyectos mediante la opción de menú “File / Import… / Existing Android Code Into Workspace”. Como ya dijimos este paquete se localiza en la ruta “<carpeta-sdk-android>/extras/google/google_play_services/libproject/google-
play-services_lib“.
Imagen 48
124
22. Tras seleccionar la ruta correcta dejaremos el resto de opciones con sus valores por defecto y pulsaremos Finish para que Eclipse importe esta librería a nuestro conjunto de proyectos.
23. El siguiente paso será referenciar esta librería desde nuestro proyecto de ejemplo. Para ello iremos a sus propiedades pulsando botón derecho / Properties sobre nuestro proyecto y accediendo a la sección Android de las preferencias. En dicha ventana podemos añadir una nueva librería en la sección inferior llamada Library. Cuando pulsamos el botón “Add…” nos aparecerá la librería recién importada y podremos seleccionarla directamente, añadiéndose a nuestra lista de librerías referenciadas por nuestro proyecto.
Imagen 49
24. Como último paso de configuración de nuestro proyecto, si queremos que nuestra aplicación se pueda ejecutar desde versiones “antiguas” de Android (concretamente desde la versión de Android 2.2) deberemos asegurarnos de que nuestro proyecto incluye la librería android-‐support-‐v4.jar, que debería aparecer si desplegamos las sección “Android Dependencies” o la carpeta “lib” de nuestro proyecto.
125
Imagen 50
25. Las versiones más recientes de ADT incluyen por defecto esta librería en nuestros proyectos, pero si no está incluida podríamos hacerlo mediante la opción del menú contextual “Android Tools / Add Support Library…” sobre el proyecto, o bien de forma manual.
Pasos para construir una aplicación básica usando la API de Google Maps
Y con esto hemos terminado de configurar todo lo necesario. Ya podemos escribir nuestro código. Y para este primer ejemplo sobre el tema nos vamos a limitar a mostrar un mapa en la pantalla principal de la aplicación. Posteriormente veremos cómo añadir otras opciones o elementos al mapa.
1. Para esto tendremos simplemente que añadir el control correspondiente al layout de nuestra actividad en este caso es activity_mapa.xml. En el caso de la nueva API v2 este “control” se añadirá en forma de fragment (de ahí que hayamos tenido que incluir la librería android-‐support para poder utilizarlos en versiones de Android anteriores a la 3.0) de un determinado tipo (concretamente de la nueva clase com.google.android.gms.maps.SupportMapFragment), quedando por ejemplo de la siguiente forma (ver Imagen 132):
Imagen 51
Gráficamente el diseño de la interfaz se vería como se muestra en la Imagen 53.
126
Imagen 52
2. Por supuesto, dado que estamos utilizando fragments, la actividad MapasActivity.java también tendrá que extender a FragmentActivity (en vez de simplemente Activity como es lo “normal”). Usaremos también la versión de FragmentActivity incluida en la librería android-‐support para ser compatibles con la mayoría de las versiones Android actuales. Veamos la Imagen 54 para ver cómo queda el código de nuestra activity.
Imagen 53
Con esto, ya podríamos ejecutar y probar nuestra aplicación. En mi caso las pruebas las he realizado sobre un dispositivo físico con Android 2.3.5 ya que por el momento parece haber algunos problemas para hacerlo sobre el emulador. Por tanto tendremos que conectar nuestro dispositivo a la PC mediante el cable de datos e indicar a Eclipse que lo utilice para la ejecución de la aplicación.
127
En la Imagen 55 podemos ver una captura en pantalla de la ejecución del código que previamente hemos creado.
Imagen 54
Interactuando con el Mapa (pasar coordenadas)
En el ejemplo anterior todo lo que hicimos fue lo necesario para configurar la API de Google y poder cargar un mapa en nuestra pantalla, pero ahora lo que se pretende hacer es ya interactuar con el mapa, en qué consistirá esta interacción, pues lo primero será lograr que el mapa se posicione sobre un punto en específico y a este punto le colocaremos unos “markers” personalizado junto a algún tipo de mensaje.
1. Bueno lo primero que haremos será abrir el archivo MapasActivity.java para modificarlo, de ahora en adelante en este ejemplo todo lo que hagamos será en esta clase.
2. Como en este ejemplo será algo demostrativa y simple pondremos las coordenadas de dos puntos de forma manual en unas variables globales del tipo LatLng, en posteriores ejemplos veremos cómo recuperar la posición actual a través del GPS del celular por lo pronto veremos la Imagen 56 y observamos la forma de declarar las coordenadas.
Imagen 55
128
3. Ahora pasaremos a inicializar la variable del tipo GoogleMap declarada y el elemento respectivo del layout. En la siguiente Imagen podemos ver cómo se hace (recuerda que esto se hace en el método onCreate):
Imagen 56
4. Posteriormente agregaremos los markers para cada uno de los puntos que crearemos en el mapa y también pondremos el zoom de inicio sobre el mapa y declararemos un efecto al momento de hacer zoom sobre el mapa, ver Imagen 58 para el código.
Imagen 57
Localización geográfica en Android La localización geográfica en Android es uno de esos servicios que, a pesar de requerir poco código para ponerlos en marcha, no son para nada intuitivos ni fáciles de llegar a comprender por completo. Y esto no es debido al diseño de la plataforma Android en sí, sino a la propia naturaleza de este tipo de servicios. Por un lado, existen multitud de formas de obtener la localización de un dispositivo móvil, aunque la más conocida y popular es la localización por GPS, también es posible obtener la posición de un dispositivo por ejemplo a través de las antenas de telefonía móvil o mediante puntos de acceso Wi-‐Fi cercanos, y todos cada uno de estos mecanismos tiene una precisión, velocidad y consumo de recursos distinto. Por otro lado, el modo de funcionamiento de cada uno de estos mecanismos hace que su utilización desde nuestro código no sea todo lo directa e intuitiva que se desearía. Iremos comentando todo esto a lo largo del documento, pero vayamos paso a paso.
¿Qué mecanismos de localización tenemos disponibles?
Lo primero que debe conocer una aplicación que necesite obtener la localización geográfica es qué mecanismos de localización (proveedores de localización, o location providers) tiene disponibles en el dispositivo. Como ya hemos comentado, los más comunes serán el GPS y la localización mediante la red de telefonía, pero podrían existir otros según el tipo de dispositivo.
129
La forma más sencilla de saber los proveedores disponibles en el dispositivo es mediante una llamada al método getAllProviders() de la clase LocationManager, clase principal en la que nos basaremos siempre a la hora de utilizar la API de localización de Android. Para ello, obtendremos una referencia al location manager llamando a getSystemService(LOCATION_SERVICE), y posteriormente obtendremos la lista de proveedores mediante el método citado para obtener la lista de nombres de los proveedores:
Imagen 58
Una vez obtenida la lista completa de proveedores disponibles podríamos acceder a las propiedades de cualquiera de ellos (precisión, coste, consumo de recursos, o si es capaz de obtener la altitud, la velocidad, etc). Así, podemos obtener una referencia al provider mediante su nombre llamando al método getProvider(nombre) y posteriormente utilizar los métodos disponibles para conocer sus propiedades, por ejemplo getAccuracy() para saber su precisión (tenemos disponibles las constantes Criteria.ACCURACY_FINE para precisión alta, y Criteria.ACCURACY_COARSE para precisión media), supportsAltitude() para saber si obtiene la altitud, o getPowerRequirement() para obtener el nivel de consumo de recursos del proveedor. La lista completa de métodos para obtener las características de un proveedor se puede consultar en la documentación oficial de la clase LocationProvider.
Imagen 59
Al margen de esto, hay que tener en cuenta que la lista de proveedores devuelta por el método getAllProviders() contendrá todos los proveedores de localización conocidos por el dispositivo, incluso si éstos no están permitidos (según los permisos de la aplicación) o no están activados, por lo que esta información puede que no nos sea de mucha ayuda.
¿Qué proveedor de localización es mejor para mi aplicación?
Android proporciona un mecanismo alternativo para obtener los proveedores que cumplen unos determinados requisitos entre todos los disponibles. Para ello nos permite definir un criterio de búsqueda, mediante un objeto de tipo Criteria, en el que podremos indicar las características mínimas del proveedor que necesitamos utilizar (podéis consultar la documentación oficial de la clase Criteria para saber todas las características que podemos definir). Así, por ejemplo, para buscar uno con precisión alta y que nos proporcione la altitud definiríamos el siguiente criterio de búsqueda:
130
Imagen 60
Tras esto, podremos utilizar los métodos getProviders() o getBestProvider() para obtener la lista de proveedores que se ajustan mejor al criterio definido o el proveedor que mejor se ajusta a dicho criterio, respectivamente. Además, ambos métodos reciben un segundo parámetro que indica si queremos que sólo nos devuelvan proveedores que están activados actualmente. Veamos cómo se utilizarían estos métodos:
Imagen 61
Con esto, ya tenemos una forma de seleccionar en cada dispositivo aquel proveedor que mejor se ajusta a nuestras necesidades.
¿Está disponible y activado un proveedor determinado?
Aunque, como ya hemos visto, tenemos la posibilidad de buscar dinámicamente proveedores de localización según un determinado criterio de búsqueda, es bastante común que nuestra aplicación esté diseñada para utilizar uno en concreto, por ejemplo el GPS, y por tanto necesitaremos algún mecanismo para saber si éste está activado o no en el dispositivo. Para esta tarea, la clase LocationManager nos proporciona otro método llamado isProviderEnabled() que nos permite hacer exactamente lo que necesitamos. Para ello, debemos pasarle el nombre del provider que queremos consultar. Para los más comunes tenemos varias constantes ya definidas:
• LocationManager.NETWORK_PROVIDER. Localización por la red de telefonía. • LocationManager.GPS_PROVIDER. Localización por GPS.
De esta forma, si quisiéramos saber si el GPS está habilitado o no en el dispositivo (y actuar en consecuencia), haríamos algo parecido a lo siguiente:
Imagen 62
131
En el código anterior, verificamos si el GPS está activado y en caso negativo mostramos al usuario un mensaje de advertencia. Este mensaje podríamos mostrarlo sencillamente en forma de notificación de tipo toast,
El GPS ya está activado, ¿y ahora qué?
Una vez que sabemos que nuestro proveedor de localización favorito está activado, ya estamos en disposición de intentar obtener nuestra localización actual. Y aquí es donde las cosas empiezan a ser menos intuitivas. Para empezar, en Android no existe ningún método del tipo “obtenerPosiciónActual()“. Obtener la posición a través de un dispositivo de localización como por ejemplo el GPS no es una tarea inmediata, sino que puede requerir de un cierto tiempo de procesamiento y de espera, por lo que no tendría sentido proporcionar un método de ese tipo.
Si buscamos entre los métodos disponibles en la clase LocationManager, lo más parecido que encontramos es un método llamado getLastKnownLocation(String provider), que como se puede suponer por su nombre, nos devuelve la última posición conocida del dispositivo devuelta por un provider determinado. Es importante entender esto: este método NO devuelve la posición actual, este método NO solicita una nueva posición al proveedor de localización, este método se limita a devolver la última posición que se obtuvo a través del proveedor que se le indique como parámetro. Y esta posición se pudo obtener hace pocos segundos, hace días, hace meses, o incluso nunca (si el dispositivo ha estado apagado, si nunca se ha activado el GPS, etc.). Por tanto, cuidado cuando se haga uso de la posición devuelta por el método getLastKnownLocation().
Entonces, ¿de qué forma podemos obtener la posición real actualizada? Pues la forma correcta de proceder va a consistir en algo así como activar el proveedor de localización y suscribirnos a sus notificaciones de cambio de posición. O dicho de otra forma, vamos a suscribirnos al evento que se lanza cada vez que un proveedor recibe nuevos datos sobre la localización actual. Y para ello, vamos a darle previamente unas indicaciones (que no son ordenes) sobre cada cuanto tiempo o cada cuanta distancia recorrida necesitaríamos tener una actualización de la posición.
Todo esto lo vamos a realizar mediante una llamada al método requestLocationUpdates(), al que deberemos pasar 4 parámetros distintos:
• Nombre del proveedor de localización al que nos queremos suscribir. • Tiempo mínimo entre actualizaciones, en milisegundos. • Distancia mínima entre actualizaciones, en metros. • Instancia de un objeto LocationListener, que tendremos que implementar previamente
para definir las acciones a realizar al recibir cada nueva actualización de la posición.
Tanto el tiempo como la distancia entre actualizaciones pueden pasarse con valor 0, lo que indicaría que ese criterio no se tendrá en cuenta a la hora de decidir la frecuencia de actualizaciones. Si ambos valores van a cero, las actualizaciones de posición se recibirán tan pronto y tan frecuentemente como estén disponibles. Además, como ya hemos indicado, es importante comprender que tanto el tiempo como la
132
distancia especificadas se entenderán como simples indicaciones o “pistas” para el proveedor, por lo que puede que no se cumplan de forma estricta.
En cuanto al listener, éste será del tipo LocationListener y contendrá una serie de métodos asociados a los distintos eventos que podemos recibir del proveedor:
• onLocationChanged(location). Lanzado cada vez que se recibe una actualización de la posición.
• onProviderDisabled(provider). Lanzado cuando el proveedor se deshabilita. • onProviderEnabled(provider). Lanzado cuando el proveedor se habilita. • onStatusChanged(provider, status, extras). Lanzado cada vez que el
proveedor cambia su estado, que puede variar entre OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE, AVAILABLE.
Por nuestra parte, tendremos que implementar cada uno de estos métodos para responder a los eventos del proveedor, sobre todo al más interesante, onLocationChanged(), que se ejecutará cada vez que se recibe una nueva localización desde el proveedor. Veamos un ejemplo de cómo implementar un listener de este tipo:
Imagen 63
Como podemos ver, en nuestro caso de ejemplo nos limitamos a mostrar al usuario la información recibida en el evento, bien sea un simple cambio de estado, o una nueva posición, en cuyo caso llamamos al método auxiliar mostrarPosicion().
En el método mostrarPosicion() nos vamos a limitar a mostrar los distintos datos de la posición pasada como parámetro en los controles correspondientes de la interfaz, utilizando para ello los métodos proporcionados por la clase Location. En nuestro caso utilizaremos getLatitude(), getAltitude() y getAccuracy() para obtener la latitud, longitud y precisión respectivamente.
133
Por supuesto, hay otros métodos disponibles en esta clase para obtener la altura, orientación, velocidad, etc… que se pueden consultar en la documentación oficial de la clase Location. Veamos el código:
Imagen 64
¿Por qué comprobamos si la localización recibida es null? Como ya hemos dicho anteriormente, no tenemos mucho control sobre el momento ni la frecuencia con la que vamos a recibir las actualizaciones de posición desde un proveedor, por lo que tampoco estamos seguros de tenerlas disponibles desde un primer momento. Por este motivo, una técnica bastante común es utilizar la posición que devuelve el método getLastKnownLocation() como posición “provisional” de partida y a partir de ahí esperar a recibir la primera actualización a través del LocationListener. Y como también dijimos, la última posición conocida podría no existir en el dispositivo, de ahí que comprobemos si el valor recibido es null. Para entender mejor esto, a continuación tenéis la estructura completa del método que lanzamos al comenzar la recepción de actualizaciones de posición:
134
Imagen 65
Como se puede observar, al comenzar la recepción de posiciones, mostramos en primer lugar la última posición conocida, y posteriormente solicitamos al GPS actualizaciones de posición cada 30 segundos.
En las Imágenes 67 -‐ 69 podemos ver la el diseño gráfico y el archivo xml que utilizaremos para la pantalla de la interfaz gráfica.
135
Imagen 66
Imagen 67
136
Imagen 68
En las siguientes Imágenes 70 -‐ 74 podemos ver el código de los diferentes métodos que conforman nuestra activity TrazarActivity.java.
Imagen 69. Declaración de variables globales.
137
Imagen 70. Método onCreate()
Imagen 71. Métodos trazarRuta() y mostrarDestino()
138
Imagen 72. Método comenzarLocalizacion()
Imagen 73. Métodos mostrarAvisoGpsDeshabilitado() y mostrarPosicion()
139
AsyncTask La clase AsyncTask nos ayuda a los desarrolladores a darle un uso adecuado y más fácil al UI thread de nuestras aplicaciones. Permite ejecutar operaciones en segundo plano y definir los resultados en el UI thread sin tener que quebrarnos la cabeza manipulando threads y handlers.
Para utilizar AsyncTask debemos:
• Crear una subclase de AsyncTask, comúnmente como una clase interna privada dentro de la actividad en la que estemos trabajando.
• Sobreescribir uno o más métodos de AsyncTask para poder realizar el trabajo en segundo plano, además de cualquier otra tarea asociada para poder mostrar alguna actualización en el UI thread.
• Cuando sea necesario, crear una instancia de AsyncTask y llamar al método execute() para que empiece a realizar su trabajo.
Tipos genéricos
AsyncTask utiliza tipos genéricos, por lo que resulta necesario definir tres tipos de datos cada vez que necesitemos crear una instancia de esta clase:
• Parámetros (Params). El tipo de información que se necesita para procesar la tarea. • Progreso (Progress). El tipo de información que se pasa dentro de la tarea para indicar su
progreso. • Resultado (Result). El tipo de información que se pasa cuando la tarea ha sido completada.
Hay que tener en cuenta que existen casos en los que algún tipo genérico no es utilizado por la AsyncTask, para ello utilizamos el tipo Void como se muestra a continuación:
Etapas del ASyncTask
Cada vez que una tarea asíncrona se ejecuta, se pasa a través de 4 etapas:
1. onPreExceute(). Se invoca en el UI thread inmediatamente después de que la tarea es ejecutada. Este paso se utiliza normalmente para configurar la tarea. El ejemplo básico es cuando se muestra una ProgressBar en la interfaz de usuario.
2. doInBackground(Params…). Se invoca en el subproceso en segundo plano inmediatamente después de que onPreExecute() termina de ejecutarse. En esta etapa se calcula el tiempo estimado que tomará la realización de la tarea, que deberá pasarse a la última etapa de todo el proceso. Los parámetros de la tarea asíncrona también se pasan en esta etapa. Dentro de esta etapa podemos utilizar el método publishProgress(Progress…) para publicar una o más unidades
140
de progreso en el UI thread por la ayuda de la siguiente etapa que es onProgressUpdate(Progress…).
3. onProgressUpdate(Progress…). Se invoca en el UI thread después de una llamada a publishProgress(Progress…). El momento de la ejecución es indefinido. Este método es utilizado para mostrar cualquier tipo de progreso en la interfaz de usuario mientras la tarea en segundo plano sigue ejecutándose.
4. onPostExecute(Result…). Se invoca en el UI thread después de que el proceso en segundo plano ha sido terminado. El resultado devuelto por todo el proceso se pasa a este método como parámetro.
Las reglas del juego
Para poder trabajar sin problemas con la clase AsyncTask, existen algunas reglas acerca de los threads que debes tomar en cuenta:
• La instancia de la tarea debe crearse en el UI thread. • El método execute(Params…) debe invocarse en el UI thread. • Los métodos onPreExecute(), onPostExecute(Result), doInBackground(Params…), on
ProgressUpdate(Progress…) no deben llamarse de forma manual. • La tarea debe ser ejecutada sólo una vez (con excepción de que la ejecución corresponda a un
segundo intento).
Navegación Navegación con Back y Up
La navegación consistente es un componente esencial de la experiencia general del usuario. Hay pocas cosas que frustran más a los usuarios de navegación básica que se comporta de maneras contradictorias e inesperadas. Android 3.0 introdujo cambios significativos en el comportamiento global de navegación. Cuidadosamente siguiendo las directrices para atrás (back) y hacia arriba (up) hará que la navegación de sus aplicaciones sea previsible y fiable para los usuarios.
Android 2.3 y anteriores se basó en el sistema de botón Atrás para apoyar la navegación dentro de una aplicación. Con la introducción de las barras de acción en Android 3.0, un segundo mecanismo de navegación apareció: el botón de arriba, que consiste en el icono de la aplicación y un símbolo de intercalación de punto a la izquierda.
141
Up vs. Back
El botón Arriba se utiliza para navegar dentro de una aplicación basada en las relaciones jerárquicas entre pantallas. Por ejemplo, si la pantalla A muestra una lista de elementos, y la selección de un elemento conduce a la pantalla de B (que presenta ese tema con más detalle), a continuación, la pantalla B debe ofrecer un botón Arriba que devuelve a la pantalla A.
Si una pantalla es la más alta en una aplicación (es decir, el hogar de la aplicación), no debe presentar un botón Arriba.
El sistema de botón Atrás se utiliza para navegar, en orden cronológico inverso, a través de la historia de las pantallas que el usuario ha trabajado recientemente. Se basa generalmente en las relaciones temporales entre las pantallas, en lugar de la jerarquía de la aplicación.
Cuando la pantalla vista anteriormente es también el padre jerarquico de la pantalla actual, al pulsar el botón Atrás tiene el mismo resultado que al pulsar un botón Up –esto ocurre comunmente. Sin embargo, al contrario el botón de arriba, lo que garantiza es que el usuario permanezca dentro de su aplicación, el botón Atrás puede devolver al usuario a la pantalla de inicio, o incluso a una aplicación diferente.
El botón Back también es compatible con algunos comportamientos que no están directamente vinculados a la navegación de pantalla a la pantalla:
• Desestima las ventanas flotantes (diálogos, popups) • Desestimar barras de acción contextual, y elimina el resaltado de los elementos seleccionados • Oculta el teclado en pantalla (IME)
142
Navegación dentro de su aplicación
1. Navegar a pantallas con múltiples puntos de entrada
A veces, una pantalla no tiene una posición estricta dentro de la jerarquía de la aplicación, y se puede llegar desde múltiples puntos de entrada-‐tales como una pantalla de configuración que se puede llegar desde cualquier otra pantalla en su aplicación. En este caso, el botón UP debería optar por regresar a la pantalla de referencia, comportándose de forma idéntica al fondo.
2. Cambio de vista en una pantalla
Cambiar las opciones de visualización para la pantalla no cambia el comportamiento de Up o posterior: la pantalla sigue en el mismo lugar dentro de la jerarquía de la aplicación, y no se crea un nuevo historial de navegación.
Ejemplos de tales cambios de vista son:
• Vistas de conmutación utilizando pestañas y / o swipes de izquierda y derecha • Cambiar vistas usando un menú desplegable (también conocido como pestañas colapsadas) • Filtrando un a lista • Ordenando una lista • Cambio de características de visualización (como el zoom)
3. Navegar entre pantallas hermanas
Cuando la aplicación soporta la navegación entre una lista de elementos hacia una vista detallada de uno de esos artículos, a menudo es conveniente que soporte la dirección de navegación a partir de ese punto a otro que le precede o le sigue en la lista. Por ejemplo, en Gmail, es fácil deslizar hacia la izquierda o hacia la derecha a partir de una conversación para ver una más nueva o más antigua en la misma bandeja de entrada. Del mismo modo que al cambiar la vista en una pantalla, como la navegación no cambia el comportamiento de Up o Back.
143
Sin embargo, una notable excepción a esto ocurre cuando se navega entre las vistas de detalle relacionados no ligadas a a referida lista -‐por el ejemplo, cuando se navega en la Play Store entre aplicaciones del mismo desarrollador, o álbumes del mismo artista. En estos casos, después de cada enlace crea la historia, haciendo que el botón Back se desplase por cada pantalla que visualizada anteriormente. Up podría continuar para eludir esas pantallas relacionadas y vaya a la pantalla de contenedores utilizada más recientemente.
144
También se tiene la capacidad de hacer el comportamiento Up aún más inteligente basado en el conocimiento de la vista de detalle. Ampliando el ejemplo de arriba de Play Store, imagine que el usuario ha navegado desde el último libro a los detalles de la adaptación de la película. En ese caso, Up puede volver a un contenedor (Películas), que el usuario no ha navegado través de el.
145
Navegando dentro de la aplicación vía Widgets en la pantalla Home y las Notificaciones.
Puede utilizar los widgets de la pantalla principal o notificaciones para ayudar a los usuarios a navegar directamente a las pantallas mas profunds de la jerarquía de su aplicación. Por ejemplo, la Bandeja de entrada del widget de Gmail y la notificación de nuevo mensaje pueden saltar la pantalla Bandeja de entrada, llevando al usuario directamente a una vista de conversación.
Para ambos de estos casos, manejar el botón UP de la siguiente manera:
• Si la pantalla de destino suele ser accesible desde una pantalla en particular dentro de la aplicación, Up debería navegar a esa pantalla.
• De lo contrario, Up debería ir a la pantalla superior ("Home") de su aplicación.
En el caso del botón Back, se debe hacer la navegación más predecible mediante la inserción en la pila de retroceso de la ruta completa de navegación hacia arriba para la pantalla superior de la aplicación. Esto permite a los usuarios que han olvidado como ellos entraron en la aplicación para navegar a la pantalla superior de la aplicación antes de salir.
146
A modo de ejemplo, el widget de Gmail en la pantalla Inicio tiene un botón para el salto directamente a la ventana de redacción. O la vuelta de la ventana de redacción podría llevar al usuario a la Bandeja de entrada, y desde allí en el botón Back sigue Home.
Notificaciones Indirectas
Cuando una aplicación necesita para presentar información sobre varios eventos a la vez, se puede utilizar una sola notificación que dirige al usuario a una pantalla intersticial. Esta pantalla resume estos eventos, y ofrece caminos para que el usuario sumergirse profundamente en la aplicación. Notificaciones de este estilo se denominan notificaciones indirectas.
A diferencia de las notificaciones estándar (directas), al pulsar Back de la pantalla intersticial de una notificación indirecta devuelve al usuario al punto donde la notificación se desencadenó -‐no se insertan pantallas adicionales en la pila de retroceso. Una vez que el usuario procede en la aplicación desde la pantalla intersticial, Up y Back se comportan como para las notificaciones estándar, tal como se describe más arriba: navegando dentro de la aplicación en lugar de volver al intersticial.
Por ejemplo, supongamos que un usuario de Gmail recibe una notificación indirecta desde Calendario. Al tocar esta notificación se abre la pantalla intersticial, que muestra recordatorios para diferentes eventos. Tocando Back desde la intersticial devuelve al usuario a Gmail. Al tocar en un evento en particular lleva al usuario lejos de la intersticial e ingresando por completo a la aplicación Calendario para mostrar los detalles del evento. A partir de los detalles del evento, arriba y atrás navegar a la vista de nivel superior de Calendario.
147
Notificaciones pop-‐up
Las notificaciones pop-‐up evitan el buzón de notificaciones, en lugar de eso aparecen directamente en frente del usuario. Rara vez se utilizan, y deben reservarse para ocasiones en que se requiere una respuesta oportuna y la interrupción del contexto del usuario si es necesario. Por ejemplo, Talk utiliza este estilo para alertar al usuario de la invitación de un amigo a unirse a un chat de vídeo, ya que esta invitación caducará automáticamente después de unos segundos.
En términos de comportamiento de navegación, las notificaciones emergentes siguen de cerca el comportamiento de la pantalla intersticial de una notificación indirecta. Back descarta la notificación emergente. Si el usuario se desplaza desde el pop-‐up en la aplicación notifica, Up y Back siguen las reglas para las notificaciones estándar, navegando dentro de la aplicación.
148
Navegando entre aplicaciones
Una de las fortalezas fundamentales del sistema Android es la capacidad de las aplicaciones para activarse entre sí, dando al usuario la posibilidad de navegar directamente desde una aplicación a otra. Por ejemplo, una aplicación que necesita capturar una foto puede activar la aplicación de la cámara, que devolverá la foto a la aplicación. Este es un gran beneficio tanto para el desarrollador, que puede aprovechar fácilmente el código de otras aplicaciones, y el usuario que disfruta de una experiencia consistente para las acciones que se realizan normalmente.
Para entender la navegación aplicación a aplicación, es importante para entender el marco de comportamiento de Android que se discute a continuación.
• Actividades, tareas e intents
En Android, una actividad es un componente de aplicación que define una pantalla de información y todas las acciones asociadas que el usuario puede realizar. Las aplicaciones son una colección de actividades, que constan tanto de las actividades que se crean y las que se reusan de otras aplicaciones.
149
Una tarea es la secuencia de actividades de un usuario sigue para lograr una meta. Una sola tarea puede hacer uso de las actividades de una sola aplicación, o puede basarse en las actividades de un número de diferentes aplicaciones.
Un intent es un mecanismo para que una aplicación indique que le gustaría la ayuda de otra aplicación en la realización de una acción. Las actividades de una aplicación pueden indicar a cuales intents pueda responder. Para los intents comunes tales como "Compartir", el usuario puede tener muchas aplicaciones instaladas que pueden cumplir esa petición.
• Ejemplo: navegación entre aplicaciones para apoyar el intercambio
Para comprender cómo las actividades, tareas, y los intents trabajan juntos, considere cómo una aplicación permite a los usuarios compartir contenido utilizando otra aplicación. Por ejemplo, el lanzamiento de la aplicación Play Store desde Home comienza nueva tarea A (ver figura siguiente). Después de navegar a través de la Play Store y tocar un libro promovido para ver sus detalles, el usuario permanece en la misma tarea, que se extiende mediante la adición de actividades. Activando la acción Compartir pide al usuario con un diálogo donde se enumeran cada una de las actividades (de diferentes aplicaciones) que se han registrado para manejar el intent Compartir.
150
Cuando el usuario decide compartir a través de Gmail, se añade la actividad de redacción de Gmail como una continuación de la tarea A-‐ninguna nueva tarea se crea. Si Gmail tenía su propia tarea en funcionamiento en segundo plano, no se vería afectada.
Desde la actividad de redacción, enviando el mensaje o tocando el botón Back, regresa al usuario a la actividad de detalles del libro. Toques posteriores de Back continuan navegando a través de la Play Store, llegando hasta Home.
Sin embargo, al tocar Up en la actividad de redacción, el usuario indica un deseo de permanecer dentro de Gmail. Aparece la actividad lista de conversaciones de Gmail, y una nueva Tarea B se crea para ello. Nuevas tareas siempre están arraigadas a la portada, por lo que tocando Back desde la lista de conversación se vuelve allí.
151
La tarea A persiste en el fondo, y el usuario puede volver a ella más tarde (por ejemplo, a través de la pantalla Recientes). Si Gmail ya tenía su propio tarea funcionando en segundo plano, sería sustituida por la tarea B -‐el contexto previo se abandona en favor de la nueva meta del usuario.
Cuando la aplicación registra manejar los intentos con una actividad profunda dentro de la jerarquía de la aplicación, consulte Navegación en su aplicación a través de widgets de la pantalla principal y Notificaciones para obtener orientación sobre cómo especificar de navegación hacia arriba.
152
Estilos en Android
Dispositivos y Pantallas
Android potencializa a millones de teléfonos, tabletas y otros dispositivos en una amplia variedad de tamaños de pantalla y factores de forma. Al tomar ventaja del sistema de diseño flexible de Android, se pueden crear aplicaciones que se adapten a la escala de grandes tabletas así como también de teléfonos más pequeños.
Sea flexible
Estirar y comprimir sus diseños para adaptarse a diferentes alturas y anchuras.
Optimice los diseños
En los dispositivos más grandes, se debe aprovechar el espacio adicional en pantalla. Cree vistas compuestas que combinan múltiples vistas para revelar más contenido y facilidad de navegación.
153
Activos para todos
Proporcionar recursos para diferentes densidades de pantalla (DPI) para garantizar que su aplicación se vea bien en cualquier dispositivo.
Estrategias
Así que, ¿por dónde empezar cuando se diseña para múltiples pantallas? Un método consiste en trabajar en la norma base (tamaño normal y MDPI) y escalar hacia arriba o hacia abajo para los otros cubos. Otro método consiste en comenzar con el dispositivo con el tamaño de pantalla más grande, y después escalar hacia abajo y averiguar el tipo interfaz de usuario que se necesitara hacer para ajustarlo en las pantallas más pequeñas.
Temas
Los temas son el mecanismo de Android para aplicar un estilo coherente con una aplicación o actividad. El estilo especifica las propiedades visuales de los elementos que componen la interfaz de usuario, como el color, la altura, el relleno y el tamaño de la fuente. Para promover una mayor cohesión entre todas las aplicaciones en la plataforma, Android proporciona dos temas del sistema que usted puede elegir la hora de la creación de aplicaciones:
• Holo Light • Holo Black
La aplicación de estos temas recorren un largo camino para ayudar a construir aplicaciones que se adapten correctamente en el lenguaje visual general de Android.
154
Gmail en Holo Light.
Ajustes en Holo Black.
Elija el tema del sistema que mejor se adapte a las necesidades y la estética de diseño para su aplicación. Si su deseo es tener una mirada más clara para su aplicación, utilizando uno de los temas del sistema como punto de partida para las personalizaciones es una buena idea. Los temas del sistema proporcionan una base sólida sobre la que se puede implementar de forma selectiva sus propios estilismos visuales.
155
Retroalimentación al Toque
Utilizar iluminación y oscurecimiento al responder a los toques, reforzar los comportamientos resultantes de gestos, e indicar qué acciones están activadas y desactivadas.
Sea sensible a toques de una manera suave. Cada vez que un usuario toca un área procesable en su aplicación, hacerles saber que la aplicación está "escuchando", proporcionando una respuesta visual. Que sea sutil sólo un poco más claro o más oscuro que el color intacto. Esto proporciona dos beneficios:
• Chispazos son más agradable que las sacudidas. • La incorporación de su marcan es mucho más fácil porque la retroalimentación táctil de
regeneración funciona con cualquier color que elija.
156
Estados
La mayor parte de los elementos de interfaz de usuario de Android tienen retroalimentación táctil incorporada, incluyendo estados que indican si tocar el elemento tendrá ningún efecto.
Comunicación
Cuando los objetos reaccionan a los gestos más complejos, ayudan a los usuarios a entender cuál será el resultado.
157
En “Recientes”, cuando un usuario inicia a deslizar la miniatura hacia la izquierda o la derecha, comienza a oscurecerse. Esto ayuda al usuario a comprender que el deslizamiento hará que el elemento será removido
Límites Cuando los usuarios intentan desplazarse más allá del principio o al final de un área de desplazamiento, se comunica el límite con una señal visual. Muchos de los widgets de interfaz de usuario de Android desplazables, como listas y listas de la red, tienen retroalimentación de apoyo para el límite integrado. Si usted está construyendo widgets personalizados, tenga retroalimentación del límite en mente y ofrezcala en su aplicación.
Métricas y Rejillas
Los dispositivos varían no sólo en tamaño físico, sino también en la densidad de la pantalla (DPI). Para simplificar la forma de diseñar para múltiples pantallas, pensar en cada dispositivo como caer en un cubo de tamaño y densidad cubo especial:
• Los cubos de tamaño son el hanset (menor que 600 DP) y la tableta (mayor que o igual 600dp).
Si un usuario intenta desplazarse más allá del último panel de la pantalla de inicio, el contenido de la pantalla se inclina hacia la derecha para indicar que una mayor navegación en este sentido no es posible.
158
• Los cubos de densidad son LDPI, MDPI, IPAP, XHDPI, XXHDPI, y XXXHDPI.
Optimizar la interfaz de usuario de la aplicación mediante el diseño de esquemas alternativos para algunos de los diferentes grupos de tamaño, y proporcionar imágenes de mapa de bits alternativos para diferentes cubos de densidad.
Porque es importante que al diseñar e implementar sus diseños para varias densidades, las siguientes instrucciones se refieren a las dimensiones de diseño con mediciones dp en lugar de píxeles.
48dp Rhythm
Los Componentes en la interfazque el usuario puede tocar o manejar son generalmente dispuestas a lo largo de 48dp unidades.
¿Por qué 48dp? En promedio, 48dp se traducen a un tamaño físico de aproximadamente de 9 mm (con cierta variabilidad). Esto es cómodo en la gama de tamaños recomendados (7-‐10 mm) para los objetos de la pantalla táctil y los usuarios serán capaces de forma fiable y precisa apuntarlos con sus dedos.
Si el diseño de los elementos son de al menos 48dp alto y ancho usted puede garantizar que:
• sus objetivos nunca será menor que el tamaño mínimo recomendado de 7mm, independientemente de lo que se muestre en la pantalla.
Consideraciones sobre el espacio Dispositivos varían en la cantidad de píxeles independientes de la densidad (dp) que se pueden mostrar.
159
• usted tiene un buena realción entre la densidad de información general por un lado, y la selección de elementos de la interfaz en el otro.
Cuidado con el hueco El espacio entre cada elemento de la interfaz de usuario es 8dp.
Ejemplos:
160
Tipografía
El lenguaje de diseño de Android se basa en herramientas tipográficas tradicionales como la escala, el espacio, el ritmo y la alineación con una cuadrícula subyacente. La implementación exitosa de estas herramientas es esencial para ayudar a los usuarios a comprender rápidamente una pantalla de información. Para apoyar este uso de la tipografía, desde la versión Ice Cream Sandwich se introduce una nueva familia tipográfica con nombre Roboto, creada específicamente para los requisitos de la interfaz de usuario y las pantallas de alta resolución.
161
El actual marco TextView ofrece Roboto en variantes delgadas, ligeras, regular y negrita, junto con un estilo de cursiva para cada variante. El marco también ofrece la variante Roboto Condensada en pesos regulares y audaces , junto con un estilo de cursiva para cada variante.
Colores de tipo predeterminado La interfaz de usuario de Android utiliza los siguientes estilos de color por defecto: textColorPrimary y textColorSecondary. Para los temas ligeros utiliza textColorPrimaryInverse y textColorSecondaryInverse. Los estilos de color de texto de framework también admiten variantes para el tacto de retroalimentación cuando se utiliza dentro de los elementos de interfaz de usuario.
Escala Tipográfica El contraste en tamaños de letra puede recorrer un largo camino para crear diseños comprensibles. Sin embargo, también muchos tamaños diferentes en la misma interfaz de usuario puede ser un poco incómodo. El framework de Android utiliza el siguiente conjunto limitado de tamaños de letra:
Los usuarios pueden seleccionar un factor de escala de todo el sistema para el texto en aplicación Ajustes. Para apoyar estas funciones de accesibilidad, la tipografía debe especificarse en pixeles de escala independiente (sp) siempre que sea posible. Los diseños que soporten tipografías escalables deben ser probados contra estos valores.
Color
Usar colores primarios para enfasis. Escoja los colores que vayan con su marca y proporcione un buen contraste entre los componentes visuales. Note que el rojo y el verde pueden ser indistinguibles para los daltonicos.
162
Paleta El azul es el color de enfasis estandar en la paleta de colores de Android. Cada color tiene su tono más oscuro correspondiente que puede ser usado como un complemento cuando sea necesario.
Iconografía
Un icono es una gráfica que ocupa una pequeña parte del espacio de la pantalla y proporciona una rápida representación intuitiva de una acción, un estado o una aplicación.
Al diseñar iconos para su aplicación, es importante tener en cuenta que su aplicación se puede instalar en una variedad de dispositivos que ofrecen una amplia gama de densidades de píxeles, como se menciona en la sección Dispositivos y Pantallas. Pero usted puede hacer que sus iconos se vean muy bien en todos los dispositivos, proporcionando a cada icono en varios tamaños. Cuando su aplicación se ejecuta, Android comprueba las características de la pantalla del dispositivo y carga los assets apropiados para la densidad especifica para su aplicación.
163
Dado que va a entregar cada icono en varios tamaños para soportar diferentes densidades, las directrices de diseño que siguen se refieren a las dimensiones del icono en las unidades dp, que se basan en las dimensiones en píxeles de una pantalla de densidad media (MDPI).
Por lo tanto, para crear un icono para diferentes densidades, debe seguir la relación de escala 2:3:4:6:8 entre los cinco densidades primarios (medio, alto, x-‐alta, xx-‐alta y alta, respectivamente-‐xxx). Por ejemplo, considere la posibilidad de que se especifica el tamaño de un icono de lanzador para ser 48x48 dp. Esto significa que la línea de base (MDPI) de activos es de 48x48 píxeles, y el asset de alta densidad (HDPI) debe ser de 1,5 x de la línea base en 72x72 píxeles, y los assets de x-‐high densidad (XHDPI) deben ser 2 veces la línea de base a 96x96 píxeles, y así sucesivamente.
Nota: Android también es compatible con pantallas de baja densidad (LDPI), pero normalmente no es necesario crear assets personalizados en este tamaño porque Android efectivamente reduce sus assets HDPI a la mitad para que coincida con el tamaño esperado.
Lanzador El icono de lanzador es la representación visual de su aplicación en la pantalla de inicio o la pantalla “All Apps”. Dado que el usuario puede cambiar el papel tapiz de la pantalla de inicio, asegúrese de que el icono de lanzador es claramente visible en cualquier tipo de fondo.
Dimensiones y escala
• Iconos del lanzador en un dispositivo móvil deben ser 48x48 dp .
• Iconos del lanzador para su visualización en Google Play debe ser 512x512 píxeles .
164
Dimensiones
• Activo completo, dp 48x48
Estilo
Utilice una silueta distinta. Tridimensional, de frente, con un ligero punto de vista como si se mira desde arriba, de modo que los usuarios perciben una cierta profundidad
165
Action Bar Iconos de la barra de acción son los botones gráficos que representan las acciones más importantes que las personas pueden tomar dentro de su aplicación. Cada uno debe emplear una metáfora simple que representa un concepto único que la mayoría de la gente puede entender de un vistazo.
Glifos pre-‐definidos deberían ser utilizados para ciertas acciones comunes tales como "actualizar" y "compartir".
Dimensiones y escala
• Iconos de la barra de acción para los teléfonos deben ser 32x32 dp .
Área focal y proporciones
• Activo completo, dp 32x32 • Plaza óptico, dp 24x24
Estilo
Pictográfico, plano, no muy detallada, con suaves curvas o formas agudas. Si el gráfico es delgado, girarla 45 ° a la izquierda o la derecha para llenar el espacio focal. El grosor de los trazos y espacios negativos debe ser un mínimo de 2 dp.
Colores
Colores: # 333333 Habilitado: el 60% de opacidad para minusválidos: 30%de opacidad
Colores: # FFFFFF Habilitado: el 80% de opacidad para minusválidos: 30%de opacidad
166
Iconos pequeños/contextuales Dentro del cuerpo de su aplicación, use pequeños iconos para acciones superficiales y/o proporcionar el estado de elementos específicos. Por ejemplo, en la aplicación de Gmail, cada mensaje tiene un icono de estrella que marca el mensaje como importante.
Dimensiones y escala
• Iconos pequeños deben ser 16x16 dp .
Área focal y proporciones • Activo completo, dp 16x16 • Cuadrado óptico, dp 12x12
Estilo
Neutro, plana, y simple. Formas rellenas son más fáciles de ver que los trazos finos. Utilice una sola metáfora visual de manera que un usuario puede reconocer fácilmente y entender su propósito.
167
Iconos de notificación Si su aplicación genera notificaciones, proporcionar un icono que el sistema puede mostrar en la barra de estado cada vez que una nueva notificación está disponible.
Colores
Utilice los colores no neutros con moderación y con un propósito.Por ejemplo, Gmail utiliza amarillo en el icono de estrella para indicar un mensaje marcado como favorito. Si el icono es recurrible, elegir un color que contraste con el fondo.
Dimensiones y escala • Iconos de notificación deben
ser 24x24 dp .
Área focal y proporciones
• Activo completo, dp 24x24 • Cuadrado óptico, dp 22x22
168
Consejos de Diseño Estos son algunos consejos que pueden resultar útiles a medida que crea iconos u otros drawables assets para su aplicación. Estos consejos se presupone que está utilizando programa Adobe ® Photoshop ® o algún similar de edición de imágenes raster y vector.
Utilice formas vectoriales cuando sea posible
Muchos programas de edición de imágenes como Adobe ® Photoshop ® le permiten utilizar una combinación de formas vectoriales y capas raster y efectos. Cuando sea posible, utilice formas vectoriales para que en caso de necesidad, los activos pueden ser ampliadas sin pérdida de detalle y nitidez de borde.
Estilo
Mantener el estilo llano y simple, utilizando el mismo single, metáfora visual como su icono de lanzador.
Colores
Iconos de notificación deben ser totalmente blanco. Además, el sistema puede escalar hacia abajo y / u oscurecer los iconos.
169
El uso de vectores también hace que sea fácil para alinear los bordes y esquinas a píxel límites a resoluciones menores.
Comience con grandes mesas de trabajo
Debido a que usted tendrá que crear assets para diferentes densidades de pantalla, lo mejor es empezar sus diseños de iconos en grandes mesas de trabajo con dimensiones que son múltiplos de los tamaños de los iconos objetivo. Por ejemplo, los iconos del lanzador son 48, 72, 96, o 144 píxeles de ancho, dependiendo de la densidad de la pantalla (mdpi, hdpi, xhdpi y xxhdpi, respectivamente). Si dibuja inicialmente iconos de lanzadores en una mesa de trabajo 864x864, será más fácil y más limpio para ajustar los iconos cuando se escala la mesa de trabajo hasta los tamaños de destino para la creación de activos final.
Al escalar, vuelva a dibujar las capas de mapa de bits, según sea necesario
Si escalas una imagen a partir de una capa de mapa de bits, en lugar de partir de una capa vectorial, tendrán que volver a dibujar manualmente a aparecer nítidas a mayores densidades esas capas. Por ejemplo, si un círculo 60x60 fue pintado como un mapa de bits para MDPI que tendrá que volver a pintar como un círculo 90x90 para HDPI.
Use convenciones de nombres comunes para los activos icono
Trate de nombrar archivos para que los assets relacionados se agrupan dentro de un directorio al que estén ordenados alfabéticamente. En particular, ayuda a utilizar un prefijo común para cada tipo de icono. Por ejemplo:
Tipo de activo Prefijo Ejemplo
Iconos ic_ ic_star.png
Iconos del Lanzador ic_launcher ic_launcher_calendar.png
Iconos de menú y barra de acción iconos ic_menu ic_menu_archive.png
Iconos de la barra de estado ic_stat_notify ic_stat_notify_msg.png
Iconos Tab ic_tab ic_tab_recent.png
Iconos de diálogo ic_dialog ic_dialog_info.png
Tenga en cuenta que no está obligado a usar un prefijo común de cualquier tipo-‐hacerlo es sólo para su conveniencia.
170
Crear un espacio de trabajo que organiza los archivos por la densidad
Para soportar multiples densidades de pantalla significa que se deben crear multiples versiones del mismo icono. Para ayudar a mantener las múltiples copias de los archivos seguros y fáciles de encontrar, se recomienda crear una estructura de directorios en tu espacio de trabajo que organiza los archivos assets basados en la densidad objetivo
ejemplo:
art/... mdpi/... _pre_production/... working_file . psd finished_asset . png hdpi/... _pre_production/... working_file . psd finished_asset . png xhdpi/... _pre_production/... working_file . psd finished_asset . png
xxhdpi/... _pre_production/... working_file.psd finished_asset.png
Debido a que la estructura del espacio de trabajo es similar a la de la aplicación, se puede determinar rápidamente qué assets deben copiarse en el directorio cada recursos. La separación de los activos por la densidad también le ayuda a detectar cualquier variación en los nombres de archivos a través de las densidades, lo cual es importante porque los activos correspondientes a diferentes densidades debe compartir el mismo nombre de archivo.
171
Para comparar, aquí está la estructura de directorio de recursos de una aplicación típica:
res/... drawable- ldpi/... finished_asset . png drawable- mdpi/... finished_asset . png drawable- hdpi/... finished_asset . png drawable- xhdpi/... finished_asset . png
Su imagen de marca
Siguiendo el patrón de diseño en Android no significa que tu aplicación tenga la misma apariencia que la de las aplicaciones de los demás. En Android, tu aplicación puede brillar como una extensión de tu marca.
Color Usa el color de tu marca para “Acentuar”, sobrescribiendo el marco azul por defecto de los elementos de la Interfaz de Usuario (UI) como, barras de progreso, botones radio, diapositivas, pestañas e indicadores de scroll.
Busca la oportunidad de usar colores de contraste-‐alto para enfatizar, por ejemplo, el color de fondo de la barra de acción, o un botón primario. Pero no te excedas: no todas las acciones son las mismas, así que úsalo solamente para una o dos cosas importantes.
Cuando se personalizan los colores, la retroalimentación táctil debe de ser sutil-‐ solo ligeramente mas claro u obscuro que el color sin tocar.
Los cuatro colores del Logo de la Cartera de Google, proporcionan una acentuación agradable, la cual consiste en cuatro puntos que aparecen mientras el usuario introduce un PIN.
172
Logo El lanzador del icono de tu aplicación es la llave para incorporar tu logo, por que es lo que los usuarios buscaran y tocaran al empezar a utilizar tu aplicación. Tu puedes mover tu icono a través de todas las pantallas en tu aplicación, mostrando en la barra de acción el nombre de la aplicación.
Otra consideración a tomar en cuenta es colocar el logo en tu icono, así como el nombre de la aplicación en la barra de acción.
La Aplicación de Google Play Music, tiene un tema naranja, el cual es utilizado para enfatizar la barra de acción y para acentuar la pestaña seleccionada, el indicador scroll así como los hipervínculos.
�Google +, refuerza su marca colocando su icono a través de la barra de acción.
173
Iconos Si cuentas con iconos que ya estas usando para tu aplicación en otras plataformas y estos a su vez tienen un look distintivo de acuerdo a tu marca, los puedes utilizar también en tu aplicación Android. Si se toma en cuenta esta consideración, tiene uno que estar seguro que el estilo de tu marca es aplicado a cada icono en tu aplicación.
Excepción: para cualquier icono en tu juego existente de iconos donde la simbología es manejada diferente a como se maneja en Android, usa los símbolos de Android para darle estilo a tu marca. De esa forma, el usuario entenderá que propósito tiene el icono. Aun así, el icono se vera como si perteneciera a tu conjunto de iconos como parte de tu marca.
Ejemplo:
El icono normal para compartir con otras plataformas es la flecha hacia la derecha
Pero, ¿qué pasaría si no tienes aun tus propios iconos-‐ por ejemplo, si estas creando una nueva aplicación solo para Android?. En este caso, usa los iconos estándar de Android.
Ejemplo del logo en la barra de acción. Esto funciona bien en los casos cuando el logo corresponde con el nombre de la aplicación.
174
Estilos de Escritura.
La voz de Android. Al escribir el texto que aparece en su aplicación, que sea concisa, simple y amigable.
Conciso:
• Describa sólo lo que el usuario necesita saber.
• Eliminar la redundancia, como los títulos que reiteran el cuerpo de un cuadro de información.
• Mantener texto tan corto como sea posible.
Evite muchas palabras, texto abultado:
No hacer:
Consulte la documentación que viene con su teléfono para futuras instrucciones.
Hacer:
Lea las instrucciones que vienen con el teléfono.
No proporcione información innecesaria
Desde una pantalla de Asistente de configuración
Accediendo ...
El teléfono necesita comunicarse con los servidores de Google para iniciar sesión en su cuenta. Esto puede tardar hasta cinco minutos.
175
Desde una pantalla de Asistente de configuración
Accediendo ...
El teléfono está en contacto con Google. Esto puede tardar hasta 5 minutos.
Simple
• Utilice palabras cortas, verbos activos, y los nombres comunes.
• Poner primero lo más importante. "Carga frontal" los primeros 11 caracteres con la información más relevante en la cadena.
• No trate de explicar las diferencias sutiles. Se pierden en la mayoría de los usuarios.
Centrarse en la preocupación del usuario, no los detalles técnicos
No hacer
Controlar manualmente el GPS para evitar que otras aplicaciones lo usen
Hacer
Para ahorrar energía, apague el modo de ahorro de batería Ubicación
Coloque las principales noticias primero
No hacer
Otras 77 personas han hecho +1 en esto, incluyendo Larry Page
Hacer
Larry Page y otros 76 han hecho +1 en esto
Ponga la meta del usuario primero
176
No hacer
Toque Siguiente para completar la configuración mediante una conexión Wi-‐Fi
Hacer
Para finalizar la configuración a través de Wi-‐Fi, toque Siguiente
Amistoso
• Utilice contracciones.
• Hablar directamente con el lector. Utilice "usted" para referirse al lector.
• Mantenga su tono informal y coloquial, pero evite el argot.
Evite ser confuso o molesto
No hacer
¡Lo siento!
La Actividad MyAppActivity (en la aplicación MyApp) no está respondiendo
Hacer
MyApp no responde
¿Desea cerrarla?
Palabras a evitar
No utilice Use
uno, dos, tres, cuatro, ... 1, 2, 3, 4, ...
177
Aplicación app
okay, ok, OK
por favor, lo siento, gracias Los intentos de cortesía pueden molestar al usuario, especialmente en los mensajes que dicen que algo ha ido mal. Excepción: En japonés, "por favor" es obligatorio y verbos imperativos deben ser localizadas en consecuencia (encender -‐> por favor encienda).
hay, es y otros temas "desaparecidos" (improperios gramaticales)
Utilice un sustantivo como sujeto
abortar, eliminar, dar por terminado detener, cancelar, finalizar, salir
fallar, fracasado, un lenguaje negativo En general, usar una frase positiva (por ejemplo, "hacer" en lugar de "no hacer", salvo en casos tales como "No mostrar de nuevo", "No se puede conectar", y así sucesivamente.)
yo, mi, mío usted, su, el suyo
¿Está seguro? ¡Advertencia! En lugar de eso encomendar a los usuarios la consecuencia, por ejemplo, "Perderá todas sus fotos y los medios"
De formato al texto
Capitalización
• Utilice mayúsculas al estilo de oración para todas las cadenas de la IU: "Palabras para vivir."
• Utilizar mayúsculas en todas las palabras importantes en:
o Nombres de la aplicación (Calendar, Google Drive)
o Características con nombre (Android Beam, Face Unlock)
o Los nombres propios (Estatua de la Libertad, San Francisco Giants)
• Sea conservador. No utilice mayúsculas en palabras que no son parte de un nombre rasgo formal:
178
o Bloqueo de tarjeta SIM, Pantalla de Inicio, no Sim Card Lock.
Puntuación
• Período. No utilice un período después de una sola oración o frase que se usa de manera aislada, como en un brindis, una etiqueta o notificación. Dondequiera que dos o más oraciones corren juntas, utilice un punto para cada frase.
• . Elipsis Utilice el carácter de puntos suspensivos (...) (Opción-‐; en MacOS y ... en HTML) para indicar
o Incompletitud, como una acción en curso ("Downloading...") o el texto truncado.
Que un elemento del menú (como impresión... o Compartir...) conduce a una mayor UI implica decisiones importantes. Excepción: Los comandos cuya redacción ya implica más (pero limitado) interfaz de usuario, tales como Buscar en la página o Elija una fecha, no requieren