PMYDM

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 246

DAM

PMYDM
PROGRAMACIÓN MULTIMEDIA Y DISPOSITIVOS
MÓVILES

Tema 1. Análisis de tecnologías para aplicaciones en dispositivos


móviles
Introducción. ¿Qué es un dispositivo móvil? Algunas de estas restricciones son:

Clasificación de los dispositivos móviles. • Suministro de energía limitado (normalmente


¿Qué tipos de dispositivos móviles existen? dependiente de baterías).
• Procesadores con capacidad de cómputo reducida. Suelen
Entre los dispositivos móviles más habituales se encuentran:
tener una baja frecuencia de reloj por la necesidad de

• SmartPhones o "teléfonos inteligentes" es un tipo de ahorrar energía. En algunos casos, por ejemplo, podrían no

teléfono móvil construido sobre una plataforma disponer de la capacidad de cálculos en punto flotante.

informática móvil, con mayor capacidad de almacenar • Poca memoria principal (RAM).
datos y realizar actividades, semejante a la de una • Almacenamiento de datos persistente reducido (pequeña
minicomputadora, y con una mayor conectividad que un memoria flash interna, tarjetas SD, etc.).
teléfono móvil convencional. El término inteligente, que se • Conexión a algún tipo de red intermitente y con ancho de
utiliza con fines comerciales, hace referencia a la banda limitado.
capacidad de usarse como un computador de bolsillo, y • Pantallas de reducidas dimensiones.
llega incluso a reemplazar a una computadora personal
• Teclados con funcionalidad muy básica y muy pequeños.
en algunos casos.
• PDA (Personal Digital Assistant - asistentes digitales Este tipo de restricciones, y algunas otras que dependerán de cada
personales) o PocketPC (PC de bolsillo). dispositivo en concreto, habrán de ser tenidas muy en cuenta a la

• Handheld, o PCs de mano. hora del análisis y diseño de una aplicación "móvil", pues no podemos
pretender, que esa aplicación pueda contener la misma
• Internet tables, que se encontrarían entre las PDA y
funcionalidad, que la que podemos encontrar habitualmente en un
los PC Ultramóviles (pequeños tablet PC).
programa que es ejecutado en un ordenador de sobremesa o un
Según las fuentes que consultes puedes encontrar diversas portátil.
clasificaciones donde se incluyan unos u otros tipos de dispositivos,
Por otro lado, no todo va a ser restricciones. También habrá que
como por ejemplo:
tener en consideración que esta tecnología va a aportar una serie
• Pagers (o buscapersonas), hoy día ya en desuso. de ventajas muy importantes: movilidad, poco peso, pequeño

• Navegadores GPS. tamaño, facilidad para el transporte, conectividad a diversos tipos


de redes de comunicaciones (mensajería SMS y MMS; voz;
• E-Readers (lectores de libros digitales).
Internet; Bluetooth; infrarrojos; radiofrecuencia, etc.). Ésas serán las
• Pequeñas videoconsolas de mano.
ventajas que podrás explotar en tus aplicaciones.
• Cámaras digitales.
• Calculadoras programables. Tecnologías disponibles
• Consolas portátiles. CUANDO VAS A DESARROLLAR UNA APLICACIÓN PARA UN DISPOSITIVO MÓVIL,
• Relojes inteligentes. ALGUNAS DE LAS PRIMERAS PREGUNTAS QUE TE PUEDES HACER SON :

• Portatiles enfocados a la movilidad y de gran autonomía,


- ¿SOBRE QUÉ TIPOS DE DISPOSITIVOS MÓVILES SE PUEDEN HACER
pesos cercanos al kilo y hasta 20 horas de autonomía,
PROGRAMAS? ¿SOBRE QUÉ TIPO DE HARDWARE SE PUEDE PROGRAMAR?
basados en tecnología móvil mayormente.
- ¿QUÉ SISTEMA OPERATIVO PUEDE LLEVAR ESE HARDWARE?
En nuestro caso, los principales aparatos a los que nos referiremos - ¿QUÉ PLATAFORMAS DE DESARROLLO EXISTEN PARA

al hablar de dispositivos móviles serán los smartPhones y DESARROLLAR SOBRE ESE HARDWARE Y ESE SISTEMA OPERATIVO? ¿CON QUÉ

las tablets. Por otro lado, según la tecnología vaya avanzando te LENGUAJES PUEDO PROGRAMAR? ¿QUÉ HERRAMIENTAS (COMPILADORES,
podrás ir encontrando con nuevos tipos de productos y servicios que BIBLIOTECAS, ENTORNOS, ETC.) HAY DISPONIBLES?

irán ampliando las posibilidades de elección. Como siempre sucede


Las respuestas a este tipo de preguntas pueden ser múltiples y
en el mundo de la tecnología habrá que estar continuamente al día
muy variadas:
de los nuevos avances que van apareciendo para no quedarse atrás.

• Respecto al hardware, te puedes encontrar, como has


Limitaciones de las tecnologías móviles
visto ya, con teléfonos móviles (smartphones), PDA y con
Antes de comenzar a desarrollar software para alguno de estos
otros dispositivos. Entre los principales fabricantes de
dispositivos, es necesario ser conscientes de las limitaciones con las
teléfonos móviles en el 2020 se
que nos podemos encontrar en estos aparatos. ¿Cuáles son las
encuentran Samsung , Xiaomi, Huawei, Apple, Motorola,
restricciones a las que nos vamos a tener que enfrentar?
Oppo, Vivo y el resto con un 20% de cuota. Entre los
principales fabricantes de tabletas se encuentran Apple,
Samsung, Huawei. En realidad muchas veces coinciden los • Acceso a Internet vía Wi-Fi o redes 4G, 3G o 2G.
proveedores que desarrollan smartphones con los que • Conectividad Wi-Fi, Bluetooth, etc.
fabrican tablets. Por otro lado, ambos tipos de productos
• Posibilidad de conexión con un ordenador para cargar y
están en muchas ocasiones convergiendo hacia un único
descargar información. Normalmente con conexión USB o
dispositivo final que cumple las dos funciones (telefonía
bien una conexión inalámbrica.
móvil y asistente personal), de ahí el aumento de pantalla
• Posibilidad de ampliación de memoria mediante tarjetas
en los últimos años y portátiles de peso reducido y gran
externas de memoria (por ejemplo SD).
autonomía.
• Pantallas de tamaño medio tendiendo a crecer con alta
• En cuanto a los sistemas operativos, dependiendo
resolución y/o con millones de colores (HD en gama baja,
del hardware habrá sistemas diseñados para unos u
Full HD en gama media y a veces QHD en gama alta). La
otros dispositivos. Los hay basados en Microsoft
tasa de refresco habitual era 60 Hz, pero empieza a ser
Windows, en Linux, y en MAC OS X, así como otros
una estándar en gamas altas y medias 90Hz, 120 Hz y
totalmente originales y desarrollados específicamente
subiendo.
para estos nuevos tipos de dispositivos. Entre los más
populares se encuentran Symbian
• Pantallas multitáctiles (multitouch) y con detección de
presión. La tendencia es subir la frecuencia de adquisición
OS, Android, iOS, Blackberry OS y Windows Phone.
de las pulsaciones, llegando a varios cientos de Hz.
• Si lo que deseas es conocer algo acerca de las plataformas
de desarrollo disponibles para cada entorno (hardware y/o
• Pequeñas pantallas pero de alta resolución y/o con
millones de colores.
sistema operativo), podemos hablar de Android SDK,
iOS SDK, Unity, o bien de IDE como Microsoft Visual • Posibilidad de pantallas táctiles o incluso multitáctiles

Studio, CodeWarrior, Eclipse, Netbeans, o hechas a medida (multitouch).

como Android Studio. • Sensores (de orientación, GPS y algunos programas de

• Si te refieres a lenguajes de programación, normalmente navegación, de temperatura, de presión, acelerómetros,

te encontrarás con lenguajes que son ya viejos conocidos magnetómetros, etc.).

para otras plataformas, como pueden ser las aplicaciones • Función multimedia (cámara digitales integradas y
de escritorio para los PCs o las aplicaciones web (Java, C#, reproductor de videos/mp3), Capacidades fotográficas.
C, etc.). Grabación de audio y vídeo.
• Receptor GPS.
En definitiva puedes observar que en este nuevo mundo del
• Receptor de radio FM.
desarrollo para dispositivos móviles te encuentras con una
problemática similar a la que te puedes enfrentar con los
• Emisor de radio FM.

ordenadores convencionales: distintos tipos de hardware, distintas • Posibilidad de instalar y ejecutar aplicaciones sofisticadas:
opciones de sistemas operativos dependiendo del hardware que los
o Aplicaciones de asistente personal (gestión de
soporte, diferentes lenguajes de programación, plataformas, API y
contactos, calendarios, citas, agendas, alarmas,
bibliotecas, entornos de desarrollo, etc.
etc.).

Hardware o Gestión del correo electrónico.


o Gestión del sistema archivos del dispositivo.
Como has visto en los apartados anteriores, dependiendo de los
o Microaplicaciones de ofimática (procesador de
criterios que se utilicen para clasificar los dispositivos móviles se
textos, hoja de cálculo, etc.), así como
puede hablar de más o menos tipos. Este curso se va a centrar
ocasionalmente la habilidad de y leer
sobre todo en smartPhones y PDA.
documentos de negocios en variedad de
Smartphones formatos como PDF y Microsoft Office.

Se puede definir un smartPhone o "teléfono inteligente" como un


o Aplicaciones multimedia (reproducción de audio
y vídeo en diversos formatos).
terminal de telefonía móvil que proporciona unas prestaciones y
una funcionalidad mayor que la que podría ofrecer un teléfono móvil
o Aplicaciones de cartografía y navegación.

normal. Hoy día una buena parte de los teléfonos móviles que se
o Diccionarios.

pueden adquirir en el mercado son de este tipo.


o Pequeñas aplicaciones científicas (matemáticas,
física, medicina, etc.).
Este tipo de terminales se caracteriza por tener instalado un o Juegos.
sistema operativo y por tanto la posibilidad de ejecutar aplicaciones o Aplicaciones de mensajería instantánea. Chats.
desarrolladas bien por el propio fabricante del terminal, bien por el
Puedes observar que aunque el dispositivo es un teléfono móvil
operador de telefonía móvil, o bien por un tercero (empresa de
muchas veces su uso principal no va a ser necesariamente el de un
desarrollo de software).
teléfono (hacer y recibir llamadas) sino que podrá estar dedicado a
Algunas otras características que suelen tener este tipo de muchos otros usos (hacer fotos, navegar por Internet, reproducir
dispositivos son: archivos de audio, jugar, gestionar la agenda personal, consultar un
mapa, usar un diccionario, escuchar la radio, ver una película, trazar
• Funcionamiento en multitarea (ejecución concurrente de
una ruta para el navegador por satélite, etc.).
varios procesos en el sistema operativo).
En la actualidad se ha dejado de emplear el término smartphone o Platform, de la empresa Texas Instrument), y las de la
"móvil inteligente" pues casi todos los terminales móviles son de empresa Marvell.
este tipo y se habla simplemente de "teléfonos móviles". De hecho
en la práctica ya ocurre hoy día, pues cuando nos acercamos a algún
PDA.
catálogo de productos la inmensa mayoría de terminales que se nos UNA PDA (PERSONAL DIGITAL ASSISTANT O ASISTENTE DIGITAL PERSONAL) U
ofrecen son de este tipo. ORDENADOR DE BOLSILLO O POCKET PC O PALMTOP COMPUTER ES UN
DISPOSITIVO MÓVIL CUYA FUNCIONALIDAD ORIGINAL ERA LA DE GESTIÓN DE
Debes conocer
DATOS PERSONALES. ES DECIR, ALGO ASÍ COMO UNA AGENDA DIGITAL MUY
SOFISTICADA, CON PEQUEÑAS APLICACIONES DE ASISTENTE PERSONAL,
Fabricantes de smartPhones
CALENDARIOS, MICRO-OFIMÁTICA, ETC.
Algunos ejemplos de smartPhones son:
Algunas de las primeras PDA que aparecieron en la segunda mitad
• Teléfonos Nokia de la serie N y de la serie E. de la década de los noventa y que se hicieron muy populares fueron
• Los iPhone de Apple. las Palm Pilot de la compañía Palm y las iPAQ de la

• La serie Xperia de Sony Ericsson. empresa Compaq.

• Los teléfonos HTC de las series A (con sistema Hoy día se han convertido en pequeños ordenadores "de mano"
operativo Android), S (con sistema operativo Windows ("palmtop") o "de bosillo" ("pocket") capaces de realizar muchas de las
Phone), T (Windows Phone con Touch), funciones que realizaría un ordenador personal convencional tales
o P (teléfonos PDA). como crear documentos con un procesador de textos, manejar hojas
• Las series Galaxy y Nexus de Samsung (con Android). de calculo, jugar, navegar por Internet, enviar y recibir correo
• En general cualquier fabricante de terminales de telefonía electrónico, transferir archivos por una red local, ver películas,
móvil actual desarrolla smartPhones. reproducir archivos de audio o vídeo, etc. Es decir, que mientras que
un ordenador convencional de escritorio ("desktop") se coloca sobre
El interior de un smartphone. la mesa ("desk"), el portátil ("laptop") es un ordenador que puedes
Si se te ocurriera abrir un teléfono móvil actual te encontraríamos poner sobre el regazo ("lap"), y un ordenador de mano ("palmtop") lo
una placa de circuito impreso con una serie de dispositivos puedes situar en la palma de la mano ("palm").
electrónicos integrados o insertos en ella. En cierto modo podría
recordar un poco a la placa base de un PC. Entre los dispositivos que Una de sus principales características externas es su pantalla

podrías observar se encontrarían el procesador principal, táctil y el lápiz-marcador (o estilete digital) para interactuar con

procesadores digitales de señal (DSP), procesadores de imagen y de ella. Debido a esto suelen tener pocos botones pues la mayoría de

audio, módem, memorias caché, etc. A este conjunto de dispositivos las tareas se pueden realizar mediante el estilete sobre la pantalla

integrados en la placa se le suele llamar chipset (lo cual te debería táctil. Normalmente tienen un mayor tamaño que la mayoría de los

recordar al mundo del hardware de los PCs y a los conceptos teléfonos móviles, sobre todo la pantalla.

estudiados en el módulo Sistemas Informáticos).


Muchos modelos pueden traer incorporadas cámaras digitales,

La principal arquitectura de microprocesadores utilizada para receptores de GPS o incluso la funcionalidad de telefonía móvil,

telefonía móvil es la diseñada por la empresa ARM ("Advanced RISC llegándose a confundir en muchas ocasiones la ambigua frontera

Machines", hoy día ARM Holdings), teniendo actualmente más del entre smartPhone y PDA. Es decir, que hay PDA que llegan a

90% de la cuota de mercado no ya sólo para teléfonos móviles, sino convertirse en auténticos teléfonos móviles inteligentes, y teléfonos

en general para todo tipo de pequeños dispositivos electrónicos que móviles que llegan a transformarse en PDA con posibilidad de

necesitan un microprocesador. Algunas de las familias de realizar llamadas telefónicas. Un ejemplo típico de dispositivos de

micros ARM más conocidas son las en su versión de bajo consumo "difícil" clasificación entre smartPhone y PDA podrían ser las

y rendimiento A57, y alto consumo y rendimiento como la A78. Hoy conocidas BlackBerries.

día muchas otras empresas diseñan microprocesadores basados en


En la actualidad han convergido hacia móviles con pantallas
las patentes de la arquitectura de los procesadores ARM como por
generosas con soporte táctil.
ejemplo DEC, IBM, Texas
Instrument, Samsung, Intel, Atmel, Alcatel- En cuanto al interior de las PDA, dependiendo de las opciones que
Lucent, Apple, Qualcomm, LG, Nintendo, Philips, Oki, Yamaha, etc ésta tenga podemos encontrar un hardware muy similar al de
muchos smartPhones (chipset, microprocesadores basados en
Algunos ejemplos de productos que incorporan uno o varios
arquitectura ARM, procesadores digitales de señal, emisores y
micros ARM son: la mayoría de teléfonos móviles (Nokia, Sony
receptores de radio, etc.).
Ericsson, Samsung, Apple, etc.), el iPod de Apple, las
consolas Gameboy y Nintendo DS, routers, navegadores GPS, Entre los fabricantes de PDA más conocidos se
cámaras digitales, etc. encuentran: HTC (serie P: teléfonos PDA), Palm (por ejemplo la serie
TX), Motorola (basados en Android), Apple (por ejemplo el iPad
Respecto al chipset, existen diversas posibilidades de arquitectura,
touch), HP (con los Pocket PC sustitutos de las
según sea el fabricante que las produce. Algunas de las
antiguas iPAQ), Dell, Acer, LG, Sony (hasta hace poco
arquitecturas más conocidas son MSM (de la
con PDA basadas en Palm OS) o Sharp (por ejemplo la
empresa Qualcomm), OMAP (Open Multimedia Application
serie Zaurus, basada en Linux).
Sistemas operativos como T-Mobile G1 y popularmente conocido con los nombres

Los sistemas operativos más habituales que te puedes encontrar de Google Phone o GPhone) en 2008.

en un dispositivo móvil son:


Es uno de los más "jóvenes" dentro del grupo de sistemas operativos
para dispositivos móviles y se ha hecho un notable hueco alcanzando
• Android. Desarrollado inicialmente por Google y basado en
un tercio de la cuota de mercado a comienzos de 2011. Existen miles
el núcleo de Linux. El primer fabricante de móviles que lo
de aplicaciones que funcionan sobre Android, con un crecimiento
incorporó fue HTC.
cada vez mayor.
• iOS. Desarrollado por Apple para el iPhone y usado más
tarde también para el iPod Touch, el iPad y Macbooks en En julio de 2005, Google adquirió Android Inc., una pequeña compañía
una futura convergencia. de Palo Alto, California fundada en 2003. Entre los cofundadores de
• Windows Phone (anteriormente llamado Windows Android que se fueron a trabajar a Google están Andy Rubin (co-
Mobile). Desarrollado por Microsoft tanto para fundador de Danger), Rich Miner (co-fundador de Wildfire
smartPhones como para otros dispositivos móviles (por Communications, Inc.), Nick Sears (alguna vez VP en T-Mobile), y
ejemplo PDA). Algunos fabricantes de teléfonos móviles Chris White (quien encabezó el diseño y el desarrollo de la interfaz
que incorporan este sistema operativo son Samsung, LG en WebTV). En aquel entonces, poco se sabía de las funciones de
o HTC. Android Inc. fuera de que desarrollaban software para teléfonos
• Blackberry OS. Desarrollado por Research in Motion (RIM) móviles. Esto dio pie a rumores de que Google estaba planeando
para sus dispositivos Blackberry. entrar en el mercado de los teléfonos móviles.

• HP webOS, desarrollado por Palm, adquirido por HP (Palm


En septiembre de 2007, «InformationWeek» difundió un estudio de
era la desarrolladora del más antiguo Palm OS). Está
Evalueserve que reportaba que Google había solicitado diversas
basado en un kernel de Linux. En la actualidad se usa para
patentes en el área de la telefonía móvil.
televisiones.
• Palm OS. Desarrollado por PalmSource para PDA, aunque El 5 de noviembre de 2007 la Open Handset Alliance, un consorcio
las últimas versiones también funcionan para de varias compañías entre las que están Texas
smartPhones. Instruments, Broadcom Corporation, Nvidia, Qualcomm, Samsung

• Maemo OS. Desarrollado por Nokia para smartPhones, Electronics, Sprint Nextel, Intel, LG, Marvell Technology Group,

PDA e Internet tables. Motorola, y T-Mobile; se estrenó con el fin de desarrollar estándares
abiertos para dispositivos móviles. Junto con la formación de la
• Bada. Desarrollado por Samsung.
Open Handset Alliance, la OHA estrenó su primer producto, Android,
Hay que tener en cuenta que a medida que los teléfonos móviles una plataforma para dispositivos móviles construida sobre la
crecen en popularidad, los sistemas operativos con los que trabajan versión 2.6 de Linux.
también adquieren mayor importancia. Según un artículo en la
El 9 de diciembre de 2008, se anunció que 15 nuevos miembros se
publicación "Kantar.com", la cuota de mercado de sistemas
unirían al proyecto Android, incluyendo PacketVideo, ARM
operativos móviles en 2015 era la siguiente:
Holdings, Atheros Communications, Asustek,
1. Android: 81% Garmin, Softbank, Sony Ericsson, Huawei, Toshiba, Vodafone y ZTE.
2. iOS: 11%
El 24 de febrero de 2014 Nokia presentó sus teléfonos
3. Windows Phone: 8%
inteligentes corriendo Android 4.1 "Jelly Bean" (aunque corriendo una
Como puedes observar, Android tiene la mayor cuota con diferencia. versión propia, tomando la base de AOSP). Se trata de los Nokia X,
X+ y XL
Hace unos años el sistema operativo más usado era Symbian OS,
ahora en desuso. Fue a lo largo del año 2011 cuando Android se colocó En Google, el equipo liderado por Rubin desarrolló una plataforma
por delante de Symbian OS en la cuota de mercado. Como siempre para dispositivos móviles basada en el núcleo Linux que fue
sucede en el mundo de la tecnología, habrá que estar muy pendiente promocionado a fabricantes de dispositivos y operadores con la
de las últimas noticias para tener una buena perspectiva de lo que promesa de proveer un sistema flexible y actualizable. Se informó
nos vamos a encontrar en el mercado durante los próximos año. que Google había alineado ya una serie de fabricantes de hardware
y software y señaló a los operadores que estaba abierto a diversos
Vamos a hacer un rápido repaso de algunos de los sistemas
grados de cooperación por su parte.
operativos más conocidos Android, iOS y Windows Phone, así
como Symbian OS por su relevancia histórica. Hoy en día podemos encontrar multitud de fabricantes que
incorporan Android en sus terminales: Samsung (series Galaxy y
Android. Note), Huawei (Series P y Mate sin los servicios de
ANDROID FUE INICIALMENTE DESARROLLADO POR ANDROID INC., HOY DÍA PARTE Google), Xiaomi, Oppo, etc.
DE LA COMPAÑÍA GOOGLE. ESTÁ BASADO EN UNA VERSIÓN MODIFICADA DEL
Android ha visto numerosas actualizaciones desde su liberación
KERNEL DE LINUX.
inicial. Estas actualizaciones al sistema operativo base típicamente
El primer fabricante que incorporó Android en sus dispositivos arreglan bugs (errores o fallo en el software) y agregan nuevas
fue HTC con su terminal HTC Dream (comercializado también funciones (debuggers). Podemos observar que los nombres de las
versiones del sistema operativo Android corresponden con postres
y que, además, según avanzan la versión avanza alfabéticamente la sean reemplazados por el usuario. Los desarrolladores
letra del postre por orden alfabético (Android 1.0 Apple Pie, Android tienen acceso completo a los mismos APIs del framework
1.1. Banana Bread, Android 1.5 Cupcake, Android 1.6 Donut, etc.). usados por las aplicaciones base.
Actualmente, a febrero de 2021, la última de versión de Android es • Bibliotecas: Android incluye un conjunto de bibliotecas
la 11.0. de C/C++ usadas por varios componentes del sistema.
Estas características se exponen a los desarrolladores a
Bugs (bicho) es un error o fallo en un programa de computador o
través del marco de trabajo de aplicaciones de Android;
sistema de software que desencadena un resultado indeseado. Los
algunas son: System C library (implementación biblioteca
programas que ayudan a la detección y eliminación de errores de
C estándar), bibliotecas de medios, bibliotecas de gráficos,
programación de software son denominados depuradores
3D y SQLite, entre otras.
(debuggers).
• Runtime de Android: Android incluye un set de bibliotecas
La reiterada aparición de nuevas versiones que, en muchos casos, base que proporcionan la mayor parte de las funciones
no llegan a funcionar correctamente en el hardware diseñado para disponibles en las bibliotecas base del lenguaje Java. Cada
versiones previas, hacen que Android sea considerado uno de los aplicación Android corre su propio proceso, con su propia
elementos promotores de la obsolescencia programada o instancia de la máquina virtual Dalvik. Desde la versión
planificada que se refiere a es la determinación o programación del 5.0 utiliza el ART, que compila totalmente al momento de
fin de la vida útil de un producto, de modo que, tras un período de instalación de la aplicación.
tiempo calculado de antemano por el fabricante o por la empresa
durante la fase de diseño de dicho producto, este se torne obsoleto, ANDROID RUNTIME (ART) ES UN ENTORNO DE EJECUCIÓN DE APLICACIONES

no funcional, inútil o inservible. UTILIZADO POR EL SISTEMA OPERATIVO MÓVIL ANDROID. ART REEMPLAZA A
DALVIK, QUE ES LA MÁQUINA VIRTUAL UTILIZADA ORIGINALMENTE POR ANDROID,
Android ha sido criticado muchas veces por la fragmentación que Y LLEVA A CABO LA TRANSFORMACIÓN DE LA APLICACIÓN EN INSTRUCCIONES DE
sufren sus terminales al no ser soportado con actualizaciones MÁQUINA, QUE LUEGO SON EJECUTADAS POR EL ENTORNO DE
constantes por los distintos fabricantes. Se creyó que esta situación EJECUCIÓN NATIVO DEL DISPOSITIVO.
cambiaría tras un anuncio de Google en el que comunicó que los
fabricantes se comprometerán a aplicar actualizaciones al menos Características y especificaciones actuales de Android.
18 meses desde su salida al mercado, pero esto al final nunca se Almacenamiento SQLite, una base de datos liviana, que es usada
concretó y el proyecto se canceló. Google actualmente intenta para propósitos de almacenamiento de datos.
enmendar el problema con su plataforma actualizable Servicios de Entorno de desarro- Incluye un emulador de dispositivos, herramientas
Google Play (que funciona en Android 2.2 y posteriores), separando llo para depuración de memoria y análisis del rendi-
todas las aplicaciones posibles del sistema (como Maps, el teclado, miento del software. Inicialmente el entorno de
Youtube, Drive, e incluso la propia Play Store) para poder desarrollo integrado (IDE) utilizado era Eclipse con
actualizarlas de manera independiente, e incluyendo la menor el plugin de Herramientas de Desarrollo de An-
cantidad posible de novedades en las nuevas versiones de Android. droid (ADT). Ahora se considera como entorno ofi-
cial Android Studio, descargable desde la página
PARA DESARROLLAR APLICACIONES SOBRE ANDROID SE NECESITA EL KIT DE oficial de desarrolladores de Android.
DESARROLLO DE SOFTWARE PARA ANDROID (ANDROID Diseño de disposi- La plataforma es adaptable a pantallas de mayor
STUDIO, ANTERIORMENTE SDK), PROPORCIONADO GRATUITAMENTE tivo resolución, VGA, biblioteca de gráficos 2D, biblioteca
POR ANDROID. ESTE PAQUETE INCLUYE TODO LO NECESARIO PARA CONSTRUIR de gráficos 3D basada en las especificaciones de la
OpenGL ES 2.0 y diseño de teléfonos tradicionales.
APLICACIONES SOBRE EL ENTORNO ANDROID (DEPURADOR, BIBLIOTECAS,
Conectividad Android soporta las siguientes tecnologías de co-
EMULADOR, DOCUMENTACIÓN, ETC.). EL LENGUAJE DE PROGRAMACIÓN QUE SE
nectividad: GSM/EDGE, IDEN, CDMA, EV-DO, UMTS,
UTILIZA ES JAVA (Y POR TANTO ES NECESARIO EL JDK DE ORACLE PARA
Bluetooth, Wi-Fi, LTE, HSDPA, HSPA+, NFC y
PODER COMPILAR PROGRAMAS JAVA). ANDROID STUDIO TAMBIÉN INCORPORA
WiMAX, GPRS, UMTS y HSDPA+.
EL IDE OFICIAL, SUSTITUYENDO A ECLIPSE.
Mensajería SMS y MMS son formas de mensajería, inclu-
yendo mensajería de texto y ahora la Android
Arquitectura de Android
Cloud to Device Messaging Framework (C2DM) es
El sistema operativo de Android tiene los siguientes componentes: parte del servicio de Push Messaging de Android.
Navegador web El navegador web incluido en Android está basado
• Núcleo Linux: Android depende de Linux para los servicios
en el motor de renderizado de código abierto
base del sistema como seguridad, gestión de memoria,
WebKit, emparejado con el motor JavaScript V8
gestión de procesos, pila de red y modelo de controladores.
de Google Chrome. El navegador por defecto de Ice
• Aplicaciones: las aplicaciones están escritas en lenguaje de Cream Sandwich obtiene una puntuación de
programación Java. 100/100 en el test Acid3.
• Marco de trabajo de aplicaciones: la arquitectura está Soporte de Java Aunque la mayoría de las aplicaciones están escri-
diseñada para simplificar la reutilización de componentes; tas en Java, no hay una máquina virtual Java en
cualquier aplicación puede publicar sus capacidades y la plataforma. El bytecode Java no es ejecutado,
cualquier otra aplicación puede luego hacer uso de esas sino que primero se compila en un ejecutable Dal-

capacidades (sujeto a reglas de seguridad del framework). vik y se ejecuta en la Máquina Virtual Dalvik, Dal-
vik es una máquina virtual especializada, diseñada
Este mismo mecanismo permite que los componentes
específicamente para Android y optimizada para Versiones de Android.
dipositivos móviles que funcionan con batería y Las versiones de Android reciben, en inglés, el nombre de
que tienen memoria y procesador limitados. A
diferentes postres o dulces. En cada versión el postre o dulce elegido
partir de la versión 5.0, se utiliza el Android Run-
empieza por una letra distinta, conforme a un orden alfabético.
time (ART). El soporte para J2ME puede ser agre-
gado mediante aplicaciones de terceros como el Nombre código Número de Fecha de lanzamiento Ni-
J2ME MIDP Runner. versión vel
Soporte multimedia Android soporta los siguientes formatos multime- de
dia: WebM, H.263, H.264 (en 3GP o MP4), MPEG-4 API
SP, AMR, AMR-WB (en un contenedor 3GP), AAC, A Apple Pie 1.0 23 de septiembre de 1
HE-AAC (en contenedores MP4 o 3GP), MP3, MIDI, 2008
Ogg Vorbis, WAV, JPEG, PNG, GIF y BMP. B Banana Bread 1.1 9 de febrero de 2009 2
Soporte para strea- Streaming RTP/RTSP (3GPP PSS, ISMA), descarga C Cupcake 1.5 27 de abril de 2009 3
ming progresiva de HTML (HTML5 <video> tag). Adobe D Donut 1.6 15 de septiembre de 4
Flash Streaming (RTMP) es soportado mediante el 2009
Adobe Flash Player. Se planea el soporte de Micro-
E Eclair 2.0–2.1 26 de octubre de 2009 5–7
soft Smooth Streaming con el port de Silverlight
F Froyo 2.2–2.2.3 20 de mayo de 2010 8
a Android. Adobe Flash HTTP Dynamic Streaming
G Gingerbread 2.3–2.3.7 6 de diciembre de 2010 9–10
estará disponible mediante una actualización de
H Honeycomb 3.0–3.2.6 22 de febrero de 2011 11–13
Adobe Flash Player.
I Ice Cream Sand- 4.0–4.0.4 18 de octubre de 2011 14–
Soporte para hard- Android soporta cámaras de fotos, de vídeo, pan-
wich 15
ware adicional tallas táctiles, GPS, acelerómetros, giroscopios,
J Jelly Bean 4.1–4.3.1 9 de julio de 2012 16–
magnetómetros, sensores de proximidad y de pre-
18
sión, sensores de luz, gamepad, termómetro, ace-
K KitKat 4.4–4.4.4, 31 de octubre de 2013 19–
leración por GPU 2D y 3D.
4.4W–4.4W.2 20
Google Play Google Play es un catálogo de aplicaciones gratui-
L Lollipop 5.0–5.1.1 12 de noviembre de 21–
tas o de pago en el que pueden ser descargadas e
2014 22
instaladas en dispositivos Android sin la necesidad
M Marshmallow 6.0–6.0.1 5 de octubre de 2015 23
de un PC.
N Nougat 7.0 15 de junio de 2016 24
Multi-táctil Android tiene soporte nativo para pantallas capa-
O Oreo 8.0 – 8.1 21 de agosto de 2017 26-
citivas con soporte multi-táctil que inicialmente
27
hicieron su aparición en dispositivos como el HTC
P Pie 9 6 de agosto de 2018 28
Hero. La funcionalidad fue originalmente desacti-
vada a nivel de kernel (posiblemente para evitar Q Android 10 10 3 de septiembre de 29
infringir patentes de otras compañías). Más tarde, 2019
Google publicó una actualización para el Nexus One - Android 11 11 8 de septiembre de 30
y el Motorola Droid que activa el soporte multi- 2020
táctil de forma nativa. - Android 12 12 En desarrollo 31
Bluetooth El soporte para A2DF y AVRCP fue agregado en la
versión 1.5; el envío de archivos (OPP) y la explora-
Debes conocer
ción del directorio telefónico fueron agregados en
la versión 2.0; y el marcado por voz junto con el
¿Sabes lo que es API?
envío de contactos entre teléfonos lo fueron en la
versión 2.2. Los cambios incluyeron: API es la interfaz de programación de aplicaciones, abreviada
Videollamada Android soporta videollamada a través de Han- como API (del inglés: Application Programming Interface), es
gouts (ex-Google Talk) desde su versión Honey- el conjunto de subrutinas, funciones y procedimientos (o métodos,
Comb. en la programación orientada a objetos) que ofrece
Multitarea Multitarea real de aplicaciones está disponible, es cierta biblioteca para ser utilizado por otro software como una capa
decir, las aplicaciones que no estén ejecutándose de abstracción. Son usadas generalmente en las bibliotecas de
en primer plano reciben ciclos de reloj.
programación.
Características ba- La búsqueda en Google a través de voz está dis-
sadas en voz ponible como "Entrada de Búsqueda" desde la ver- Una API representa la capacidad de comunicación entre
sión inicial del sistema. componentes de software. Uno de los principales propósitos de una
Tethering Android soporta tethering, que permite al teléfono API consiste en proporcionar un conjunto de funciones de uso
ser usado como un punto de acceso alámbrico o general, por ejemplo, para dibujar ventanas o iconos en la pantalla.
inalámbrico (todos los teléfonos desde la versión De esta forma, los programadores se benefician de las ventajas de
2.2, no oficial en teléfonos con versión 1.6 o inferio-
la API haciendo uso de su funcionalidad, evitándose el trabajo de
res mediante aplicaciones disponibles en Google
programar todo desde el principio. Las API asimismo son abstractas:
Play (por ejemplo PdaNet). Para permitir a un PC
el software que proporciona una cierta API generalmente es
usar la conexión de datos del móvil Android se po-
llamado la implementación de esa API.
dría requerir la instalación de software adiciona
Por ejemplo, se puede ver la tarea de escribir "Hola Mundo" sobre la - MONITOR DE 1280X800.
pantalla en diferentes niveles de abstracción: - JAVA DEVELOPMENT KIT 7.

1. Haciendo todo el trabajo desde el principio: PARA GNU/LINUX:


1. Traza, sobre papel milimetrado, la forma de las
- GNU LIBRARY C 2.15 O SUPERIOR.
letras (y espacio) "H, o, l, a, M, u, n, d, o".
2. Crea una matriz de cuadrados negros y blancos
Android Studio Ofrece herramientas personalizadas para
que se asemeje a la sucesión de letras.
programadores de Android. Se incluyen herramientas completas de
3. Mediante instrucciones en ensamblador, escribe
edición, depuración, pruebas y perfilamiento de códigos. Entre las
la información de la matriz en la memoria
últimas y más importantes herramientas de Android Studio
intermedia (buffer) de pantalla.
destacamos las siguientes:
4. Mediante la instrucción adecuada, haz que la
tarjeta gráfica realice el volcado de esa Escribe código e iteraciones más rápido que nunca
información sobre la pantalla. Android Studio, basado en IntelliJ IDEA, proporciona el me-
2. Por medio de un sistema operativo para hacer parte del nor tiempo de respuesta en tu flujo de trabajo de codifi-
trabajo: cación y ejecución.
1. Carga una fuente tipográfica proporcionada por Instant Run

el sistema operativo. Cuando hagas clic en Run o Debug, la función Instant Run de
Android Studio aplicará los cambios en el código y los recursos
2. Haz que el sistema operativo borre la pantalla.
en tu aplicación en ejecución. Esta interpreta de manera inteli-
3. Haz que el sistema operativo dibuje el texto
gente los cambios y a menudo los entrega sin reiniciar tu app ni
"Hola Mundo" usando la fuente cargada.
volver a compilar tu APK, para que puedas ver los efectos de
3. Usando una aplicación (que a su vez usa el sistema
inmediato.
operativo) para realizar la mayor parte del trabajo:
Editor de código inteligente
1. Escribe un documento HTML con las palabras
Al ofrecer compleción avanzada de código, refactorización y aná-
"Hola Mundo" para que un navegador web lisis de código, el editor de código inteligente te permite escribir
como Firefox, Chrome, Opera, Safari, Midori, un código más eficaz, trabajar más rápido y ser más productivo.
Iceweasel, Web o Internet Explorer pueda A medida que escribes, Android Studio proporciona sugerencias
representarlo en el monitor. en una lista desplegable. Simplemente presiona Tab para inser-
tar el código.
Android Studio: Desarrollo en Android Emulador rápido y cargado de funciones
Android Studio es el IDE oficial de Android. Está diseñado para que Android Emulator se instala e inicia tus apps más rápido que un
Android pueda acelerar el desarrollo y te permita crear las apps de dispositivo real. También te permite crear prototipos de tu app y
mejor calidad para todos los dispositivos de Android y, por tanto, probarlos en todas las configuraciones de dispositivos Android:
proporciona las herramientas más rápidas para la creación de teléfonos, tablets y dispositivos Android Wear y Android TV.
aplicaciones en todos los tipos de dispositivos Android. También puedes simular varias funciones de hardware, como la
localización de GPS, la latencia de red y las funciones multitácti-
Es un entorno de desarrollo integrado para la plataforma Android. les.
Fue anunciado el 16 de mayo de 2013 en la conferencia Google I/O, y Configuración ilimitada de compilaciones
reemplazó a Eclipse como el IDE oficial para el desarrollo de La estructura de proyectos y las compilaciones basadas
aplicaciones para Android. La primera versión estable fue publicada en Gradle de Android Studio te brindan la flexibilidad que
en diciembre de 2014. necesitas para generar APK para toda clase de dispositi-
vos.
Está basado en el software IntelliJ IDEA de JetBrains, y es publicado Sistema de compilación sólido y flexible
de forma gratuita a través de la Licencia Apache 2.0. Está disponible Android Studio ofrece automatización de compilaciones, adminis-
para las plataformas Microsoft Windows, Mac OS X y GNU/Linux. tración de dependencias y configuraciones de compilación perso-
nalizables. Puedes configurar tu proyecto de modo que se incor-
Android Studio está disponible para Windows 2003, Vista, 7, 8, 10 y
poren bibliotecas locales y alojadas, y definir variantes que inclu-
GNU/Linux, tanto plataformas de 32 como de 64 bits, Linux con yan código y recursos diferentes, además de aplicar configura-
GNOME o KDE y 2 GB de memoria RAM mínimo y Mac OS X, desde ciones de reducción de código y firma de apps.
10.8.5 en adelante. Diseño para equipos
Android Studio se integra con herramientas de control de ver-
ANDROID STUDIO ESTÁ DISPONIBLE PARA WINDOWS 2003, VISTA, 7, 8, 10 Y sión, como GitHub y Subversion, para que puedas mantener a tu
GNU/LINUX, TANTO PLATAFORMAS DE 32 COMO DE 64 BITS, LINUX CON equipo actualizado respecto de los cambios en proyectos y com-
GNOME O KDE Y 2 GB DE MEMORIA RAM MÍNIMO Y MAC OS X, DESDE 10.8.5 pilaciones. El sistema de compilación de código abierto de Gradle
EN ADELANTE. te permite adaptar la compilación a tu entorno y ejecutarla en
un servidor de integración continua, como Jenkins.
LOS REQUISITOS DEL SISTEMA PARA LAS TRES PLATAFORMAS SON:
Crea código con confianza
- 2 GB DE RAM (4 GB RECOMENDADOS). A cada paso, Android Studio te permite asegurarte de
crear el mejor código posible.
- 400 MB DE ESPACIO EN DISCO.
Plantillas de código y apps de ejemplo
- 1 GB PARA ANDROID SDK.
En Android Studio se incluyen plantillas de proyectos y código de dispositivos Nokia (ha sido principalmente desarrollada por ellos),
que facilitan la adición de patrones bien establecidos, como un como los de las series N o E y algunos modelos del XpressMusic.
panel lateral de navegación y un paginador de vistas. También También otros fabricantes como Samsung (por ejemplo en el L870)
puedes importar apps totalmente funcionales desde GitHub di- han incorporado la interfaz de usuario S60 en algunos de sus
recto desde la pantalla Create Project. Puedes experimentar con dispositivos.
estas apps, creadas por Google y otros, y también reutilizarlas.
Lintelligence Otras plataformas/interfaces de usuario de Symbian han sido la
Android Studio ofrece un framework de análisis sólido y estático, serie S80 (para los Nokia 92xx, 93xx y 95xx), la S90, UIQ (en
e incluye más de 280 verificaciones de Lint diferentes en toda tu los Sony Ericsson series P800 y P900 o los Motorola RIZR serie Z)
app. A su vez, proporciona varias correcciones rápidas que te per- y MOAP.
miten solucionar con un clic problemas en diferentes aspectos,
como el rendimiento, la seguridad y la corrección. Para el desarrollo de aplicaciones sobre Symbian se utilizó
Herramientas y frameworks de prueba inicialmente una versión específica de C++, aunque hoy día se emplea
Android Studio proporciona una gran cantidad de herramientas C++ estándar con el framework Qt. Los entornos de desarrollo más
y frameworks para ayudarte a probar tus apps de Android. Crea habituales para programar sobre Symbian son Carbide.C++ (basado
y ejecuta código de prueba para tus apps, incluidas pruebas de en Eclipse), o bien QtCreator.
JUnit 4 y de IU funcional. Puedes ejecutar tus pruebas en un
dispositivo, un emulador, un entorno de integración continua o También se pueden desarrollar aplicaciones
Firebase Test Lab. sobre Symbian utilizando Python (Python para S60), Adobe
Crea apps completas y conectadas Flash o Java ME.
Android Studio reconoce que no todo el código se escribe
La última versión de Symbian es OS 10. Nokia Belle, Feature Pack
en Java y tampoco se ejecuta en el dispositivo del usuario.
2., con el lanciamiento el 11 de octubre de 2012 en forma de
Compatibilidad con C++ y NDK
Android Studio te permite usar C++ y el Android NDK junto con actualización.

tu código Java. Proporciona resalte y refactorización de sintaxis


El 12 de junio de 2013 Financial Times da a conocer que ese mismo
para C++, y un depurador basado en lldb que te permite depurar
verano Nokia dejará de vender dispositivos Symbian.
Java y C++ de manera simultánea.
Integración en la nube Windows Phone
Las herramientas integradas para Google Cloud te permiten
Es el sistema operativo desarrollado
crear e implementar un backend para tu app de Android usando
por Microsoft para smartPhones y otros dispositivos móviles.
servicios como Google Cloud Endpoints y Firebase Cloud Mes-
Sustituye a su antecesor Windows Mobile. Las primeras versiones
saging
de sistemas operativos para dispositivos móviles desarrolladas
Estudio de recursos vectoriales
Android Studio facilita la creación de un nuevo recurso de imagen por Microsoft eran conocidas como Windows CE (Compact

para cada densidad. Con Vector Asset Studio puedes seleccionar Embeded), y de hecho la familia de sistemas operativos
íconos de diseño de material design proporcionados por Google o de Microsoft para sistemas empotrados en general
cargar tu propio archivo SVG. Vector Asset Studio luego genera (smartPhones, PDA y otros pequeños dispositivos) sigue
archivos de mapa de bits para cada densidad de pantalla a fin llamándose Windows CE, así como el núcleo del sistema operativo.
de admitir versiones anteriores de Android no compatibles con
Algunos de los primeros Windows CE podían encontrarse en
el formato dibujable en vector.
Editor de traducciones dispositivos como las PDA iPAQ de Compaq (hoy día parte de HP),

Translations Editor te proporciona una vista única de todos tus las cuales surgieron como competencia directa de
recursos traducidos, lo cual facilita la modificación o adición de las PDA de Palm (las Palm Pilot) durante la segunda mitad de la
traducciones, y la localización de traducciones faltantes sin abrir década de los noventa. Las iPAQ con Windows CE ofrecían un
cada versión del archivo strings.xml. Proporciona, incluso, un entorno más parecido al Windows de los ordenadores de escritorio
vínculo para pedir servicios de traducción. frente al aspecto más sobrio de la interfaz de usuario de Palm OS.

La última versión (a mediados del año 2014)


Symbian OS de Windows para smartPhones y PDA es Windows Phone 8.1.
El inicio de Symbian fue un producto originalmente desarrollado por Algunas de las versiones anteriores han sido: Windows
varias empresas como Nokia, Sony Ericsson, Psion, Samsung y Phone 6.5, Windows Mobile 6.x, Windows Mobile 5.x, Windows
muchos otros fabricantes de dispositivos, junto con la propia Mobile 2003 o Pocket PC 2002. Todas ellas basadas en la
empresa de desarrollo de software Symbian Ltd (fundada plataforma Windows CE.
entre Ericsson, Nokia, Motorola y Psion a finales de los noventa).
Entre los fabricantes que incluyen Windows Phone en sus
La idea era el desarrollo de un sistema operativo para dispositivos dispositivos se encuentran HTC (series S y T), Samsung (serie
móviles que compitiera con el de Palm (Palm OS) o con el Omnia), LG (Optimus 7 y Pacific) y Sony Ericsson (Xperia X7).
de Microsoft (Windows CE). Además, después de los últimos acuerdos entre Nokia y Microsoft,
habrá móviles de Nokia con este sistema operativo.
Dentro de las distintas plataformas o interfaces de usuario basadas
en Symbian, la S60 ha sido la más ampliamente utilizada y la más
conocida por el gran público, pudiéndose encontrar en gran cantidad
Para desarrollar aplicaciones sobre Windows Phone pueden Plataformas de desarrollo y lenguajes de
utilizarse las tecnologías Silverlight y XNA de Microsoft, así como el
programación.
entorno de desarrollo Visual Studio.
PARA PODER DESARROLLAR APLICACIONES PARA ALGUNO DE LOS ANTERIORES
Muy recientemente acaba de lanzarse Windows 10 Mobile, que SISTEMAS OPERATIVOS ES NECESARIO, DISPONER DE ALGUNA PLATAFORMA DE

sutituirá a Windows Phone. DESARROLLO QUE PERMITA GENERAR CÓDIGO EJECUTABLE SOBRE ESOS

SISTEMAS, O BIEN SOBRE ALGUNA MÁQUINA VIRTUAL, O ALGÚN INTÉRPRETE QUE


iOS ESTÉ INSTALADO EN EL DISPOSITIVO Y QUE SEA SOPORTADO POR EL SISTEMA
Se trata del sistema operativo desarrollado por Apple originalmente OPERATIVO.
para su iPhone, aunque hoy día también es utilizado por otros
dispositivos de la empresa. Dependiendo de la versatilidad de la plataforma de desarrollo, la
aplicación podrá ser, más o menos portable a otros sistemas
Apple sólo permite que este sistema operativo funcione operativos y/o dispositivos. Por ejemplo, si realizamos programas
sobre hardware Apple. Es un sistema operativo derivado del Mac OS en Objective-C utilizando el SDK de Apple, lo más probable es que
X, también de Apple. nuestra aplicación pueda ser únicamente ejecutada en un
dispositivo Apple (iPhone por ejemplo) sobre el que esté
Tenía el 27 % de cuota de mercado de sistemas operativos móviles
implantado iOS. Si, por el contrario, desarrollamos un programa
vendidos en el último cuatrimestre de 2020, detrás de Android.
en Java usando el Java ME de Sun (hoy día ya Oracle), y
Actualmente su sistema operativo se encuentra en la decimocuarta generamos una aplicación tipo midlet, ésta podrá ser ejecutada en
versión, mejor conocida como iOS 14, y el iPad tiene su propia versión cualquier dispositivo que disponga de una pequeña máquina virtual
con iPad OS 14, para aprovechar sus peculiaridades. de Java (KVM). Se trata nuevamente de la misma problemática que
podemos encontrar en el mundo de los ordenadores convencionales.
A través del App Store, puedes descargar apps nuevas para el
iPhone, el iPad, el iPod touch o el Apple TV (4.ª generación). Para Entre las plataformas de desarrollo para móviles más populares se
descargar y comprar apps en el App Store, necesitas un ID de Apple. encuentran normalmente las que los propios autores de los
El ID de Apple es la cuenta que utilizas para acceder a los servicios sistemas operativos ofrecen para trabajar sobre su plataforma. De
de Apple. hecho ya hemos hablado algo sobre algunas de ellas al describir los
sistemas operativos. Hacemos un repaso rápido de las más
Como se ha comentado antes, los únicos dispositivos que incorporan
conocidas:
este sistema operativo son los distintos modelos de dispositivos
fabricados por la propia compañía Apple. Es decir, • Windows Phone. Para desarrollar aplicaciones
los iPhones (iPhone, iPhone 3G, iPhone 3GS, iPhone 4, etc.), el iPod sobre Windows Phone pueden utilizarse las
Touch, el iPad, y la Apple TV. tecnologías Silverlight, XNA y .NET Compact
Framework de Microsoft. Las herramientas Visual Studio
Para desarrollar aplicaciones sobre iOS, las aplicaciones deben ser
2010 Express y Expression Blend para Windows
compiladas específicamente para este sistema operativo basado en
Phone son ofrecidas gratuitamente por Microsoft. El
la arquitectura ARM. Para ello puede utilizarse el kit de
lenguaje más habitual para el desarrollo ha sido C#,
desarrollo iPhone SDK. El lenguaje de programación principal para
aunque la idea es que se pueda utilizar también otros
este conjunto de herramientas es el Objective-C. Para poder utilizar
lenguajes de la plataforma .NET como Visual Basic .NET.
este kit de desarrollo es necesario un ordenador MAC con un
sistema operativo MAC OS X Leopard 10.5 o superior. Actualmente,
• Android. Google proporciona también de manera gratuita
el Android Studio para programar aplicaciones sobre su
en febrero de 2020, tenemos el sistema operativo MAC OS X Big
sistema operativo. También es posible programar en
Sur 11.2.1.
otros lenguajes como C o C++ gracias al uso
A mediados de 2011 ni el entorno .NET y ni Adobe Flash son del Android NDK (Native Development Kit) que permite
soportados por iOS. Lo mismo sucede con Java, aunque se han generar código nativo (native code), frente al código
producido algunos intentos de máquinas virtuales de Java (basadas gestionado (managed code), que se ejecuta sobre una
en JAVA ME) para iOS, que rozan la legalidad a la que están máquina virtual, como es el caso de Java o C#.
sometidas las restrictivas licencias de Apple. Por supuesto, siempre • iOS. El SDK también se puede descargar gratis, pero para
habrá comunidades de usuarios y desarrolladores que intentarán poder comercializar el software a través de su tienda de
saltarse estas restricciones (para más información al respecto aplicaciones hay que registrarse en el programa de
puedes consultar el término iOS Jailbreaking en la web). desarrollo del iPhone, lo cual no es gratuito. El lenguaje de
programación en este caso es Objective-C.
EL IOS SDK SE PUEDE DESCARGAR GRATUITAMENTE, AUNQUE ES NECESARIO
• Java ME: Este caso sería una excepción respecto a los
REGISTRARSE EN EL PROGRAMA DE DESARROLLO DEL IPHONE PARA PODER
demás, pues no se trata de una serie de herramientas
PUBLICAR EL SOFTWARE CREADO, LO CUAL REQUIERE LA APROBACIÓN DE LA
(bibliotecas, compiladores, IDEs, etc.) asociadas a un
COMPAÑÍA APPLE Y UN PAGO POR ESE SERVICIO, QUE PROPORCIONARÁ AL
sistema operativo, sino de un subconjunto de la
PROGRAMADOR LAS CLAVES FIRMADAS QUE LE PERMITIRÁN PUBLICAR EN
plataforma Java orientada para el desarrollo sobre
LA APPLE STORE.
dispositivos móviles. Se programaría en lenguaje Java y
sería necesario que hubiera instalada una máquina virtual
en el sistema operativo del dispositivo sobre el que se • Gran cantidad de herramientas y frameworks de prueba.
deseara ejecutar la aplicación desarrollada (normalmente • Herramientas Lint para detectar problemas de
se puede programar en modo nativo o bien con Java ME). rendimiento, uso, compatibilidad de versión, etc.
• Soporte integrado para Google Cloud Platform, que facilita
Elección de una alternativa
la integración de Google Cloud Messaging y App Engine.
En nuestro caso Java puede ser una buena elección dado que es el
lenguaje con el que aprendiste a programar en el módulo
• Compatibilidad con C++ y NDK.

de Programación y por tanto no tendrás que aprender un nuevo


Máquinas Virtuales.
lenguaje y podrías dedicarte de lleno al aprendizaje de las
UNA MÁQUINA VIRTUAL ES UNA APLICACIÓN SOFTWARE QUE EMULA EL
características específicas para el desarrollo de aplicaciones para
COMPORTAMIENTO DE OTRA MÁQUINA (POR EJEMPLO LA EMULACIÓN DE UN
dispositivos móviles. Por otro lado, un lenguaje multiplataforma
ORDENADOR "VIRTUAL" DENTRO DE UN ORDENADOR "REAL").
como Java es también muy adecuado para un ciclo con un nombre
como "Desarrollo de Aplicaciones Multiplataforma". Pero en cualquier
En el caso de una máquina virtual de Java (JVM – Java Virtual
caso no debes olvidar que se trata de una de las muchas opciones
Machine), si recordamos lo visto en el módulo de Programación, se
con las que te puedes encontrar en el mercado, y que estas
trata de un programa encargado de interpretar código intermedio
alternativas irán apareciendo y desapareciendo constantemente en
(bytecode en el caso de Java) de los programas precompilados (en
función del éxito que vayan obteniendo.
lenguaje Java) a código máquina ejecutable por

Respecto a la plataforma, habría que elegir entre aquellas que estén el hardware (instrucciones máquina), realizar las llamadas al

relacionadas con Java (Java ME, Android). Finalmente se ha sistema operativo necesarias, velar por la seguridad e integridad del

decidido Java ME, que es la que lleva algo más de tiempo código ejecutado, etc.

utilizándose (desde 2001). Se trata de una buena opción para


De esta manera, la JVM proporciona al programa compilado
desarrollar pequeñas aplicaciones, que podemos emular en
en Java independencia de la plataforma hardware y del sistema
un PC durante la fase de desarrollo y posteriormente subirlas
operativo que corra sobre él.
fácilmente al dispositivo. El desarrollo de aplicaciones mediante
estas API resulta bastante económico de portar a otros dispositivos Las JVM clásicas para la plataforma Java SE son demasiado
que tengan implantada una máquina virtual de Java. En cualquier pesadas (necesidades de memoria, requerimientos de cómputo,
caso, siempre habrá que estar a la expectativa de nuevas pantalla, etc.) para dispositivos pequeños. Imagina por ejemplo la
tecnologías. Por ejemplo, en el caso de Java no hay que perder de limitada pantalla de un teléfono móvil. Es fácil suponer que ese tipo
vista la alternativa Android, debido a la gran importancia que tiene de pantallas serían incapaces de soportar toda la funcionalidad
actualmente en cuota de mercado. proporcionada por la AWT, que es la principal interfaz gráfica de
usuario que tiene Java. Es por esta razón que la plataforma Java
COMO SUELE SUCEDER EN EL MUNDO REAL, NORMALMENTE NO PODRÁS ELEGIR ME ha propuesto otras máquinas virtuales bastante más ligeras.
UNA OPCIÓN U OTRA EN FUNCIÓN DE TUS PREFERENCIAS PERSONALES, SINO MÁS

BIEN DEPENDIENDO CUÁLES SEAN LAS NECESIDADES DE TU CLIENTE. ÉSA ES UNA Por otro lado, una única plataforma de Java podría no encajar
BUENA RAZÓN PARA INTENTAR ESTAR SIEMPRE AL DÍA. adecuadamente con todos los modelos de dispositivos posibles. Es
por ello por lo que Java ME introdujo los conceptos de configuración
El entorno de ejecución y de perfil.

Entorno de desarrollo en Android. Instalación de Las dos máquinas virtuales disponibles en Java ME son la KVM (K
Android Sudio y características. Virtual Machine, se le ha dado ese nombre porque sólo ocuparía

Android Studio es el entorno de desarrollo integrado (IDE) oficial unos pocos Kilobytes de memoria) y la CVM (Compact Virtual

para el desarrollo de aplicaciones para Android y se basa en IntelliJ Machine), algo más pesada.

IDEA .
Configuración del IDE de Android Studio.
Además del potente editor de códigos y las herramientas para Android Studio facilita varias plantillas que ayudan a cumplir con
desarrolladores de IntelliJ. Android Studio ofrece aún más funciones las especificaciones de tu sistema, como el Java Development Kit
que aumentan tu productividad durante la compilación de apps para (JDK) y la memoria RAM disponible, y configuran los ajustes y
Android, como las siguientes: requisitos predeterminados, como una emulación optimizada
predeterminada del Android Virtual Device (AVD) e imágenes del
• Sistema de compilación flexible basado en Gradle. sistema actualizadas.
• Un emulador rápido con varias funciones.
• Un entorno unificado en el que puedes realizar desarrollos Creación de un proyecto en Android
para todos los dispositivos Android. Cada proyecto en Android Studio contiene uno o más módulos con
• Instant Run, para aplicar cambios mientras tu app se archivos de código fuente y archivos de recursos.
ejecuta sin la necesidad de compilar un nuevo APK.
Entre los tipos de módulos se incluyen los siguientes:
• Integración de plantillas de código y GitHub, para ayudarte
a compilar funciones comunes de las apps e importar • Módulos de apps para Android.
ejemplos de código. • Módulos de bibliotecas.
• Módulos de Google App Engine. Interfaz de usuario de Android Studio.
La ventana principal de Android Studio consta de varias áreas
De forma predeterminada, en Android Studio se muestran los
lógicas que se identifican en la siguiente imagen:
archivos de tu proyecto en la vista de proyectos de Android.

Esta vista está organizada en módulos para que puedas acceder


rápidamente a los archivos de origen claves de tu proyecto.

Todos los archivos de compilación son visibles en el nivel superior


de Secuencias de comando de Gradle y cada módulo de la
aplicación contiene las siguientes carpetas:

• manifiestos: contiene el archivo AndroidManifest.xml.


• java: contiene los archivos de código fuente de Java,
incluido el código de prueba JUnit.
• res: Contiene todos los recursos, como diseños XML,
cadenas de IU e imágenes de mapa de bits.

La estructura del proyecto para Android en el disco difiere de esta


representación plana. Para ver la estructura de archivos real del
proyecto, selecciona Project en la lista desplegable Project, tal y Los puntos rojos que se muestran en la imagen anterior
como puedes ver en la imagen. corresponden a lo siguiente:

También puedes personalizar la vista de los archivos del proyecto 1. La barra de herramientas te permite realizar una gran
para concentrarte en aspectos específicos del desarrollo de tu app. variedad de acciones, como la ejecución de tu app y el inicio
Por ejemplo, al seleccionar la vista Problems de tu proyecto, de herramientas de Android.
aparecerán enlaces a los archivos de origen que contengan errores 2. La barra de navegación te ayuda a explorar tu proyecto y
conocidos de codificación y sintaxis, como una etiqueta de cierre abrir archivos para editar. Proporciona una vista más
faltante para un elemento XML en un archivo de diseño. compacta de la estructura visible en la ventana Project.
3. La ventana del editor es el área en la que puedes crear y
modificar código. Según el tipo de archivo actual, el editor
puede cambiar. Al visualizar un archivo de diseño, por
ejemplo, el editor muestra el Editor de diseño.
4. Las ventanas de herramientas te permiten acceder a
tareas específicas, como la administración de proyectos, la
búsqueda y los controles de versión, entre otras. Puedes
expandirlas y contraerlas.
5. En la barra de estado se muestra el estado de tu proyecto
y el IDE, además de advertencias o mensajes.

Puedes organizar la ventana principal para tener más espacio en


pantalla ocultando o desplazando barras y ventanas de
herramientas. También puedes usar combinaciones de teclas para
acceder a la mayoría de las funciones del IDE.

En cualquier momento, puedes realizar búsquedas en tu código


fuente, bases de datos, acciones, elementos de la interfaz de usuario,
etc., presionando dos veces la tecla Shift o haciendo clic en la
lupa que se encuentra en la esquina superior derecha de la ventana
de Android Studio. Esto puede ser muy útil, por ejemplo, si intentas
localizar una acción específica del IDE que olvidaste cómo activar.

Compilación en Android Studio.


Android Studio usa Gradle como base del sistema de compilación, y
proporciona más características específicas de Android a través
del Complemento de Android para Gradle.

Este sistema de compilación se ejecuta en una herramienta


integrada desde el menú de Android Studio, y lo hace
independientemente de la línea de comandos. Puedes usar
las funciones del sistema de compilación para lo siguiente:
• Personalizar, configurar y extender el proceso de posible que su aspecto varíe según se instale en un dispositivo u
compilación; otro, así como también si lo pruebas en un emulador u otro.

• Crear varios APK para tu app con diferentes funciones


Esto se debe tanto a las características físicas del propio dispositivo
usando el mismo proyecto y los mismos módulos;
(tamaño, resolución y colores de la pantalla, tipo de joystick, botones,
• Volver a usar códigos y recursos entre conjuntos de etc.) como a la implementación de la máquina virtual de Java que
orígenes. tenga ese dispositivo (y por tanto del perfil MIDP). Aunque la
comparación no es del todo precisa, la situación podría se similar a
Recurriendo a la flexibilidad de Gradle, puedes lograr todo esto sin
la que se produce cuando navegas por un mismo sitio web con
modificar los archivos de origen de tu app.
ordenadores distintos, con sistemas operativos diferentes y con
Los archivos de compilación de Android Studio se navegadores también distintos, obteniendo visualizaciones de
denominan build.gradle. Son archivos de texto sin formato que usan la web diferentes.
sintaxis Groovy para configurar la compilación con elementos
La manera más sencilla de probar tu aplicación será transfiriendo
proporcionados por el complemento de Android para Gradle.
el archivo .jar del midlet a la memoria del dispositivo donde deseas
Cada proyecto tiene un archivo de compilación de nivel superior para realizar la prueba (a través de un cable USB, por Bluetooth,
todo el proyecto y archivos de compilación de nivel de módulo mediante una tarjeta de memoria, etc.).
independientes para cada módulo. Cuando importas un proyecto
Dependiendo del fabricante del dispositivo, es posible que se
existente, Android Studio genera automáticamente los archivos de
disponga de alguna utilidad que permita realizar la instalación desde
compilación necesarios.
el ordenador (como por ejemplo el Nokia PC Suite). En cualquier caso
VARIANTES DE COMPILACIÓN siempre se puede hacer lo que se ha comentado antes: copiar el
archivo .jar al dispositivo y una vez copiado, instalarlo en el
EL SISTEMA DE COMPILACIÓN PUEDE AYUDARTE A CREAR DIFERENTES dispositivo simplemente pinchando sobre el archivo .jar. El
VERSIONES DE LA MISMA APLICACIÓN A PARTIR DE UN SOLO PROYECTO. ESTO dispositivo preguntará si deseas instalarlo, contestas
RESULTA ÚTIL CUANDO TIENES UNA VERSIÓN GRATUITA O UNA VERSIÓN PAGA DE
afirmativamente y la aplicación quedará instalada en el aparato
TU APP, O SI QUIERES DISTRIBUIR MÚLTIPLES APK PARA DIFERENTES junto con el resto de aplicaciones. Ya sólo faltará ejecutarla como
CONFIGURACIONES DE DISPOSITIVOS EN GOOGLE PLAY. una aplicación más. Si estuvieras desarrollando para otra
plataforma diferente a Java ME, el proceso podría cambiar
Emuladores en Android Studio.
dependiendo del fabricante del dispositivo así como del tipo de
Un emulador es un software que permite ejecutar programas o
archivos generados (no será el mismo proceso para un iOS que para
videojuegos en una plataforma (sea una arquitectura de hardware
un Windows Phone o para un Android, cada plataforma tendrá sus
o un sistema operativo) diferente de aquella para la cual fueron
procedimientos específicos de instalación de software).
escritos originalmente. A diferencia de un simulador, que solo trata
de reproducir el comportamiento del programa, un emulador trata Crear un módulo de un dispositivo en Android Studio.
de modelar de forma precisa el dispositivo de manera que este Los módulos proporcionan un contenedor para código de la
funcione como si estuviese siendo usado en el aparato original. aplicación fuente, archivos de recursos y ajustes de nivel de
aplicación, tales como el fichero de construcción de nivel de módulo
Los emuladores son programas creados con un proceso de
y el archivo de manifiesto de Android. Cada módulo se puede
ingeniería inversa el cual nos traerá viejas glorias del gaming de
construir de forma independiente, probado y depurado.
nuestro pasado para jugar en nuestro dispositivos, por tanto su uso
popular de los emuladores es el de imitar la experiencia de los Android Studio utiliza módulos para que sea fácil agregar nuevos
videojuegos de máquinas recreativas o videoconsolas en dispositivos a su proyecto. Siguiendo unos sencillos pasos en Android
computadoras personales, o el poder ser jugados en otras Studio, puede crear un módulo para contener código que es específico
videoconsolas. para un tipo de dispositivo, tales como Android Wear o Android TV.

Un emulador de Android en un ordenador puede tener miles de Android Studio crea automáticamente directorios de módulos, como
usos, desde un desarrollador que quiere probar sus aplicaciones fuente de recursos y directorios, y un archivo build.gradle defecto
hasta usuarios que quieran jugar con teclado y ratón a los cientos más adecuados para el tipo de dispositivo. Además, Android Studio
de títulos disponibles en esta plataforma. También es una forma de crea módulos de dispositivos con configuraciones de construcción
acceder a aplicaciones que no tenemos disponibles en un ordenador recomendadas, como el uso de la biblioteca de módulos Leanback
y que usamos constantemente cuando estamos con el móvil o Android TV.
tableta.
Modelo de estados de una aplicación
Instalación y ejecución en un dispositivo real.
Las actividades son uno de los componentes fundamentales de las
Como ya se ha comentado antes, algo que debes tener en cuenta apps en la plataforma de Android. Sirven como punto de entrada
cuando se desarrollan aplicaciones con la tecnología Java ME (y en para la interacción del usuario con una app y también son
general para casi cualquier plataforma) es que el aspecto que fundamentales para el usuario que navega en una app (como con
presente el midlet puede que sea diferente en un dispositivo real el botón Atrás) o entre apps (como con el botón Recientes).
con respecto al que se podía observar en un emulador. Es muy
Cuando un usuario navega por tu app, sale de ella y vuelve a entrar,
las instancias de Activity de tu app pasan por diferentes estados de
su ciclo de vida. La clase Activity proporciona una serie de
devoluciones de llamada que permiten a la actividad saber que
cambió un estado, es decir, que el sistema está creando, deteniendo
o reanudando una actividad, o finalizando el proceso en el que se
encuentra.

Para navegar por las transiciones entre las etapas del ciclo de vida
de una actividad, la clase Activity proporciona un conjunto básico de
seis devoluciones de llamadas: onCreate(), onStart(), onResume(),
onPause(), onStop() y onDestroy(). El sistema invoca cada una de
estas devoluciones de llamada cuando una operación entra en un
nuevo estado.
Tema 2. Programación de aplicaciones para dispositivos móviles (I)
Herramientas y fases de construcción el entorno de desarrollo elegido: S. Lo harás del mismo modo que se
suele hacer con todos los lenguajes: mediante el desarrollo de la
En la unidad anterior ya se ha avanzado algo sobre las
clásica aplicación "Hola Mundo".
herramientas que podrían utilizarse para desarrollar una aplicación
de tipo midlet. Como ya has debido ver anteriormente en el módulo Lo primero que hay que hacer para desarrollar una
de Programación, una aplicación Android podrá ser desarrollada aplicación Android con Android Studio es lo mismo que se hace con
utilizando herramientas a través de la línea de órdenes o bien cualquier tipo de aplicación Java: crear un proyecto NetBeans. En
mediante la utilización de un entorno integrado (IDE). Sea cual sea este caso será un proyecto de tipo aplicación móvil. Una vez creado
el método que se utilice para el desarrollo del código, podrás el proyecto, habrá que ir incluyendo y programando clases Java en
distinguir las siguientes fases de construcción para una aplicación él. La primera clase Java que tendrás que escribir será el programa
basada en Java ME: principal de la aplicación. El programa principal de un activity se
encuentra dentro de una clase que será subclase
• Desarrollo del código.
de AppCompatActivity y que tendrá una serie de métodos que
• Compilación.
heredará de ella. A partir de ahí, irás incluyendo el código necesario
• Preverificación. en tu clase midlet para dotar a tu aplicación de la funcionalidad que
• Empaquetamiento. ésta requiera. Además de esta clase principal, podrás ir añadiendo
• Ejecución. poco a poco todas las clases que vayas necesitando.
• Depuración.
Vamos a ver con detalle cada uno de esos pasos.
En nuestro caso, la herramienta que vas a utilizar será Android
Creación de un proyecto Java ME.
Studio. Por otro lado, en la unidad anterior ya vimos cómo instalar
esta herramienta con la funcionalidad necesaria para poder En la unidad anterior ya creaste un ejemplo de proyecto Java ME.

desarrollar aplicaciones Java ME y cómo compilar una aplicación Como pudiste ver, el proceso es similar a la creación de cualquier

básica para poder ejecutarla en el emulador y en un dispositivo real. otro proyecto Java con NetBeans (aplicaciones Java de
escritorio, applets, servlets, etc.).
Desarrollo del código
UN PROYECTO REPRESENTA UNA MIDLET SUITE, ES DECIR, UN CONJUNTO
DE MIDLETS (APLICACIONES) Y NO NECESARIAMENTE SÓLO UNA.

La creación del proyecto dará lugar a la generación de una serie


carpetas que contendrán los archivos fuentes, los binarios y otros
recursos añadidos al proyecto, así como el archivo JAD (descriptor)
y el manifiesto. Se trata de una estructura de carpetas típica de un
proyecto generado con la herramienta Ant:

• La carpeta raíz del proyecto, que contiene al resto de


carpetas y el archivo de configuración del proyecto
Esta es la fase en la que vas a escribir las líneas de código de tu (build.xml).
aplicación. Si no estuvieras utilizando un IDE (en este • src, que contiene los archivos fuente (Java). Es el que más
caso NetBeans) necesitarías un editor de texto para escribir y te interesará por el momento.
almacenar los archivos java que contendrán las clases que vas a ir • dist, que contiene el archivo final JAR y el archivo JAD, así
generando. En este caso, la cosa es tan sencilla como utilizar el como otras bibliotecas externas en formato JAR o ZIP. De
editor de textos de NetBeans. aquí podrías sacar el archivo final para copiarlo a un
dispositivo e instalarlo.
Si el entorno integrado de desarrollo dispone de asistentes para la
• nbproject, que contiene información de configuración del
creación de aplicaciones, parte de ese código podrá ser generado
proyecto para NetBeans.
automáticamente por el asistente, especialmente todo lo que
concierne a la interfaz gráfica de usuario. NetBeans dispone de ese • build, que contiene el archivo de manifiesto, así como los

asistente, aunque nosotros por el momento vamos a aprender a archivos de clases Java procesadas (clases preprocesadas,

escribir una aplicación "desde cero" para poder comprender cómo clases compiladas, clases preverificadas, etc.).

funciona todo el entramado de una aplicación tipo midlet. Una vez


En principio no tendrás que acceder directamente a estas carpetas
comprendido el funcionamiento y las partes de una aplicación,
y archivos, pues será el propio NetBeans el que las administre.
podrán utilizarse los asistentes sin problema, pues seremos capaces
de entender e interpretar el código generado por ellos. SI SE UTILIZA OTRA HERRAMIENTA DE DESARROLLO DIFERENTE A NETBEANS ES
PROBABLE QUE LA ESTRUCTURA DE CARPETAS Y ARCHIVOS DEL PROYECTO SEA
Como ya viste en la unidad anterior, las aplicaciones desarrolladas
TAMBIÉN DIFERENTE.
con Android se ejecutan sobre dispositivos móviles suelen recibir el
nombre de activity. Ahora vas a escribir tu primer middlet utilizando
El esqueleto de un midlet. Funcionamiento de un midlet
Una vez que tengas creado un proyecto Java ME en NetBeans, ya
puedes comenzar a escribir código. La primera clase Java que
tendrías que escribir sería la que representa a toda la aplicación, es
decir, el midlet. Todo midlet necesita tener una clase principal que
hereda de la clase abstracta midlet. Esta clase se encuentra en el
paquete javax.microedition.midlet.

RECUERDA QUE NO ES POSIBLE INSTANCIAR DIRECTAMENTE UN OBJETO DE UNA


CLASE ABSTRACTA SINO QUE ES NECESARIO CREAR UNA CLASE QUE HEREDE DE

ELLA Y QUE IMPLEMENTE TODOS SUS MÉTODOS ABSTRACTOS. ESA NUEVA CLASE

SÍ SERÁ INSTANCIABLE.

Por tanto, cuando crees una aplicación de tipo midlet, su clase


principal deberá heredar de la clase midlet: public class Un midlet comienza en estado en pausa. Cuando se carga en
midletEjemplo extends midlet memoria, se ejecuta su constructor y, si no se produce ningún
problema, se pasa al estado activo ejecutándose el método startApp.
Y se deben implementar todos sus métodos abstractos:
La aplicación continuará en ese estado hasta que pase a un estado

Método Descripción en pausa o destruido.


protected abstract void Método que se ejecuta cuando se
Debido a un acontecimiento externo a la aplicación (una llamada
startApp () pasa de estado en pausa a es-
entrante, la recepción de un mensaje, una alarma, etc.) el gestor de
tado activo.
aplicaciones puede hacer pasar al midlet a estado en pausa, dando
protected abstract void Método que se ejecuta cuando se
los recursos del dispositivo (pantalla, teclado, CPU, etc.) a otra
destroyApp (boolean un- pasa de estado activo a estado en
aplicación y ejecutándose el método pauseApp. Dentro de este
conditional) pausa.
protected abstract void Método que se ejecuta cuando se método tendrás la oportunidad de dejar los elementos de la

pauseApp () destruye el midlet. aplicación correctamente guardados antes de perder el control del
dispositivo (liberar algún recurso, almacenar el estado actual, etc.).

Estos tres métodos ya los viste en la anterior unidad cuando Debes tener en cuenta que, aunque el midlet pierda el acceso a la
estudiaste el modelo de estados de una aplicación, pues sirven para pantalla, los threads continuarán en segundo plano y los
tratar los cambios de estado que se producen en el midlet por temporizadores de la aplicación permanecerán activos.

acciones externas (del entorno de ejecución o del usuario).


Cada vez que se vuelve al estado activo se produce la ejecución del
Para crear un clase que herede de midlet puedes crear una clase método startApp. Dentro de esta función podrás reanudar todo
Java desde cero escribiendo todo el código necesario (indicar que es aquello que se tuvo que detener cuando se ejecutó pauseApp. Es
subclase de midlet e ir escribiendo al menos los tres métodos posible que debas tener en cuenta si el método se está ejecutando

abstractos que debes implementar) o bien indicar en el proyecto por primera vez (por ejemplo para activar la pantalla de comienzo
de NetBeans que quieres crear un nuevo midlet (en lugar de y realizar inicializaciones) o se está volviendo de una pausa (para
simplemente una nueva clase). En tal caso NetBeans escribirá esas restaurar valores pero no inicializarlos).
líneas de código por ti y lo tendrá en cuenta en el proyecto:
Cuando el usuario o el gestor de aplicaciones cierran el midlet se
produce primero una llamada al método destroyApp. Dentro de ese
método podrás liberar recursos y almacenar la información que
haya de ser persistente y no deba perderse. Podrías notificar que
no quieres pasar al estado destruido lanzando la
excepción midletStateChangeException. Si el
parámetro unconditional de destroyApp es false, se evitará la
destrucción del midlet, pero si es true no habrá nada que hacer y se
finalizará la ejecución. Si se produce alguna excepción durante la
ejecución de este método, será ignorada y se pasará en cualquier
caso a estado destruido.

Estos tres métodos de cambio de estado


(startApp, pauseApp, destroyApp) son llamados por el gestor de
aplicaciones. Pero desde el propio midlet también puede se pedir que
se cambie su estado con los siguientes métodos:
ADEMÁS DE LOS TRES MÉTODOS ANTERIORES, QUE SON DE OBLIGADA
IMPLEMENTACIÓN, UN MIDLET SUELE INCORPORAR TAMBIÉN UN CONSTRUCTOR

EN EL QUE SE DEFINEN LOS ELEMENTOS GRÁFICOS Y SE INICIALIZAN LAS

VARIABLES DE ESTADO DE LA APLICACIÓN.


Métodos de solicitud de cambio de estado de la clase midlet Los procesos de verificación durante la generación de código se
encargan de realizar revisiones de seguridad, integridad y
Método Descripción cumplimiento de los estándares.
public void notifyPaused () Indica al gestor de aplicaciones que el mid-
let desea pasar a estado en pausa (y se pasa a Debido a las importantes restricciones existentes en los dispositivos
estado en pausa). móviles, la verificación de las clases tiene dos etapas:
public void notifyDestroyed () Indica al gestor de aplicaciones que el mid-
let desea pasar a estado destruido (y se pasa a • Preverificación, que se realiza como un paso más de la
estado destruido). generación de los binarios.
public void resumeRequest () Indica al gestor de aplicaciones que el mid- • Verificación, llevada a cabo por la máquina virtual durante
let desea pasar a estado activo. Cuando el gestor la ejecución.
lo considere oportuno, llamará al método star-
tApp del midlet y se pasará a estado activo. El proceso de preverificación de las clases generadas por el
compilador también lo realiza NetBeans automáticamente después
compilar. Si realizáramos el proceso "a mano", es decir, sin el IDE y
DEBES TENER EN CUENTA QUE CUANDO SE UTILIZAN ESTOS MÉTODOS, LOS utilizando la línea de órdenes, habría que moverse a la carpeta donde
MÉTODOS DE CAMBIO DE ESTADO (STARTAPP, PAUSEAPP, DESTROYAPP) NO se encuentren las clases generadas y aplicarles la
SON EJECUTADOS. POR EJEMPLO, SI DESEAS FINALIZAR LA EJECUCIÓN DE LA herramienta preverify.
APLICACIÓN MEDIANTE NOTIFYDESTROYED, HAS DE SER CONSCIENTE DE

QUE DESTROYAPP NO SERÁ EJECUTADO. LO MISMO SUCEDERÍA Empaquetamiento.


CON NOTIFYPAUSED Y PAUSEAPP. EN EL CASO DE RESUMEREQUEST, NO PODRÁ Esta fase es la que se encarga de empaquetar la aplicación con
SER LLAMADO DESDE EL MIDLET (PUES ESTÁ EN PAUSA), PERO PODRÍA HACERSE todas las clases y recursos que vaya a necesitar, dejándola lista para
DESDE UN TEMPORIZADOR O DESDE UN THREAD EN BACKGROUND. ser descargada en un dispositivo que la instale.

Ejemplo de un midlet básico Esta fase implica la creación de los siguientes archivos:

Vamos a ver un pequeño ejemplo de un midlet que muestra un


• El archivo JAR (Java Archive), que es el paquete que
cuadro de texto en la pantalla y permite escribir sobre él.
contendrá todos los archivos que conforman la aplicación

Para ello es necesario llevar a cabo los siguientes pasos: (clases; recursos como imágenes y sonidos; archivo
de manifiesto; etc.).
• Crear un nuevo proyecto Java ME de tipo "Mobile • El archvo de manifiesto, que consiste en una descripción
Application". del contenido del archivo JAR. Es un archivo opcional.
• Añadir un nuevo midlet, obteniéndose el esqueleto con los • Archivo JAD.
métodos que deben implementarse obligatoriamente.
De todo esto también se encargará automáticamente
• Importar las bibliotecas de la interfaz gráfica
el IDE de NetBeans cuando pulses el botón para compilar el midlet:
(javax.microedition.lcdui).
se generarán los archivos JAR (incluido el manifiesto) y JAD. En
• Añadir un método constructor donde se creen e inicialicen
cualquier caso también se podría realizar "a mano" con la orden en
los elementos de la aplicación (componentes gráficos,
línea de comandos jar.
variables, etc.).
• Establecer la pantalla inicial en el método startApp. El archivo de manifiesto.
• Implementar el método commandAction. Como has visto, el archivo JAR es el encargado de encapsular todas
• Compilar la aplicación. las clases y recursos que conforman uno o varios midlets. Pero
• Ejecutar la aplicación sobre el emulador. también incluye un archivo de manifiesto (manifest.mf). Este
archivo describe el contenido del JAR y consiste un archivo de texto
Compilación y preverificación. con una estructura de una línea por atributo, donde los valores de
EL PROCESO DE COMPILACIÓN, ES DECIR, DE GENERACIÓN DE UN ARCHIVO los atributos son especificados de la forma "atributo: valor".
BINARIO DE TIPO BYTECODE (CLASS) A PARTIR DE OTRO EN CÓDIGO JAVA YA LO
Un ejemplo de archivo de manifiesto podría ser:
HAS ESTUDIADO EN OTROS MÓDULOS Y NO CAMBIA EN ABSOLUTO.

Una vez que tengas el código fuente de tu programa escrito en uno


o varios archivos con extensión .java, la compilación de aplicaciones
se puede realizar con el programa javac incluido en el software de
desarrollo de Java o bien mediante la utilización del entorno
integrado de NetBeans (o cualquier otro IDE) como ya has visto en
el módulo de Programación. Lo único que hay que hacer es pulsar
un par de botones (a veces solamente uno) en el entorno de
desarrollo.
Dentro de un mismo archivo JAR puede haber almacenados más de • Copiando el archivo JAR en alguna memoria externa
un midlet, en tal caso vendría especificado en el archivo de soportada por el dispositivo (una tarjeta SD por ejemplo)
manifiesto. e insertándole esa memoria.

Se trata de un archivo de texto que puedes consultar y modificar En algunos casos el fabricante del dispositivo (por ejemplo Nokia)
con cualquier editor de texto, aunque NetBeans te permite acceder proporciona un software específico que facilita la gestión y
a él consultando las propiedades del proyecto. mantenimiento de las aplicaciones instaladas (por ejemplo el
Nokia PC Suite). Pero en el fondo lo que hacen es automatizar el
proceso anterior (transferir el archivo JAR a la memoria del
dispositivo e instalarlo en él).

Ejecución
La ejecución de un midlet puede realizarse bien en un
emulador (basta con ejecutar el midlet en NetBeans para que éste
sea ejecutado en el emulador que tengamos configurado en el
proyecto) bien en un dispositivo real. Es casi seguro que el aspecto
final será distinto en ambos casos, aunque el funcionamiento
debería ser prácticamente idéntico (a veces podría haber algunas
diferencias debido a la máquina virtual que haya instalada en el
dispositivo). También es muy probable que haya cambios de aspecto
entre distintos dispositivos (dependiendo de la máquina virtual que
tengan instalada).

Para ejecutar la aplicación en el emulador, bastará como hemos


dicho con hacerlo desde NetBeans. Para hacerlo desde el dispositivo
Archivos JAD.
será también suficiente con seleccionar su icono (que habrá colocado
El archivo JAD (Java Application Descriptor) sirve para proporcionar
la instalación) y abrir la aplicación como cualquier otra aplicación
información adicional sobre el juego de midlets contenido en
que haya instalada en el dispositivo.
un JAR. Su estructura es similar a la del archivo de manifiesto
(pares atributo: valor). En el momento en que el midlet se ponga a funcionar se irán
sucediendo cada uno de los estados que has estudiado (en
Un ejemplo de archivo JAD podría ser:
pausa, activo, destruido) y se irán ejecutando todos sus métodos
(constructor, startApp, pauseApp, destroyApp y todos los que tú
hayas añadido).

Depuración

Instalación en un dispositivo real.


Para la instalación de un midlet en un dispositivo real existen varias
alternativas, pero has de tener en cuenta que en el fondo de lo que
se trata es de copiar el archivo JAR del midlet en la memoria del
dispositivo y realizar su instalación en él.
Para depurar una aplicación de tipo midlet lo ideal es disponer de
Si el archivo JAR se encuentra en un servidor, lo normal es que un IDE adecuado que proporcione todas las posibilidades típicas de
disponga también de su archivo JAD. En ese caso sería suficiente un depurador:
con descargar el archivo JAD mediante el navegador web del
• Ejecución paso a paso y sus variantes (saltando funciones,
dispositivo y a partir de ese momento sería el propio dispositivo el
entrando en ellas, etc.).
que se encargaría de descargar e instalar el archivo JAR.
• Observar el contenido de las variables (y, si es posible,
Si el archivo JAR no está en un servidor, sino que lo tienes en tu modificarlo).
ordenador, recién compilado y en espera de pruebas, puedes copiarlo • Establecer puntos de parada o breakpoints.
a la memoria del dispositivo de la manera que te resulte más
• Control de los threads.
cómoda:
Lo normal será realizar una depuración local sobre el emulador y
• Mediante la transferencia del archivo JAR por Bluetooth. cuando hayamos encontrado la mayor parte de los errores en la
• A través de una conexión USB y copiando el aplicación, instalarla en el dispositivo y volver a probarla para ver si
archivo JAR en la memoria del dispositivo. quedaba algún fallo sin corregir, así como probar cosas que eran
difíciles de probar en el emulador. No se suele realizar depuración
remota sobre el dispositivo. Haría falta el uso de alguna
herramienta especializada. Normalmente la forma de "depurar" Las clases necesarias para crear interfaces de usuario (tanto de
sobre el dispositivo físico suele ser mediante la escritura en pantalla alto nivel como de bajo nivel) se encuentran en el
de mensajes indicativos o valores de variables. paquete javax.microedition.lcdui. A partir de ahora, la mayoría de tus
clases Java dentro de proyectos midlet incluirán la
Interfaces de usuario sentencia import javax.microedition.lcdui.*.
EN ESTE APARTADO VAS A ESTUDIAR CÓMO FUNCIONAN LAS INTERFACES DE
Antes de estudiar los componentes específicos de estas dos
USUARIO EN UN MIDLET, ASÍ COMO CUÁLES SON SUS PRINCIPALES CLASES
interfaces gráficas es importante que conozcas algunos conceptos
ASOCIADAS.
generales comunes como la gestión de la pantalla o el manejo de
La interfaz gráfica de usuario es la parte de un programa que eventos. Para ello estudiarás las
permite al usuario interaccionar con él. En el caso de Java clases Display, Displayable y Command, así como
SE dispones de dos extensas bibliotecas gráficas (AWT y Swing, que la interface CommandListener.
son parte de las JFC), pero para el caso de Java ME, dada la gran
Gestión de la pantalla y los dispositivos de entrada.
cantidad de restricciones que tienen los dispositivos móviles, hubo
que diseñar un pequeño conjunto de clases específicas que La clase Display.
cumplieran esas restricciones. La clase Display es la responsable del control de la pantalla y de la
interacción con el usuario. Proporciona métodos para obtener las
Los elementos gráficos proporcionados por el perfil MIDP se pueden características del dispositivo y para mostrar en pantalla los objetos
clasificar en dos grandes grupos: que componen la interfaz de la aplicación.

• Interfaz de usuario de alto nivel. En este caso es el Sólo existe una instancia (un objeto) de la clase Display por midlet.
dispositivo (en realidad la máquina virtual del dispositivo) Se puede obtener una referencia a él mediante el método
quien se encarga de la colocación adecuada de los estático getDisplay (no creamos nosotros un Display, sino que el
elementos (componentes gráficos, barras de propio midlet ya lo trae implícitamente incorporado al ser creado)
desplazamiento, textos, etc.), de la forma de navegación de la propia clase Display. El método getDisplay se suele ejecutar
(por menús y ventanas) y de características visuales una sola vez (habitualmente en el constructor del midlet) y
como el color o los tipos de letra. También se tiene normalmente se almacena la referencia al Display obtenido en una
asociado un sistema específico de entrada de alto nivel. La variable, en lugar de ejecutar continuamente el método getDisplay.
ventaja de utilizar estas APIs de alto nivel es el alto grado Posteriormente, se utilizará ese Display para crear la interfaz de
de portabilidad de la aplicación para diferentes usuario del midlet.
dispositivos. El posible inconveniente es la pérdida de
control sobre el aspecto final de la aplicación, pues la En el siguiente ejemplo de código, obtenemos el Display de

estética de estos componentes va a depender mucho del un midlet en el constructor y lo almacenamos en una variable de la
dispositivo en el que se ejecute el programa. aplicación:

• Interfaces de usuario de bajo nivel. En este caso el


programador tendrá un control total de todo lo que va a
aparecer en la pantalla. Estas APIs proporcionan un
control completo de los recursos del dispositivo, pudiendo
manejar eventos de bajo nivel como por ejemplo el rastreo
de pulsaciones de teclas (tienen asociadas un sistema de En la siguiente tabla tienes los principales métodos de la
entrada de bajo nivel). La otra cara de la moneda es que clase Display:
son mucho más complicadas de usar (hay que dibujar
Método Descripción
todo lo que aparece en pantalla e interpretar cualquier
static Display getDisplay Devuelve el objeto Display del mid-
entrada del usuario). Suelen utilizarse para la creación
(midlet m) let indicado.
aplicaciones donde el control de lo que aparece en la
Displayable getCurrent () Devuelve el objeto Displayable actual
pantalla y las acciones del usuario requieren un control y
(pantalla actual) del Display.
precisión absoluto (por ejemplo en los juegos). El posible
void setCurrent (Alert alert, Muestra una alerta seguida del ob-
inconveniente en este caso es que una aplicación que
Displayable siguienteDispla- jeto Displayable indicado.
utilice esta biblioteca puede no ser portable o muy poco yable)
portable. void setCurrent (Displayable Muestra un nuevo objeto Displayable.
siguienteDisplayable)
Como puedes observar, las ventajas de un tipo de interfaz frente a
bolean isColor() Indica si el dispositivo soporta pantalla
otro pueden convertirse en un inconveniente según las necesidades
a color.
de la aplicación que se va a desarrollar. En el caso una aplicación que
int numColors() Devuelve el número de colores sopor-
haga uso de la APIs de bajo nivel habrá que comprobar que los
tados.
recursos del dispositivo al que se intenta portar la aplicación sean
bolean vibrate (int duration) Solicita la vibración del dispositivo.
compatibles (por ejemplo comprobar tamaños de pantalla para
asegurarse no dibujar píxel más allá de los límites).
Recomendación Pantallas en un midlet. La clase Displayable (II).
DADO QUE PUEDE HABER VARIAS APLICACIONES EJECUTÁNDOSE
Recuerda que cada vez que un midlet sale del estado en pausa para
CONCURRENTEMENTE EN EL MISMO DISPOSITIVO, PODRÍA DARSE EL CASO DE
volver al estado activo, se realiza una llamada al método startApp,
QUE EL DISPLAYABLE ACTIVO DE UNA APLICACIÓN NO SEA EL QUE HAYA EN ESE
por lo que la construcción de las pantallas y demás elementos que
MOMENTO DIBUJADO FÍSICAMENTE EN LA PANTALLA, NI QUE LOS EVENTOS DE
formarán parte del midlet habrá que hacerlas en el constructor
USUARIO (POR EJEMPLO LAS PULSACIONES DE TECLAS) SEAN DIRIGIDOS A ÉL.
(para que se haga una sola vez).

Se dice que una aplicación está en primer plano (foreground) si


Pantallas en un midlet. La clase Displayable (I).
el displayable activo de esa aplicación es el que está mostrándose
La clase Displayable representa a las posibles pantallas o ventanas
en ese momento en la pantalla del dispositivo y los eventos de
que va a tener una aplicación. Una pantalla (o displayable en
usuario le son entregados a él (es un concepto análogo al de
terminología MIDP) normalmente consistirá en un conjunto de
ejecución en primer plano al que se utiliza en los sitemas
objetos de interfaz gráfico de usuario (tanto de alto como de bajo
operativos). Si la aplicación no está en primer plano (carece del
nivel) organizados de algún modo. Todos ellos formarán parte de un
acceso a la pantalla y a los mecanismos de entrada) se dice que
objeto de la clase Displayable.
está en segundo plano (background). La política de asignación del
Las aplicaciones que construyas estarán formadas normalmente primer plano a una u otra aplicación en un momento dado
por un conjunto de estas pantallas (como sucede en general con dependerá del software responsable de la gestión de aplicaciones
cualquier aplicación que tenga un interfaz gráfico), las cuales suelen que estudiamos en la unidad anterior (el AMS – Application
ser creadas en el constructor del midlet. Management System, o Gestor de Aplicaciones, o Administrador de
Aplicaciones).
En un momento dado la aplicación puede sólo tener un displayable
mostrado en la pantalla y a través de él se produce la interacción Todo midlet tiene un displayable activo en todo momento, aunque
del usuario (es el "displayable activo" o "current Displayable") esté en background. Esto significa que el
método getCurrent siempre va a devolver el displayable activo de la
La clase Display dispone de un método getCurrent para obtener aplicación independientemente de su estado
el Displayable activo y de un método setCurrent para establecerlo. de foreground o background. Por ejemplo si un midlet que se está
Normalmente la aplicación cambiará el Displayable activo en ejecutando en primer plano tiene como displayable actual
respuesta a alguna acción del usuario. Sin embargo en algunas el displayable A y otro midlet en segundo plano tiene
ocasiones puede que sea otro thread el que cambie como displayable actual el B, cuando midlet en segundo plano vuelva
el Displayable activo como respuesta a otro estímulo. a estar en primer plano y llame a getCurrent obtendrá B y si
El Displayable activo también puede cambiar cuando ha finalizado cambia el displayable activo no modificará el displayable activo de
el tiempo establecido para una alerta y entonces se activa la ningún otro midlet que se encuentre en ejecución.
pantalla que debería aparecer tras la alerta.
Tipos de pantallas. Especializaciones de la clase
En la siguiente tabla tienes los principales métodos de la
clase Displayable:
Displayable.
Como acabamos de ver, en el Display de una aplicación sólo se
Método Descripción pueden usar objetos que hereden de la clase Displayable.
static addCommand (Com- Añade el comando cmd al displayable.
mand cmd) Displayable es una clase abstracta que incluye los métodos
int getHeight() Devuelve la altura de la pantalla. encargados de manejar los eventos de la pantalla y añadir o
Ticker getTicker () Devuelve el Ticker (cadena de texto móvil eliminar comandos en ella.
que se desplaza de izquierda a derecha
Existen dos grandes categorías de displayables o pantallas, o dicho
estilo "teletipo") asignado al displayable.
de otro modo, la clase Displayable tiene dos subclases:
String getTitle() Devuelve el título de la pantalla.
int getWidth() Devuelve la anchura de la pantalla.
• La clase Screen (también clase abstracta), que
bolean isShown () Devuelve true si la pantalla (Displayable)
implementa la API de alto nivel. Dentro de esta categoría
está activa.
podríamos hablar también de dos subcategorías:
void removeCommand Elimina el comando cmd del displayable.
o Pantallas con estructura predefinida, que
(Command cmd)
encapsulan componentes de interfaz específicos
void setCommandListener Establece un listener para la captura de
y que las aplicaciones no pueden enriquecer con
(CommandListener lst) eventos.
nuevos componentes. Hay tres tipos:
void setTicker (Ticker ticker) Establece un Ticker para el displayable.
void setTitle (String titulo) Establece un título para la pantalla. ▪ Alert. Alertas.
protected void sizeChanged El AMS llama a este método cuando el ▪ List. Listas.
(int w, int h) área disponible para el objeto Displayable ▪ TextBox. Cuadros o pantallas de texto.
ha sido modificada. o Formularios. Pantallas genéricas donde el
programador de la aplicación puede insertar
etiquetas, campos de texto, imágenes, barras de
progreso y otros componentes de interfaz de
usuario para dar lugar al tipo de pantalla que la • ItemStateListener.
aplicación necesite. Se trata de la clase Form. • ItemCommandListener
• La clase Canvas (nuevamente también abstracta), que
implementa la API a bajo nivel. En este caso las Para que una clase sea oyente (listener) de algún tipo de evento

aplicaciones tienen un control total sobre los debe implementar (implements) su correspondiente interfaz.

componentes en la pantalla y pueden acceder a eventos


Cualquier objeto Displayable puede ser fuente de
de bajo nivel. La clase Canvas es una pantalla en la cual
eventos Command, mientras que sólo objetos Form pueden ser
el midlet puede dibujar directamente. A la hora de crear
fuente de eventos ItemStateListener y los objetos Item lo serían
un interfaz de bajo nivel para la aplicación tendrás que
de ItemStateListener.
crear una subclase de Canvas y redefinir el
método paint para dibujar directamente en la pantalla del La interfaz de usuario de bajo nivel permite gestionar una mayor
dispositivo. cantidad de eventos como la pulsación de una tecla concreta, la
pantalla táctil, etc. Para poder capturar eventos de bajo nivel es
En la siguiente imagen puedes observar la jerarquía de clases de los
necesario utilizar Displayables de bajo nivel (objetos subclase de la
componentes de la interfaz gráfica de usuario:
clase Canvas).

Tanto los eventos a alto nivel como a bajo nivel tienen una cosa en
común: la gestión de eventos debe de realizarse siempre en el
mismo thread en el que se produce el evento.

Comandos. La clase Command (I).

La visualización gráfica de un objeto Command suele ser una


etiqueta de texto en la parte inferior de la pantalla que puede ser
"pulsada" o seleccionada mediante los botones no numéricos del
RECUERDA QUE NO SE PUEDE CREAR UN OBJETO DE UNA CLASE ABSTRACTA , teclado del dispositivo (habitualmente conocidos como botones
SINO QUE DEBE HACERSE DE ALGUNA DE SUS SUBCLASES QUE SE ENCUENTREN "programables") y que sirven para navegar por los menús o
TOTALMENTE DEFINIDAS. POR EJEMPLO NO PUEDEN INSTANCIARSE OBJETOS DE seleccionar opciones. En cualquier caso, siempre dependerá de la
TIPO DISPLAYABLE O SCREEN, AUNQUE SÍ PODRÍAN HACERSE DE implementación de la máquina virtual que tenga instalada el
TIPO ALERT O FORM. dispositivo. Lo más frecuente suele ser una visualización de tipo
"soft-button" o un ítem dentro de un menú.
Un objeto Displayable puede tener asociados un título, un ticker,
algunos comandos y un listener. Dependiendo del tipo Los objetos de clase Command mantienen información sobre un
de Displayable (subclase) que se utilice algunos de esos elementos evento. Contienen tres valores importantes:
podrán tener o no valor.
• Etiqueta (Label). Cadena de texto (String) que representa
Gestión de eventos la acción de ese Command. Ese texto es el que será
En MIDP la gestión de eventos sigue un modelo similar al que usa mostrado en la pantalla del dispositivo para representar
la interfaz AWT en Java SE. Este modelo estaba formado por dos ese comando.
componentes: • Tipo (Type). Número entero que indica el tipo
de Command (qué función puede realizar). Existen ocho
• Las fuentes de eventos, que los generan.
tipos definidos
• Los "listeners" u oyentes de eventos, que los que los de Command: BACK, CANCEL, HELP, EXIT, ITEM, OK, SCRE
procesan. EN, y STOP.

La interfaz de usuario de alto nivel utiliza tres tipos de eventos: • Prioridad (Priority). Número entero que especifica la
importancia o prioridad del Command. Cuanto menor sea
• Activación de un comando (Command) asociado un el número, mayor prioridad tendrá.
displayable (Alert, List, Form, TextBox, Canvas).
Cuando un Command (normalmente asociado a un botón del
• Cambio de estado de un elemento (Item) de un formulario
dispositivo) recibe un evento (normalmente que el botón asociado
(Form).
ha sido pulsado), se realiza una llamada al
• Activación de un comando (Command) asociado a un
método commandAction de la interfaz CommandListener.
elemento (Item) de un formulario (Form).
Con este tipo de elementos podremos elegir distintas opciones
Estos tres tipos de eventos disponen a su vez de su correspondiente
dentro de una determinada pantalla (Displayable) como, por
interfaz de listener asociado:
ejemplo, volver a la pantalla anterior, salir de la aplicación, elegir una
opción entre varias, etc. Son los eventos que te permitirán dar
• CommandListener.
movilidad a tu aplicación para poder seleccionar opciones y saltar de CUANDO SE TIENEN VARIOS COMMANDS EN UN MIDLET (LO CUAL ES BASTANTE
una pantalla a otra. HABITUAL PUES AL MENOS APARECERÁ UN COMANDO PARA VOLVER HACIA

ATRÁS Y UNO O VARIOS PARA REALIZAR ALGUNA ACCIÓN ) LOS DE MAYOR


En la siguiente tabla tienes los principales métodos de la
PRIORIDAD SERÁN LOS MAS ACCESIBLES (SELECCIONABLES DIRECTAMENTE
clase Command:
MEDIANTE LA PULSACIÓN DE POR EJEMPLO SOFT-BUTTON) MIENTRAS QUE LOS

Método Descripción DE MENOR PRIORIDAD SE ACUMULARÁN COMO ÍTEMS DENTRO DE UN MENÚ

Command (String etiqueta, int tipoCmd, int prio- Constructor. DESPLEGABLE ASOCIADO A OTRO SOFT-BUTTON.

ridad)
Command (String etiqueta, String etiquetaLarga, Constructor Oyentes. La interfaz CommandListener y el método
int tipoCmd, int prioridad) commandAction.
int getCommandType () Obtiene el tipo Como has visto en los ejemplos anteriores de gestión de eventos,
de Command.
para realizar una determinada acción no es suficiente sólo con crear
String getLabel () Obtiene la etiqueta
un objeto Command, sino que también hay que implementar la
del Command.
interfaz CommandListener en el midlet para que una vez
int getPriority () Obtiene la prioridad
capturada la activación de ese Command se lleve a cabo una
del Command.
respuesta.

Recuerda que para implementar una interfaz en Java es necesario


Comandos. La clase Command (II).
utilizar la palabra reservada implements: public class Ejemplomidlet
En la siguiente tabla puedes observar la descripción de los distintos
extends midlet implements
tipos posibles de Command:
javax.microedition.lcdui.CommandListener
Tipo Descripción
La interfaz CommandListener contiene sólo un
BACK Orden de navegación para volver a la pantalla ante-
método: commandAction (Command c, Displayable d). Dentro de ese
rior.
método es donde se incluirá el código que debe ejecutarse cuando se
CANCEL Orden para cancelar la acción en curso.
produzca el evento Command c que se encuentra en el
EXIT Orden para salir de la aplicación.
objeto Displayable d.
HELP Orden para solicitar ayuda.
ITEM Para especificar la orden como un ítem en la pantalla. Para continuar con nuestro ejemplo nos faltaría añadir el
OK Orden para aceptar la acción en curso.
método commandAction al midlet:
SCREEN Para indicar órdenes específicas para una pantalla de-
terminada.
STOP Orden para detener una operación en curso.

Si, por ejemplo, se desea crear un objeto Command de tipo BACK,


con la etiqueta "Atrás" y prioridad 0 se podría escribir el siguiente
código de llamada al constructor:

Dentro de este método podrás ir colocando toda la lista de


Una vez creado el Command habrá que asignarlo a alguna pantalla
comprobaciones de los posibles eventos de Commands que hayas
(Displayable) para que forme parte de ella y activar el oyente de
incluido en cada una de las pantallas de tu aplicación de manera que
ese Displayable (normalmente el propio midlet). Por ejemplo:
se realizará una acción u otra en función del evento que se haya
producido.

La interfaz de usuario de alto nivel (I): pantallas


Es responsabilidad del programador de la aplicación garantizar que
las distintas pantallas disponen de Commands para poder navegar básicas.
por ellas (para poder ir pasando de unas a otras). Si se llega a una En este apartado vas a estudiar cómo funcionan las pantallas
pantalla que no dispone de ningún mecanismo para saltar a otra ni básicas de la interfaz de usuario de alto nivel: TextBox, Alert y List.
para volver, no se podrá salir de esa situación hasta que el usuario
cierre la aplicación. Una vez que has captado el concepto general de cómo gestionar y
controlar las acciones del usuario a través de objetos Command y
Reflexiona de cómo insertar éstos en las pantallas de la aplicación, ya puedes
comenzar a estudiar la jerarquía de clases de la interfaz de usuario
Los Commands podrían imaginarse como algo parecido a los
de alto nivel, la cual está compuesta fundamentalmente por la
botones que aparecen en las aplicaciones Windows. Cuando los
clase Screen y sus clases derivadas.
pulsas, la aplicación es consciente de ello y dan lugar a que se realice
una acción determinada.
En los apartados anteriores has visto cómo funcionan dos de los • Alert.
elementos fundamentales en el desarrollo de una interfaz: la • Form.
clase Displayable y la gestión de eventos. Dentro de la
clase Displayable se distinguían dos subclases: Los principales métodos de la clase Screen, heredados en todas sus
subclases son:
• Screen (para la API de alto nivel).
• Canvas (para la de bajo nivel). • String getTitle ().
• setTitle (String titulo).
Vas a comenzar a estudiar ahora la funcionalidad del API de alto
nivel, es decir, vas a trabajar con la clase Screen y sus subclases. A través de los métodos de las subclases, es decir, de cada tipo de
pantalla en particular, la aplicación puede modificar el contenido de
Pantallas de la interfaz de usuario de alto nivel. La un objeto Screen mientras es mostrado en el dispositivo. Si esto
clase Screen. sucede, y el objeto Screen es visible en ese momento (es

La clase Screen es la superclase abstracta común a todas las clases el Displayable activo y el midlet está en primer plano), la pantalla

del interfaz de usuario de alto nivel. La información mostrada en será actualizada automáticamente. De este modo, la aplicación

una pantalla y su comportamiento están definidos por cada una de actualiza la pantalla sin necesitar ninguna otra acción por parte de

sus subclases. la aplicación. Por ejemplo, supongamos que un objeto de tipo List se
está mostrando en ese momento en pantalla y por tanto todos los
Como ya vimos, hay cuatro tipos de pantallas o displayables en esta elementos de esa lista son visibles. Si la aplicación inserta un nuevo
interfaz. Es decir, cuatro clases que heredan directamente de la elemento al comienzo de esa lista, éste será mostrado
clase Screen: inmediatamente a la vez que el resto de elementos se reorganizará
adecuadamente. No es necesario que la aplicación llame a ningún
• TextBox.
otro método para refrescar o actualizar la pantalla (con la API de
• List. bajo nivel es posible que sí haya que hacerlo).

Recuerda que para crear una pantalla de la interfaz de usuario de En la siguiente tabla puedes observar los principales métodos de la
alto nivel (Screen) en tu midlet no puedes crear un objeto de la clase TextBox:
clase Screen (y mucho menos de la clase Displayable), pues se trata
Método Descripción
de una clase abstracta. Tendrías que crear un objeto de alguna de
TextBox (String titulo, Constructor.
las subclases de Screen: TextBox, List, Alert o Form.
String texto, int tamMax,
int restricciones)
Cuadros de texto. La clase TextBox (I).
void delete (int desplaza- Borra cierta cantidad de caracte-
Un TextBox es una pantalla (hereda de la clase Screen) que permite
miento, int longitud) res a partir de un determinado
introducir texto en múltiples líneas. También ofrece la posibilidad de desplazamiento.
restringir el tipo de texto que puede ser introducido. void insert (String texto, int Inserta un texto en el TextBox a
posicion) partir de determinada posición.
String getString () Devuelve el contenido del Text-
Box en un String.
void setString (String Establece el contenido del Text-
texto) Box a partir de un String.
int getConstraints() Obtiene las restricciones actuales
en el TextBox.
void setConstraints (int Establece las restricciones en
restricciones) el TextBox.
int getMaxSize () Obtiene el número máximo de
caracteres establecido.
void setMaxSize (int Establece el número máximo de
maxSize) caracteres.
int size () Obtiene el número de caracteres
introducidos.

Cuadros de texto. La clase Texbox (II).


Para crear un TextBox en el que vayas a introducir un mensaje corto
(con tamaño máximo de 160 caracteres) podrías escribir el siguiente
código de ejemplo:

Método Descripción
Alert(String titulo) Constructor.
Como se ha dicho antes, se pueden establecer restricciones Alert(String titulo, String Constructor.
(constraints) al texto que se permite introducir en un TextBox. Esas texto, Image alertImage,
restricciones se encuentran en la clase TextField, que se estudiará AlertType alertType)

más adelante (al ver los componentes de un formulario). En la Image getImage() Obtiene la imagen asociada con la
alerta.
siguiente tabla puedes observar las restricciones de entrada de
void setImage(Image img) Establece la imagen asociada con la
caracteres que proporciona la clase TextField:
alerta.
Restricciones de la clase TextField String getString() Obtiene el texto asociado con la alerta.
void setString(String str) Establece el texto asociado con la
Tipo Descripción alerta.
ANY Permite cualquier carácter de entrada. int getDefaultTimeout() Obtiene el tiempo por defecto que se
DECIMAL Sólo permite valores numéricos, opcionalmente muestra la alerta.
con decimales (números reales). int getTimeout() Obtiene el tiempo real que se mues-
EMAILADDR Sólo permite caracteres válidos en una direc- tra la alerta.
ción de e-mail. void setTimeout(int time) Establece el tiempo real que se mues-
NUMERIC Sólo permite caracteres numéricos. tra la alerta.
PASSWORD Oculta los caracteres introducidos mediante AlertType getType() Obtiene el tiempo AlertType asociado
una máscara para proporcionar privacidad. a la alerta.
PHONENUMBER Sólo permite caracteres válidos para números void setType(AlertType type) Establece el AlertType asociado a la
de teléfono. alerta.
URL Sólo permite caracteres válidos en una URL. LAS ALERTAS PUEDEN RESULTAR MUY ÚTILES PARA AVISAR AL USUARIO DE UNA
SITUACIÓN ESPECIAL COMO UN ERROR, PREGUNTAR SI SE ESTÁ SEGURO DE UNA

ACCIÓN, INFORMAR DE QUE SE VA A LLEVAR A CABO ALGUNA ACCIÓN


Por ejemplo, si se incluye la restricción TextField.ANY, el usuario
IMPORTANTE, ETC.
podrá insertar cualquier texto, incluyendo saltos de línea.

Cuando se crea un TextBox hay que indicar su capacidad, es decir, el El título de la alerta es el texto que aparecerá en la parte superior

número de caracteres máximo que podrá contener. Si esa capacidad de la pantalla, mientras que el texto de la alerta será el cuerpo del

es mayor que la que el dispositivo puede mostrar a la vez, la mensaje de la alerta.

implementación incorpora un mecanismo automático de scroll para


Los objetos Alert pueden ser dos tipos: modal o no modal, en
poder desplazarte y visualizar el texto completo.
función del tiempo que se desee que el aviso permanezca en
pantalla.
Alertas. La clase Alert.
Un objeto de la clase Alert representa una pantalla de aviso. Son • Modal. Cuando la alerta permanece un tiempo
ventanas emergentes que suelen utilizarse para mostrar mensajes indeterminado hasta que es cerrada por el usuario.
cortos de error, información, confirmación, etc. Muestran un texto
• No modal. Cuando la alerta permanece un tiempo definido
(y opcionalmente una imagen) informativo al usuario. Puede
por el programa. Ese tiempo puede ser definido mediante
recordarnos a componentes gráficos del
el método setTimeout (int time). Una vez que transcurra
tipo MessageBox o messageDialog de otras APIs.
ese tiempo, la alerta desaparecerá y aparecerá el objeto

En la siguiente tabla puedes observar los principales métodos de la displayable que se hubiera establecido para después de la

clase Alert: alerta.

Se dispone de varios tipos de alertas predefinidos. Cada uno de ellos


tiene asociado un sonido:

• ALARM. Indica la llegada de una petición a notificar.


• CONFIRMATION. Indica la finalización de un evento o void setSelectedIndex(int numE- Establece el elemento se-
acción. lem, boolean selected) leccionado.

• ERROR. Indica que se ha producido un error. boolean isSelected(int numElem) Indica si el elemento está
seleccionado
• INFO. Indica información general.
int size() Obtiene el número de ele-
• WARNING. Indica una situación o problema potencial. mentos de la lista.

En la siguiente presentación puedes ver un ejemplo de uso de la


clase Alert. Crearemos dos alertas, una de tipo modal y otro de Aquí tienes un ejemplo de uso del constructor básico para crear una
tipo no modal. Para ello vamos a crear un objeto TextBox al que le lista de cada tipo:
asignaremos dos Commands. Cada uno de ellos activará una
pantalla de alerta.

Reflexiona

¿Te habías planteado que podrías utilizar las pantallas Alert para Además de este constructor, también puedes utilizar un segundo
ayudarte a depurar tus midlets? De esa manera podrías seguir el constructor que permite proporcionar una lista con un conjunto
rastro de ejecución de tu aplicación. inicial de opciones y de imágenes asociadas.

Listas. La clase List.


La clase List permite construir pantallas que contienen una lista de
Listas implícitas.
opciones, lo cual resultará muy útil para crear menús y otras listas
de selección. La clase List implementa la interfaz Choice, lo cual va
a dar la posibilidad de crear tres tipos diferentes de listas:

• Exclusivas (List.EXCLUSIVE), donde sólo se permite una


selección en cada momento.
• Múltiples (List.MULTIPLE), donde se permiten cero o más
selecciones al mismo tiempo.
• Implícitas (List.IMPLICIT), donde la selección de un
elemento genera un evento.

En la siguiente tabla puedes observar los principales métodos de la


clase List:

En las listas implícitas cuando se selecciona un elemento se


Método Descripción
List(String titulo, int listType) Constructor. desencadena un evento. Puede resultar muy útil para implementar

List(String titulo, int listType, Constructor. menús de opciones en las aplicaciones.


String[] elementos, Image[] ima-
Para crear la lista implícita, puedes utilizar alguno de sus
genes)
constructores indicando el tipo de lista (en este caso List.IMPLICIT):
int append(String stringPart, Añade un elemento al fi-
Image imagePart) nal de la lista.
void delete (int numElem) Borra el elemento especi-
ficado.
A continuación, puedes añadir las distintas opciones de la lista con
void insert(int numElem, String Inserta el elemento en el
el método insert o con el método append (si no fueron
stringPart, Image imagePart) índice especificado.
proporcionadas previamente en el constructor o si se desean añadir
void set(int numElem, String Sustituye el texto del ele-
en tiempo de ejecución):
stringPart, Image imagePart) mento en el índice especi-
ficado.
String getString(int numElem) Obtiene el texto del ele-
mento en el índice especi-
ficado.
Image getImage(int numElem) Obtiene la imagen del ele-
mento en el índice especi-
Cuando se seleccione un elemento de la lista se generará un evento
ficado.
de tipo List.SELECT_COMMAND que tendrá que ser gestionado
int getSelectedIndex() Obtiene el índice del ele-
desde el gestor de eventos commandAction (como si se hubiera
mento seleccionado.
seleccionado un Command):
Listas exclusivas En este caso, la selección de un item de la lista no da lugar a la
generación de ningún evento como sucedía con las listas implícitas,
de manera que será necesario utilizar un Command para consultar
el estado de las opciones de la lista, recuperar la opción elegida y
realizar la acción que se considere oportuna.

Para crear la lista implícita, utilizamos nuevamente alguno de sus


constructores indicando el tipo de lista (en este
caso List.EXCLUSIVE):

Para obtener la opción elegida usamos también el


método getSelectedIndex:
En una lista exclusiva, solamente puedes seleccionar un único
elemento. Los elementos de la lista tendrán la apariencia de Radio
Buttons (botones de radio o botones de selección).

Listas múltiples.
En las listas múltiples se pueden seleccionar varios elementos (o
ninguno). Los elementos de esta lista tendrán la apariencia
de Check Boxes (casillas de verificación).

Como en el caso de las listas exclusivas, la selección de un elemento


no desencadena ningún evento, de manera que habrá utilizar la
misma técnica que con las listas exclusivas (tener
un Command para confirmar la opción u opciones seleccionadas). La interfaz de usuario de alto nivel (II):
formularios
Para crear la lista múltiple se utilizará el
identificador List.MULTIPLE en el constructor: VAS A ESTUDIAR CÓMO FUNCIONAN LOS FORMULARIOS (FORM) DE LA INTERFAZ
DE USUARIO DE ALTO NIVEL, ASÍ COMO LOS COMPONENTES (ITEMS) QUE PUEDE
CONTENER.

El último tipo de pantalla o displayable de alto nivel (subclases


Para la obtención de las opciones elegidas, tendrás que usar el
de Screen) que falta por ver es el formulario (clase Form). Se trata
método getSelectedFlags, que rellena un array de bolean indicando
de un displayable algo más complejo que los anteriores. Consiste en
para cada elemento si está seleccionado (true) o no (false):
una pantalla configurable que puede contener diversos
Método Descripción
componentes. Estos componentes son los objetos gráficos típicos
del formulario clásico de una aplicación de gestión (etiquetas,
void setItemStateListe-
campos de texto, imágenes, grupos de selección, barras de progreso, Añade un oyente.
ner(ItemStateListener iListener)
etc.), con las lógicas limitaciones que impone el perfil MIDP. Todos los
objetos que puede contener un formulario derivan de la clase Item.
Devuelve el número
int size ()
de Items en el formulario.
Formularios. La clase Form.
La clase Form dispone de métodos para añadir, eliminar y sustituir
componentes. Los elementos de un formulario están identificados
Cuando se incrustan nuevos Items en un formulario con los
mediante índices, comenzando con el 0 para el primer elemento
métodos append o insert, se obtiene como valor devuelto el índice
hasta size() – 1size devuelve el número de elementos que contiene
de ese elemento en el formulario.
un formulario).

La cantidad de objetos que se pueden incluir en un formulario Elementos en un formulario. La clase Item
dependerá del programador, aunque no es muy recomendable
saturar los formularios con muchos elementos, dado el pequeño
tamaño que suelen tener las pantallas de los dispositivos. para el
último (el método

¡Un mismo Item no puede estar en dos formularios a la vez! Si


intentas insertar un ítem en un formulario cuando ese ítem ya está
siendo utilizado en otro formulario se producirá una excepción de
tipo IllegalStateException.

En la siguiente tabla puedes observar los principales métodos de la


Un formulario no es más que una pantalla contenedora de
clase Form:
componentes gráficos con los que puede interactuar el usuario en
mayor o menor medida. Todos esos componentes derivan de la clase
Método Descripción
base Item.

Form (String titulo) Constructor. Todos los objetos de tipo Item tienen una propiedad label (etiqueta
de texto) que es mostrada junto con el objeto en pantalla cuando
Form (String titulo, Item[] éste se hace visible. Esa etiqueta normalmente se coloca en línea
Constructor.
items) con el ítem o sobre él. Al mostrarse la etiqueta en la pantalla del
dispositivo la implementación de la máquina virtual procurará
Añade una imagen al for- distinguir la etiqueta propiamente dicha del propio contenido del
int append (Image imagen)
mulario. elemento. Si se produce un desplazamiento en la pantalla, la
implementación procurará también mantener visibles a la vez
Añade un Item al formula-
int append (Item item) etiqueta y contenido del ítem.
rio.
En la siguiente tabla puedes observar algunos de los principales
Añade un String al formu- métodos de la clase Item:
int append (String cadena)
lario.
Método Descripción

Elimina un Item del formu- String getLabel Obtiene la etiqueta del Item.
void delete (int numItem) ()
lario.
int getLayout () Obtiene las directivas de layout (disposición)
para la colocación del Item en el formulario.
Elimina todos los ítems del
void deleteAll () void setLabel Establece la etiqueta del Item.
formulario.
(String etiqueta)
int setLayout Establece las directivas de layout (disposi-
void insert (Item numItem, Item Inserta un Item en la posi-
(int layout) ción) para la colocación del Item en el formu-
item) ción numItem.
lario.

Devuelve el Item en la posi-


Item get (int numItem)
ción numItem.

void set (int numItem, Item


Sustituye un Item.
item)
Método Descripción
StringItem (String etiqueta, Constructor.
La clase Item es una clase abstracta y no puede ser instanciada.
String texto)
Los componentes que se pueden incrustar en un formulario serán
StringItem (String etiqueta, Constructor.
objetos de clases que derivan de Item. En concreto dispones de los String texto, String apariencia)
siguientes: int getAppearanceMode () Devuelve la apariencia del
texto del StringItem.
• Etiquetas de texto. Clase StringItem.
Font getFont () Devuelve la fuente del texto
• Imágenes. Clase ImageItem. del StringItem.
• Campos de texto. Clase TextField. String getText() Devuelve el texto del Strin-
• Campos de fecha y hora. Clase DateField. gItem.

• Grupos de selección. Clase ChoiceGroup. void setFont () Establece la fuente del texto
del StringItem.
• Barras de progreso. Clase Gauge.
void setText() Establece el texto del Strin-
Todos esos elementos tendrán sus propias características gItem.
específicas, además de las que hereden de la clase Item.

La disposición de un Item en su contenedor (formulario) puede ser Las posibles apariencias del texto de un objeto StringItem son:
configurada mediante las directivas de layout o disposición. Según
• Item.PLAIN. Aspecto normal. Utilizado normalmente para
la directiva que se elija, se intentará colocar el elemento en una
mostrar información textual no interactiva.
disposición u otra (alineación vertical, alineación horizontal,
colocación un avance de línea, etc.) dentro del formulario.
• Item.HYPERLINK. Aspecto de tipo hiperenlace.
• Item.BUTTON. Aspecto de tipo "botón". Utilizado para
Etiquetas de texto. La clase StringItem simular botones en el formulario (lo veremos más
adelante).

La apariencia puede cambiar dependiendo de la implementación de


la máquina virtual que tenga el dispositivo. En cualquier caso, la
apariencia de un StringItem no implica ningún comportamiento
especial (habría que implementarlo aparte), sino únicamente su
aspecto al mostrarse en la pantalla. Para que un StringItems pueda
tener cierto comportamiento "interactivo" (por ejemplo: simular el
comportamiento de un botón), habría que asociarlo a un comando,
cosa que veremos más adelante.

Un objeto de la clase StringItem muestra en la pantalla una


Imágenes (I). La clase Image.
etiqueta y un contenido de texto no modificable con los que el
En Java ME las imágenes se representan mediante la clase Image.
usuario en principio no puede interactuar, aunque sí pueden ser
Las imágenes (objetos de la clase Image) pueden ser:
cambiados desde la aplicación. Este elemento puede recordarnos al
típico componente Label o "etiqueta" que aparece en muchas APIs de • Inmutables. Imágenes que una vez que son creadas no es
interfaces de usuario (como por ejemplo la clase JLabel en Swing). posible modificarlas. Una imagen inmutable suele ser
Un objeto de tipo StringItem está compuesto por una etiqueta y un creada a partir de un recurso (archivo, array de bytes, etc.).
contenido (texto).
• Mutables. Cuando se crea una imagen mutable en

En la siguiente tabla puedes observar los principales métodos de la realidad se está reservando un bloque de memoria para

clase StringItem: crear en él la imagen más adelante (y modificarla si así


se decide). Para que una imagen mutable se haga visible
es necesario que se indique explícitamente (por ejemplo
mediante el método paint de la clase Canvas).
Las imágenes inmutables pueden aparecer en: En la siguiente tabla puedes observar los principales métodos de la
clase ImageItem:
• Formularios (Form), incrustado como un ítem más.
• Los elementos de una lista (List) o de Método Descripción
un ChoiceGroup (como un adorno que acompaña a cada ImageItem (String etiqueta, Imagen Constructor.

elemento). imagen, int layout, String textoAlt)


ImageItem (String etiqueta, Imagen Constructor.
• Alertas (Alert), como imagen que acompaña al texto de la
imagen, int layout, String textoAlt, int
alerta.
apariencia)
En la siguiente tabla puedes observar los principales métodos de la Imagen getImage () Obtiene la imagen aso-
clase Image: ciada al ImageItem.
void setImage (Imagen imagen) Establece la imagen aso-
Método Descripción ciada al ImageItem.
static Image createImage Crea una imagen inmutable int getLayout () Obtiene la apariencia ac-
(String recurso) a partir de un recurso. tual.
static Image createImage Crea una imagen inmutable int setLayout (int layout) Establece la apariencia
(Image imagen) a partir de otra imagen. actual.
static Image createImage (byte Crea una imagen inmutable String getAltText () Obtiene el texto alterna-
[] datosImagen, int desplaza- a partir de un array de da- tivo a la imagen.
mientoImagen, int longitudIma- tos. void setAltText () Establece el texto alter-
gen) nativo a la imagen.
static Image createImage (int Crea una imagen mutable. Del mismo modo que la clase StringItem sirve para insertar textos
ancho, int alto) y etiquetas en un formulario, la clase ImageItem permite incluir
Graphics getGraphics () Obtiene una referencia al imágenes y, del mismo modo, el usuario tampoco puede en principio
objeto Graphics de una ima- interactuar directamente con ella.
gen mutable.
int getHeight () Obtiene la altura de una
imagen.
int getWidth () Obtiene la anchura de una
imagen.
Bolean isMutable () Indica si una imagen es mu-
table o no.
SI VAS A AÑADIR IMÁGENES A TUS APLICACIONES DEBES TENER EN CUENTA QUE
CUESTIONES COMO EL TAMAÑO Y EL POSICIONAMIENTO DE LA IMAGEN EN LA

PANTALLA DEPENDERÁN DIRECTAMENTE DE LA IMPLEMENTACIÓN DE EN EL

DISPOSITIVO.
El parámetro textoAlt del constructor consiste en el texto
Para trabajar con archivos de imágenes puedes crear un directorio alternativo a la imagen que se mostrará en caso de que ésta no
dentro la carpeta de archivos fuentes del midlet (y llamarlo por pueda ser dibujada en la pantalla (normalmente porque no quepa).
ejemplo "resources", recursos en inglés). Para acceder a los archivos El parámetro layout indica la posición de la imagen (layout o
de esa carpeta desde el código del midlet con el disposición) en la pantalla
método createImage bastaría con escribir la trayectoria (LAYOUT_LEFT, LAYOUT_CENTER, LAYOUT_RIGHT, LAYOUT_NEWLI
"/resources/nombre_archivo". Ese archivo habrá sido incluido en el NE_AFTER, LAYOUT_NEWLINE_BEFORE.). El
archivo .jar al compilar el midlet. Por ejemplo, si el archivo se llama parámetro apariencia tiene los mismos posibles valores que en el
"europa_occidental.png": caso de StringItem (PLAIN, HYPERLINK, BUTTON). Para obtener más
información sobre el layout y la apariencia puedes consultar en la
documentación de la clase ImageItem.

La asociación de una imagen (objeto Image) con un


El formato del archivo gráfico debe ser un formato compatible con
objeto ImageItem se realiza en el constructor o bien con el objeto
la implementación de máquina virtual que tenga el dispositivo. El
ya creado con el método setImage. Por ejemplo:
formato PNG suele ser funcionar en todas las implementaciones.
Para saber más sobre los formatos compatibles puedes consultar
la documentación de las clases Image e ImageItem.

Imágenes (II). La clase ImageItem.


Campos de texto. La clase TextField
Para insertar imágenes (objetos de tipo Image) en un formulario es
Un objeto de tipo TextField consiste en un campo de texto que el
necesario utilizar un marco (un componente o ítem de formulario)
usuario sí podrá editar, al contrario de lo que sucedía con el
en el que poder incrustarlas: la clase ImageItem.
componente StringItem. Es parecido al componente TextBox, con la
diferencia de que TextBox es directamente un tipo de pantalla, como
lo es un formulario Form, y sin embargo TextField es un ítem de
formulario, necesita ser colocado dentro de un formulario. Este
elemento puede recordarnos al típico componente "campo de texto"
que aparece en muchas APIs de interfaces de usuario (como, por
ejemplo, la clase JTextField en Swing).

Tanto la clase TextField como la TextBox comparten las restricciones


Campos de fecha y hora. La clase DateField.
de entrada (constraints) que se vieron al estudiar la clase TextBox.
Un objeto de la clase DateField consiste en un componente editable
En la siguiente tabla puedes observar los principales métodos de la que permite trabajar con información de fecha y hora en un
clase TextField: formulario. La visualización de los formatos de fecha y hora
dependerán bastante del dispositivo en el que se ejecute la
Método Descripción
aplicación.
TextField (String etiqueta, String Constructor.
texto, int tamMax, int restriccio-
nes)
String getString () Obtiene el contenido
del TextField.
void setString (String texto) Establece el contenido
del TextField.
int getConstraints () Obtiene las restricciones
del TextField.
void setConstraints (int restric- Establece las restricciones
ciones) del TextField.
int getMaxSize () Devuelve el número má- En la siguiente tabla puedes observar los principales métodos de la
ximo de caracteres del Tex- clase DateField:
tField.
void setMaxSize (int tamMax) Establece el número má- Método Descripción
ximo de caracteres del Tex- void DateField (String etiqueta, Constructor.
tField. int modo)
int size () Obtiene el número de ca- void DateField (String etiqueta, Constructor.
racteres actual del Text- int modo, TimeZone zonaHora-
Field. ria)
Como en el resto de los objetos que heredan de la clase Item, Date getDate () Devuelve el contenido del Da-
un TextField dispondrá, además del propio contenido de texto teField.

editable, de una etiqueta o label. int getInputMode () Devuelve el modo de entrada


del contenido en el DateField.
Para crear y añadir campos de texto a un formulario no hay más void setDate () Establece un nuevo valor de
que instanciar objetos de la clase TextField y añadirlos al formulario. fecha u hora para el DateField.
Por ejemplo: void setInputMode (int modo) Establece un modo de entrada
para el contenido del Date-
Field.
Esta clase hace uso de la clase java.util.Date para manipular fechas
y horas. El modo de entrada del contenido de un
objeto DateField puede establecerse para manipular fechas (DATE),
horas (TIME) o ambas (DATE_TIME).

En este ejemplo tienes el código java de un midlet basado en un


formulario con los campos de texto anteriores para que puedas
probarlo tú mismo:
Aquí tienes varios ejemplos de uso de campos de fecha y hora:

Grupos de selección. La clase ChoiceGroup.


Un componente de tipo ChoiceGroup consiste en un grupo de
elementos seleccionables, que podrán ser del tipo Check Box (casillas
de verificación, donde se pueden elegir varias opciones), de tipo Radio
Button (botones seleccionables, donde sólo se puede escoger una
opción), o de tipo lista desplegable (también se podrá hacer sólo una
elección). Es decir, un conjunto de ítems los cuales pueden marcarse,
desmarcarse, seleccionarse, etc.

Para representar listas de elementos que se pueden marcar o


desmarcar vimos que también se disponía de las listas múltiples
(objetos de la clase List de tipo MULTIPLE o EXCLUSIVE). La
diferencia fundamental entre ambos tipos de grupos de selección
Los eventos de un componente de tipo ChoiceGroup se pueden
es que en el caso de las listas se trata ya de un tipo de pantalla y
gestionar de dos formas diferentes:
no se podrá añadir nada más a ella (tan solo elementos a la lista),
mientras que en el caso de un ChoiceGroup se trata precisamente • Mediante el
de un tipo de Item que podrá convivir con muchos otros (campos de método commandAction (interfaz CommandListener),
texto, imágenes, otros grupos de selección, etc.) dentro de una del mismo modo que cuando se trabajaba con listas
misma pantalla de tipo Form. Podría decirse que un ChoiceGroup es (exclusivas o múltiples). Es decir, incluyendo en el
como una lista (múltiple o exclusiva) que puede insertarse dentro formulario un Command que cuando sea activado realice
de un formulario. una consulta de las opciones elegidas en el ChoiceGroup.

Tanto la clase ChoiceGroup como la clase List implementan la • Mediante el

interfaz Choice, de manera que muchos de los métodos de ambas método itemStateChanged (interfaz ItemStateListener),

clases son iguales. que será llamado cada vez que se seleccione (o
deseleccione) una opción.
En la siguiente tabla puedes observar los principales métodos de la
clase ChoiceGroup: En el siguiente ejemplo tienes el código java de un midlet basado en
un formulario que contiene un ChoiceGroup con algunos elementos
Método Descripción en su interior:
ChoiceGroup (String label, int choceType) Constructor.
ChoiceGroup (String label, int choceType, Constructor.
String [] elementos, Image[] imagenes)
Int append (String strElem, Image imgE- Añade un elemento al fi-
lem) nal de la lista.
void delete (int numElem) Elimina el elemento espe-
cificado de la lista.
void deleteAll () Elimina todos los elemen-
tos de la lista.
void insert (int numElem, String strElem, Inserta un elemento en el
Image imgElem) índice especificado.
void set (int numElem, String strElem, Sustituye el elemento en Barras de progreso. La clase Gauge.
Image imgElem) el índice especificado. La clase Gauge sirve para implementar barras de progreso. Este
String getString (int numElem) Recupera el elemento en tipo de componentes pueden resultar muy útiles para representar
el índice especificado. de manera gráfica una determinada cantidad que puede ir variando
Image getImage (int numElem) Recupera la imagen del (volumen de audio, tiempo, cantidad de memoria transferida, etc.).
elemento en el índice es- Representa un valor entero que va desde cero hasta un valor
pecificado. máximo prefijado.
int getSelectedIndex (int numElem, boo- Recupera el índice del ele-
lean seleccionado) mento seleccionado.
boolean isSelected (int numElem) Indica si el elemento es-
pecificado está seleccio-
nado.
int size () Devuelve el número de
elementos de la lista.
Una barra de progreso se considera interactiva cuando puede ser • El atributo de disposición (layout) de un ítem puede
cambiada por el usuario al realizar éste alguna acción, por ejemplo, permitirte situarlo en una determinada zona de la
al pulsar un botón. Se considera no interactiva cuando su valor pantalla dentro de la fila en la que se esté colocando el
cambia por la lógica interna de la aplicación, por ejemplo, la barra de componente (Item.LAYOUT_LEFT, Item.LAYOUT_RIGHT,
progreso de un contador de tiempo, o de una descarga, o de la etc.), pero además puedes decidir si quieres que ese
instalación de un programa. elemento comience en una nueva fila obligatoriamente
(Item.LAYOUT_NEWLINE_BEFORE) o que no haya más
ítems en esa fila después de él
(Item.LAYOUT_NEWLINE_AFTER). También existen otras
En la siguiente tabla puedes observar los principales métodos de la opciones posibles como utilizar todo el espacio disponible
clase Gauge: (Item.LAYOUT_EXPAND) o lo contrario
(Item.LAYOUT_SHRINK).
Método Descripción • El ítem Spacer (también subclase de Item) permite
definir ítems "invisibles" pero de un determinado tamaño,
Gauge (String etiqueta, boolean de manera que puedes colocar separadores (espacios en
interactivo, int valMax, int valI- Constructor. blanco) entre elementos que estén en una misma fila.
nicial)
Gestión de eventos en un formulario
Obtiene el valor actual La gestión de eventos de un formulario se realiza de manera similar
int getValue ()
del Gauge. a la de los Commands de un midlet, es decir, mediante un gestor de
eventos que debemos implementar. En este caso se trata de la
Establece el valor actual
void setValue (int valor) interfaz ItemStateListener y de su método
del Gauge.
abstracto itemStateChanged. De este modo, cuando se realice algún
tipo de acción sobre algún componente (Item) de un formulario, se
Obtiene el valor máximo
int getMaxValue () ejecutará el método itemStateChanged y se podrán llevar a cabo las
del Gauge.
acciones (código desarrollado por el programador) que se consideren
oportunas.
Establece el valor máximo
void setMaxValue (int valor)
del Gauge. Otra forma de gestionar eventos sobre los elementos de un
formulario es mediante la interfaz ItemCommandListener, que
Indica si el Gauge es o no in- dispone también de un único método y que nuevamente se
bolean isInteractive ()
teractivo. llama commandAction (aunque en este caso con otros parámetros
En el siguiente ejemplo tienes el código java de un midlet basado en diferentes al caso de la interfaz CommandListener). Para ello,

un formulario que contiene varios objetos de tipo Gauge: tendremos que asociar un Command a un Item de manera que
cuando se accione ese Command se ejecutará el
método commandAction.

Gestión de los cambios en un Item. La interfaz


ItemStateListener
La forma de detectar cambios en los Items que componen un
formulario es a través de un ItemStateListener registrado en el
formulario.

La interfaz ItemStateListener dispone de un único método que


habrá que implementar:

Organizando los elementos en un formulario Este método será llamado cuando algún valor interno de un Item de
En algunos casos puede que te interese una disposición determinada un formulario sea modificado por la acción del usuario (por ejemplo
de los componentes (ítems) en un formulario. Aunque la al rellenar un campo de texto o modificar una opción en un grupo
visualización y organización del formulario va a depender en gran de selección). Con el parámetro item se informa de qué Item es el
medida de la implementación en el dispositivo final, hay algunas que ha sufrido el cambio. Esto puede suceder en casos como los
cosas que se pueden hacer y con las que se pueden obtener buenos siguientes:
resultados estéticos:
• Cuando se cambian los elementos seleccionados en un
• El orden de inserción de los elementos en el formulario es grupo de selección (ChoiceGroup).
importante (método append), pues se irán mostrando en • Cuando se modifica el valor de una barra de
ese orden. También puedes decidir insertar un elemento desplazamiento (Gauge) interactiva.
en una posición determinada (entre dos elementos) con el • Cuando se introduce un valor (o se modifica lo que hay)
método insert. en un campo de texto (TextField).
• Cuando se introduce o modifica el valor de un campo de • Asignar
fecha y hora (DateField). el Command al Item (método addCommand o setDefault
Command).
El criterio para decidir si se ha producido o no un cambio en el estado
• Asignar un oyente para el Item (método
de un Item dependerá de la implementación de la máquina virtual
setItemCommandListener).
en el dispositivo. Por ejemplo, cuando se esté manipulando el valor
de un TextField, en algunas implementaciones bastará con mover el
• Escribir el código apropiado para gestionar el evento
(implementar el método CommandAction de la
cursor en el campo de texto para producir un evento y en otras
interfaz ItemCommandListener)
implementaciones puede que no se produzca un evento de cambio
de estado hasta que el foco no pase a otro componente del
formulario.

Debes tener en cuenta que si es la propia aplicación la que modifica


el valor de un Item, el listener listener no será activado y por tanto
no se llamará al método itemStateChanged.

Para llevar a cabo esta gestión será necesario lo siguiente:

• Crear y asignar a un formulario los Items cuya


modificación se desea controlar.
• Asignar un oyente para los elementos del formulario
(método setItemStateListener).
• Escribir el código apropiado para gestionar el evento
(implementar método itemStateChanged de la
interfaz ItemStateListener). En la siguiente presentación puedes observar un ejemplo de cómo
crear un formulario con elementos susceptibles de ser gestionados
a través de la interfaz ItemCommandListener.

Gestión de Commands asociados a un Item. La interfaz


ItemCommandListener.
Es posible asociar Commands a un Item mediante el Ejemplo de Commands asociados a Items. Simulación de
método addCommand o setDefaultCommand. botones

Para que el sistema pueda detectar que un comando ha sido


invocado, sobre un determinado Item, es necesario implementar la
Si te has fijado bien en la jerarquía de componentes de la interfaz
interfaz ItemCommandListener la cual también dispone, como en
gráfica de alto nivel es probable que hayas echado en falta un objeto
casos anteriores de interfaces, de un solo método:
muy habitual en los formularios: los botones (también conocidos
como "soft-buttons"). No parece que haya nada parecido en toda la
colección de componentes que has estudiado.

El nombre de ese método debería sonarte (de la Aunque la API no proporciona explícitamente una clase Button o
interfaz CommandListener) aunque ahora en lugar de recibir un algo similar para implementar botones, sin embargo sí puedes
parámetro de tipo Displayable, recibe un parámetro de tipo Item ya intentar simularla mediante el uso de objetos StringItem (si
que el Command ha sido invocado desde un Item (elemento de un quieres un botón con un texto) o ImageItem (si quieres un botón
formulario) y no desde un displayable (pantalla que tenía asociados con una imagen) a los cuales les podrías asociar un Command.
una serie de Commands).
Para simular un botón basta con que sigamos los pasos vistos en
Para llevar a cabo esta gestión será necesario lo siguiente: el apartado anterior:

• Crear el Item al que se le desea asociar un comando. • Crear un Item (StringItem o ImageItem) al que se le
• Crear el Command que se le desea asociar. asocia un Command. En este caso es mejor utilizar el
método setDefaultCommand.
• Establecer el oyente de ese Item.
• Implementar el método commandAction con las acciones En el siguiente ejemplo tienes el código java de un midlet en el que
que se deban tomar cuando se active el Command sobre se crean botones utilizando el método que acabas de ver:
ese Item.

En estos casos se suele utilizar el valor Item.BUTTON para la


apariencia del Item (normalmente un StringItem o un ImageItem)
y de ese modo en la pantalla aparecerá de manera que tenga un
aspecto "pulsable", aunque eso siempre dependerá de la
implementación de la máquina virtual en el dispositivo (podría darse
el caso en que no haya diferencia de presentación entre las
apariencias PLAIN, BUTTON o HYPERLINK).

Se recomienda utilizar el método setDefaultCommand (establecer


comando por defecto) para asociar un Command a un Item a la
hora de simular un botón porque así cuando se pulse en el Uniéndolo todo
dispositivo la tecla con la que habitualmente se seleccionan opciones Cuando desarrolles una aplicación medianamente compleja es
(normalmente el botón central de los cursores o del joystick) se posible que tengas un buen número de pantallas diferentes de
activará ese Command (el establecido por defecto). De ese modo la distintos tipos (formularios, cuadros de texto, listas y alarmas). Es
simulación del botón quedará muy elegante. importante gestionar adecuadamente el paso de unas pantallas a
otras para que la aplicación tenga un funcionamiento racional y sea
fácil de utilizar.

Recuerda que para establecer una nueva pantalla como la activa También es recomendable que tus aplicaciones incluyan siempre
debes utilizar el método setCurrent de la clase Display, de manera algunas pautas que ayuden al usuario a manejar el programa con
que cuando se active un Command, se pulse un botón o se dé facilidad:
cualquier otra circunstancia bajo la cual deba mostrarse una nueva
pantalla tendrás que hacer uso de ese método: • Una pantalla inicial de presentación de la aplicación.
• Una pantalla de ayuda con algunas instrucciones
mínimas sobre la aplicación.
• Comandos con etiquetas claras y concisas.

Puede ser interesante dibujar inicialmente cuáles son las pantallas • Formularios sin recargar excesivamente.
(displayables) que vas a necesitar en la aplicación y, a continuación,
Se trata en general de hacer la aplicación lo más amigable y fácil
indicar cómo se podrá ir pasando de unas a otras y bajo qué
de usar al usuario.
circunstancias. Estos esquemas de flujo entre pantallas harán que
el diseño de la aplicación te resulte más fácil y puedas tener una
visión global de la aplicación antes de comenzar a escribir una sola
línea de código.
Creación de interfaces gráficos de usuario podrás arrastrar los elementos hasta la pantalla. Con
esta vista podrías crear formularios sin tener que
utilizando asistentes y herramientas del declarar los ítems que formen parte de él, ni instanciarlos,
entorno integrado ni insertarlos. Todas esas líneas de código las generaría

EN ESTE APARTADO VAMOS A ECHAR UN VISTAZO AL ASISTENTE DE DISEÑO DE automáticamente el asistente.

APLICACIONES MÓVILES DE NETBEANS: VISUAL MOBILE DESIGNER • Vista Source. Ésta la vista que has estado utilizando hasta
ahora. Es la vista del código fuente de las clases de la
El IDE de NetBeans proporciona un asistente para crear midlets aplicación.
(proyectos MIDP) rápidamente y con mucha facilidad, sin tener que • Vista Analyzer. Esta vista es útil para comprobar la
preocuparte demasiado por la parte del código responsable de crear compatibilidad de la aplicación con versiones anteriores de
las pantallas, los Items o los Commands, sino tan solo de utilizarlos la configuración CLDC. También sirve para comprobar los
adecuadamente dentro de tu aplicación. recursos que han sido definidos en la aplicación y cuáles
no son utilizados.
Si tienes mucha experiencia en el desarrollo de midlets es posible
que prefieras no utilizar los asistentes y escribir todo el código tú Con estas cuatro vistas podrás desarrollar aplicaciones de manera
mismo. Así, serás consciente en todo momento de la totalidad de visual, encargándose el asistente de generar la mayor parte del
los elementos que forman parte de tu aplicación y podrás código "rutinario" de creación de objetos, inserción de elementos,
controlarlo todo con mayor facilidad pues lo has escrito tú. Ahora asignación de comandos, etc.
bien, en el caso de un programador que esté aprendiendo a trabajar
con este nuevo entorno o que simplemente quiera hacer alguna
aplicación rápida sin complicarse la vida, siempre puede hacerse uso
de las capacidades que nos brinda un entorno de desarrollo visual
para diseñar e implementar midlets.

En el caso de NetBeans ese asistente se llama "Visual Mobile


Designer" y permite al programador diseñar cada una de las
pantallas de la aplicación así como planificar el flujo entre ellas a
través de los comandos. El asistente generará automáticamente el
código necesario para crear las pantallas y sus componentes. Tú tan
solo tendrías que ocuparte de la lógica del programa, los algoritmos,
etc.

Un ejemplo de asistente: el Visual Mobile Designer de


Netbeans. La interfaz de usuario de bajo nivel.
Cuando un midlet es desarrollado con el VMD (Visual Mobile EN ESTE APARTADO VAS A APRENDER A CREAR PANTALLAS DONDE TODO LO
Designer) dispondrás de cuatro posibles vistas para trabajar con él: QUE APAREZCA SERÁ CONTROLADO POR EL CÓDIGO QUE TÚ ESCRIBAS . PARA
ELLO HARÁS USO DE LA INTERFAZ DE USUARIO DE BAJO NIVEL.
• Vista Flow. Es la vista que te permite crear y modificar el
flujo de pantallas de la aplicación, indicando cómo se puede La clase Displayable tiene dos subclases: la
ir de una pantalla a otra y a través de qué comando. Todo clase Screen (implementa el API de alto nivel) y la
esto se puede hacer fácilmente con el uso del ratón, clase Canvas (implementa el API de bajo nivel). Hasta ahora has
arrastrando y soltando elementos desde la paleta de estado utilizando las primera. Es el momento de explorar algunas
componentes sobre al panel principal. Como en la mayoría de las posibilidades de la segunda.
de los asistentes, es posible editar los atributos de los
Todas las pantallas que utilicen la API de bajo nivel heredan de la
elementos seleccionados a través de un panel de
clase Canvas, que es la clase base para escribir aplicaciones que
propiedades. La paleta de componentes en este caso serán
necesitan manejar eventos a bajo nivel, así como realizar peticiones
sobre todo tipos de pantalla (displayables
para dibujar gráficos en la pantalla.
como Alert, List, Form, etc.), comandos, Items, etc. El
asistente incorpora algunos tipos de pantalla nuevos La clase Canvas es la clase base o superclase de todas las pantallas
creados por NetBeans que utilizan las APIs de bajo nivel, del mismo modo que Screen lo
(como LoginScreen o SplashScreen). También se dispone era para las pantallas que usaban las APIs de alto nivel. Con objetos
de un panel "Navegador" mediante el cual se muestran derivados de la clase Canvas se pueden manejar eventos de bajo
todos los elementos del midlet (displayables, comandos, nivel y dibujar cualquier cosa en la pantalla. Un ejemplo de aplicación
fuentes, recursos, etc.) de manera jerárquica (en forma de que utiliza de manera intensiva el Canvas es el caso de
árbol). los videojuegos .
• Vista Screen Design. A través de esta vista puedes
Ahora bien, utilizar el API de bajo nivel, es decir, pantallas basadas
realizar el diseño de las pantallas, observando el aspecto
en la clase Canvas, no significa que haya que renunciar a las
final que tendrán cuando la aplicación sea ejecutada. En
pantallas basadas en la clase Screen (API de alto nivel). En una
este caso también dispones de la paleta desde donde
misma aplicación de tipo midlet, pueden utilizarse displayables
tanto de tipo Canvas como de tipo Screen. De hecho, lo habitual será
utilizar pantallas de alto nivel siempre que nos sea posible (pues
nos dan una buena parte del trabajo hecho) y pantallas de bajo nivel
cuando tengamos que hacer algo que los displayables de
tipo Screen no permitan.

El lienzo donde dibujar. La clase Canvas (I).


La clase Canvas es la clase base para escribir aplicaciones que
necesitan dibujar gráficos en la pantalla y manejar eventos a bajo
nivel.

Esta clase proporciona métodos para manejar acciones, eventos de


teclado y eventos de puntero (para aquellos dispositivos que lo El lienzo donde dibujar. La clase Canvas (II).
soporten). También dispone de métodos para identificar las
Si deseas que se dibuje algo más en el Canvas puedes intentar este
capacidades gráficas del dispositivo y para asociar las teclas o
otro método paint que se encarga de dibujar un tablero de nxm
botones con determinadas acciones. Al igual que otros displayables
casillas:
ya estudiados permite a una aplicación registrar un listener para
los comandos.

Una diferencia fundamental al crear nuestras pantallas de bajo


nivel es que los displayables que nuestras aplicaciones utilicen deben
ser subclases de Canvas que tendremos que definir. Es decir, no se
trata simplemente utilizar y configurar subclases de Screen ya
predefinidas en la API (Form, TextBox, Alert, List) como hacías en
alto nivel, sino que tendrás que implementar tus propias clases,
hijas de Canvas, para dibujar el tipo de pantalla que la aplicación
necesite.

La clase Canvas posee un método abstracto llamado paint() que se


encarga de dibujar en la pantalla del dispositivo. Ese método tendrá
que ser implementado en cada clase que sea subclase de Canvas. Eventos de bajo nivel.
Se dispone de dos medios de interacción con un objeto Canvas:
RECUERDA QUE SI UNA CLASE DISPONE DE UNO O VARIOS MÉTODOS
ABSTRACTOS, ÉSTOS DEBEN DE SER IMPLEMENTADOS OBLIGATORIAMENTE POR • Mediante los Commands, como has hecho en la API de
AQUELLAS CLASES QUE HEREDEN DE LA PRIMERA. alto nivel.
• Mediante eventos de bajo nivel.
La clase Canvas dispone también de una serie de métodos de
notificación de eventos que no son abstractos pero cuya Los eventos de bajo nivel son:
implementación está vacía, siendo misión del desarrollador de cada
aplicación específica implementar aquéllos que vayan a ser
• Eventos de teclado.

requeridos en función de las necesidades de la aplicación. • Eventos de puntero.

Resumiendo, se puede decir que ahora tendrás que crear tu propia Como has visto anteriormente, la clase Canvas proporciona

clase displayable, que será una subclase de Canvas (en lugar de métodos para manejar los eventos de bajo nivel. La implementación

heredar de Screen), y por tanto tendrás que escribir su código, de estos métodos en la propia clase Canvas está vacía. Será cuando

implementando al menos: tú, al desarrollar una subclase de Canvas, sobrescribas el código de


aquellos métodos que necesites según las necesidades de tu
• Un constructor (no es obligatorio pero será lo más aplicación.
habitual).
Los eventos de teclado pueden ser gestionados con los
• El método paint () (por heredar de la clase
métodos keyPressed, keyReleased y keyRepeated. En tu aplicación
abstracta Canvas).
implementarás los que sean necesarios para su correcto
• El método commandAction () (por implementar el
funcionamiento. Del mismo modo también existen métodos para
interfaz CommandListener). Será la propia clase, en lugar
gestionar posibles eventos de puntero, si el dispositivo soporta esa
del midlet, la que contendrá al gestor de eventos de esa
funcionalidad, que son pointerDragged, pointerPressed,
pantalla.
y pointerReleased.
• Aquellos métodos de notificación que se consideren
necesarios. En el caso de aquellas teclas relacionadas con las direcciones
(cursores) y los juegos (disparar, recoger, seleccionar, etc.) se
De este modo, si se deseas crear un midlet que haga uso de la utilizarán los mismos métodos que para los eventos de teclado (en
clase Canvas tendrías un esqueleto con la siguiente estructura: realidad son también pulsaciones de teclas y por tanto eventos de
teclado), aunque utilizando unos códigos especiales
llamados acciones de juego que garanticen la portabilidad de las de Canvas desarrollada por él) su funcionalidad según las
aplicaciones (pues no todos los dispositivos tienen los cursores y necesidades de la aplicación.
demás acciones relacionadas con los juegos en las mismas teclas).

Eventos de teclado y puntero.


La gestión de los eventos de teclado se realiza utilizando códigos de
teclas o key codes, que son códigos numéricos que se corresponden
unívocamente con las teclas del dispositivo. Los key
codes disponibles en el perfil MIDP van
desde KEY_NUM0 hasta KEY_NUM9, KEY_STAR y KEY_POUND (la
s diez cifras numéricas, el asterisco y la almohadilla).

Los valores de estos códigos corresponden a la codificación Unicode


del carácter que representa la tecla asociada. Si el dispositivo Si por ejemplo queremos mostrar en pantalla cuál es la tecla que

dispone de alguna otra tecla que tenga una correspondencia con un se va pulsando en cada momento podríamos escribir el siguiente

carácter Unicode, su código de tecla debería coincidir con el Unicode. código en el método keyPressed:

Con estos códigos es posible conocer cuál ha sido la tecla pulsada en


un momento dado y, por tanto, podemos desencadenar las acciones
oportunas para gestionar el evento de pulsación de una tecla.

Los métodos proporcionados por Canvas para el manejo de códigos Acciones de juego
de teclas son: No todos los dispositivos tienen las opciones de dirección
(arriba, abajo, izquierda, derecha) y las relacionadas con los juegos
Método Descripción
(fuego, seleccionar, etc.) en las mismas teclas (mismos códigos de
boolean hasRepeatEvents Indica si el dispositivo puede gene-
() rar eventos repetidos cuando se tecla). Esto podría dar un problema de portabilidad si se utilizaran
mantiene una tecla pulsada. Per- los códigos de teclas de un dispositivo (o una familia de dispositivos)
mitiría detectar la repetición de te- determinado.
clas.
Por esa razón se recomienda, para garantizar la portabilidad, que
string getKeyName (int Devuelve el nombre de una tecla
las aplicaciones que requieran el empleo eventos de teclado de
codigo) dado su código asociado.
dirección (cursores) y eventos relacionados con juegos hagan uso de
void keyPressed (int co- Se llama a este método cuando se
digo) pulsa una tecla. las acciones de juego en lugar de los códigos de tecla (key codes) o

void keyReleased (int co- Se llama a este método cuando se los nombres de tecla.
digo) suelta una tecla.
Las acciones de juego no son más que códigos estándar para las
void keyRepeated (int co- Se llama a este método cuando se
funciones típicas de juegos (direcciones, disparo, etc.). El
digo) mantiene pulsada una tecla.
perfil MIDP define las siguientes acciones de
El perfil MIDP también permite detectar eventos producidos por un
juego: UP, DOWN, LEFT, RIGHT, FIRE, GAME A, GAME B, GAME
ratón o un puntero en una pantalla táctil (pulsar, soltar y arrastrar)
C y GAME D. Esas acciones de juego se corresponderán con uno (o
si el dispositivo lo soporta. Los métodos relacionados con los eventos
varios) códigos de tecla que a su vez se corresponderán con teclas
de puntero son:
de dirección (cursores) y demás botones especializados del
Método Descripción dispositivo. Dependiendo del aparato, cada una de estas acciones de
boolean hasPointerEvents () Indica si el dispositivo soporta juego puede estar asociada con una tecla diferente (y por tanto con
puntero. un código de tecla diferente).
bolean hasPointerMotionEvents () Indica si el dispositivo soporta ac-
Dado que las acciones de juego están asociadas también a la
ciones de puntero (pulsar, soltar,
arrastrar). pulsación de teclas estamos en realidad también hablando

void pointerDragged () Llamado cuando se arrastra el de eventos de teclado y por tanto se utilizan los mismos métodos
puntero. (keyPressed, keyReleased y keyRepeated) para la gestión de los
void pointerPressed () Llamado cuando se presiona el eventos.
puntero (o clic de ratón).
Para poder realizar las conversiones entre los códigos de teclas y
void pointerReleased () Llamado cuando se suelta el pun-
las acciones de juego la clase Canvas proporciona los siguientes
tero.
métodos

Recuerda que la implementación de los métodos que son llamados


cuando se produce un evento (métodos de
tipo Pressed, Released, Repeated, Dragged, etc.) es vacía (la
clase Canvas contiene código vacío). Debe ser el desarrollador de la
aplicación quien sobrescriba (en una subclase
Método Descripción pantalla activa). En aquellos casos en los que la pantalla actual deba
int getGameAction Obtiene la acción de juego asociada a ser redibujada porque se ha producido algún tipo de evento (y por
(int keyCode) un código de tecla. tanto van a cambiar elementos en la pantalla) se realizará una
int getKeyCode (int Obtiene el código de tecla asociado a llamada al método repaint.
gameAction) una acción de juego.
Cada key code puede estar asociado como máximo con una acción Cuando el administrador de aplicaciones hace visible un Canvas en

de juego. Sin embargo, una acción de juego puede estar asociada con la pantalla, llama al método showNotify y cuando lo elimina de la

más de un código de tecla (imagina por ejemplo el caso de los pantalla hace una llamada a hideNotify. Estos métodos están vacíos
cursores que tienen sus propios botones pero que también están en la clase Canvas. Su implementación puede resultar muy útil para:
en el teclado numérico).
• Almacenar el estado de la aplicación (disposición de la
Si por ejemplo quisieras controlar el movimiento de los cursores y pantalla, variables, etc.) cuando el AMS por alguna razón
de la tecla "disparo" podrías utilizar la función getGameAction para (por ejemplo por una llamada telefónica entrante o por la
recuperar la acción de juego (en lugar de utilizar directamente llegada de un mensaje corto) quita un objeto Canvas de la
un código de tecla, que sería dependiente del dispositivo). La pantalla del dispositivo (antes llamará a hideNotify).
implementación del método keyPressed podría quedar entonces así: • Restaurar posteriormente la aplicación (al llamar
a showNotify) con la información que previamente había
sido almacenada.

Herramientas de dibujo. La clase Graphics.


Los objetos de la clase Graphics (también a veces llamados contexto
gráfico) contienen las herramientas gráficas necesarias para
dibujar elementos en una pantalla de tipo Canvas.

La clase Graphics permite dibujar figuras geométricas en dos


dimensiones en la pantalla de un dispositivo. Proporciona todas las
herramientas (métodos) necesarias para dibujar texto, imágenes,
líneas, rectángulos, arcos, etc. Dependiendo del tipo de componente

Dibujar elementos en la pantalla. El método paint. que se dibuje se podrán establecer unas u otras propiedades (color,
tamaño, posición, relleno, etc.).
La clase Canvas puede imaginarse como el lienzo que representa la
pantalla. Sobre ese lienzo es donde la aplicación escribirá todo lo que Hay dos formas de obtener un objeto de la clase Graphics:
considere oportuno durante su ejecución. Para ello Canvas dispone
del método abstracto paint que se encarga de dibujar el contenido • Como parámetro del método paint: protected void
de la pantalla. paint (Graphics g). Ese objeto es el que podrás utilizar
para dibujar elementos en la pantalla.
Dado que el método paint es abstracto, para cada subclase
• A través de una imagen mutable (una imagen que se creó
de Canvas que definas deberás implementar ese método. Un
reservando un bloque de memoria y sobre la que luego se
ejemplo trivial de implementación de paint podría ser:
puede obtener un objeto Graphics para poder escribir
sobre ella).

El objeto Graphics que ahora interesa es el primero, esto es, el que


está disponible dentro del método paint de un objeto Canvas. Con él
podrás controlar cualquier píxel que sea dibujado en la pantalla
En este ejemplo cada vez que se muestre el Canvas en la pantalla
(Canvas).
del dispositivo (es decir cuando sea el displayable activo y la
aplicación esté en foreground) se dibujará un rectángulo cuyo vértice Entre los métodos de Graphics habrá muchos que tendrán un
superior izquierdo estará en las coordenadas (5,5) y tendrá una nombre del
anchura y una altura de 10 píxeles. Normalmente el método paint de tipo drawXXX o fillXXX (drawArc, drawLine, drawString, drawRect,
un Canvas será bastante más complejo y mostrará unos drawRoundRect, fillArc, fillTriangle, etc.) que permitirán dibujar
elementos u otros en función del estado en que se encuentre la elementos (figuras geométricas, textos, etc.), otros del
aplicación en ese momento. estilo getXXX (getColor, getDisplayColor, getFont, etc.)
o setXXX (setColor, setFont, etc.), que servirán para obtener o
Como puedes observar, el método paint recibe como parámetro un
establecer determinados atributos y así sucesivamente. Ésas serán
objeto de la clase Graphics. Éste es el objeto que se utiliza para
las herramientas que utilizarás para dibujar sobre el Canvas.
dibujar sobre la pantalla (Canvas).

El método paint nunca debe de ser invocado desde la aplicación (el


resultado puede ser impredecible), sino que será el administrador
de aplicaciones (AMS) del dispositivo quien realizará las llamadas a
paint cuando sea necesario (por ejemplo cuando el midlet estaba en
pausa y vuelve estar en ejecución, teniéndose que volver a dibujar la
Sistema de coordenadas Mediante los métodos getWidth y getHeight de la clase Canvas se

Una posición en la pantalla del dispositivo está definida por dos puede obtener el alto y el ancho de la pantalla.

coordenadas x e y que indican el desplazamiento horizontal y


vertical respecto a un origen (0,0) situado en la esquina superior
izquierda. La dirección del eje vertical es positiva hacia abajo y la del
eje horizontal es positiva hacia la derecha. Es decir, que
incrementando los valores de x e y nos desplazaremos
verticalmente hacia abajo y lateralmente hacia la derecha.

El sistema de coordenadas representa posiciones entre píxeles, no


los propios píxeles. Por tanto el primer píxel en la esquina superior
izquierda de la pantalla sería el cuadrado definido por las
coordenadas (0,0), (1,0), (0,1) y (1,1). En el siguiente ejemplo se muestra cómo dibujar unos ejes de
coordenadas en la pantalla:

El origen de coordenadas puede ser modificado mediante el Algunos de los métodos proporcionados por la clase Graphics para
método translate de la clase Graphics, provocando un el manejo de los colores son:
desplazamiento de todos los objetos en la pantalla.
int getColor () Devuelve el color actual.
Reflexiona int getGreenComponent () Devuelve el componente R (rojo), G
int getRedComponent () verde) o B (azul) del color actual.
Piensa en lo útil que puede resultar el método translate para int get BlueComponent ()
producir scrolls o desplazamientos por la pantalla. int setColor (int RGB) Establece el color actual.
int setColor (int red, int green,
Manejo de los colores. int blue)
La clase Graphics proporciona un modelo de coloreado de 24 bits de void setGrayScale (int valor) Establece un valor en la escala de
tipo RGB con 8 bits para cada componente de color (rojo, verde, azul). grises.
Sin embargo no todos los dispositivos soportan colores de 24 bits.
La clase Display proporciona métodos con los que se podrá saber si
Aquí tienes algunos ejemplos de uso de setColor:
el aparato cuenta con una pantalla a color (isColor) o para obtener
el número de colores soportados (numColors).

Algunos de los métodos proporcionados por la clase Display para el


manejo de los colores son:

Método Descripción
int getColor (int Devuelve el color actual de alguno de los elemen-
Una vez seleccionado un color determinado para el "pincel", cualquier
especificadorCo- tos indicados en el especificador de color indi-
cosa que se dibuje en la pantalla será con ese color hasta que se
lor) cado (color del fondo, de la tinta, del borde, etc.).
establezca otro color.
boolean isColor () Indica si el dispositivo tiene pantalla a color.
int numColors () Devuelve el número de colores (si isColor() de- Por ejemplo:
vuelve true) o de niveles de gris (si isColor() de-
vuelve false) que pueden ser representados en
el dispositivo.
Escritura de texto. Dibujo de figuras geométricas
Para dibujar texto en la pantalla la clase Graphics proporciona una Las principales figuras geométricas que la clase Graphics permite
serie de herramientas (entre ellas el método drawString). dibujar son:

El aspecto que tendrá el texto al mostrarse en la pantalla podrá ser • Líneas. Mediante el método drawLine.
configurado a través de la clase Font, permitiendo jugar con la • Rectángulos. Mediante los
combinación de tres tipos de atributos: aspecto, estilo y tamaño. métodos drawRect, drawRoundRect, fillRect, fillRoundRec
t.
Algunos de los métodos proporcionados por la clase Graphics para
el manejo de texto son: • Arcos. Mediante los métodos drawArc, fillArc.
• Triángulos. Mediante el método fillTriangle.
Método Descripción
void drawChar (char caracter, int x, int Dibuja un carácter en la Según el tipo de figura se podrán tener unas u otras características
y, int anchor) pantalla (con el color y específicas (líneas continuas o discontinuas, rellenos, esquinas
fuentes actuales). redondeadas, etc.).
void drawChars (char[] caracter, int Dibuja un conjunto de ca-
desplazamiento, int longitud, int x, int y, racteres en la pantalla (con Algunos de los métodos proporcionados por la clase Graphics para
int anchor) el color y fuentes actuales). el dibujo de figuras geométricas son:
void drawString (String cadena, int x, Dibuja una cadena de ca-
Método Descripción
int y, int anchor) racteres en la pantalla (con
void drawArc (int x, int y, int ancho, Dibuja un arco circular o elíp-
el color y fuentes actuales).
int alto, int startAngle, int arcAngle) tico.
void drawSubstring (String cadena, int Dibuja una cadena de ca-
void drawLine (int x1, int y1, int x2, int Dibuja una línea.
desplazamiento, int longitud, int x, int y, racteres en la pantalla (con
y2)
int anchor) el color y fuentes actuales).
void drawRect (int xi, int y, int ancho, Dibuja un rectángulo.
Font getFont () Obtiene la fuente actual.
int alto)
void setFont (Font fuente) Establece la fuente actual.
void drawRoundRect (int x, int y, int Dibuja un rectángulo con las
ancho, int alto, int arcAncho, int ar- esquinas redondeadas.
Para obtener una fuente podemos utilizar el método cAlto)

estático getFont de la clase Font que devuelve un objeto de tipo Font. void fillArc (int x, int y, int ancho, int Rellena un arco circular o elíp-
alto, int startAngle, int arcAngle) tico (con el color actual).
Para ello se debe indicar una especificación del tipo de fuente que se
void fillRect (int xi, int y, int ancho, int Rellena un rectángulo (con el
desea (atributos face, style y size; o aspecto, estilo y tamaño), por
alto) color actual).
ejemplo:
void fillRoundRect (int x, int y, int an- Rellena un rectángulo con las
cho, int alto, int arcAncho, int arcAlto) esquinas redondeadas (con el
color actual).
void fillTriangle (int x1, int y1, int x2, Rellena un triángulo (con el co-
Una vez que se disponga de un objeto de la clase Font, habrá que
int y2, int x3, int y3) lor actual).
asociarlo al objeto Graphics que será quien ejecutará alguno de sus
métodos (por ejemplo drawString) para dibujar el texto. Esa
asociación se puede llevar a cabo mediante el método setFont de la Dibujo de imágenes. Uso del contexto gráfico.
clase Graphics. Por ejemplo: Al igual que en algunos componentes del API gráfico de alto nivel
(formularios, listas, etc.), también pueden insertarse imágenes en
un Canvas. En este caso pueden incrustarse imágenes mutables e
inmutables.
Para indicar la posición en la que se desea que se dibuje el texto los
métodos de escritura de la clase Graphics disponen del Lo primero que hay que hacer es crear un objeto de la
parámetro anchor (anchor point o punto de anclaje). clase Image para alojar la imagen, a continuación se podrá
manipular esa imagen si se considera oportuno (y si es mutable) y
La definición de un punto de anclaje consiste en la combinación de por último ya se podría mostrar en pantalla mediante el uso del
una constante horizontal (LEFT, HCENTER, RIGHT) con una constante método drawImage de la clase Graphics.
vertical (TOP, BASELINE, BOTTOM) mediante el operador lógico OR.
En el ejemplo anterior la Para imágenes inmutables:
expresión BASELINE | HCENTER significaría que el texto se desea
• Se crea la imagen (por ejemplo a partir de un archivo
anclar de manera centrada tanto horizontal (HCENTER) como
gráfico)
verticalmente (BASELINE). Para obtener más información sobre
los puntos de anclaje puedes consultar la documentación
de MIDP sobre la clase Graphics.
• Se muestra la imagen en un Canvas (usando el método Desplazamiento del origen de coordenadas.
drawImage de la clase Graphics dentro del método paint Como ya has visto al estudiar el sistema de coordenadas, el
del Canvas): origen puede ser movido mediante el método translate de la
clase Graphics, provocando un desplazamiento de todos los
objetos en la pantalla.

Los métodos que la clase Graphics proporciona para trabajar


con el desplazamiento del origen de coordenadas son:

Para imágenes mutables: Método Descripción


void translate (int x , Desplaza el origen de coordenadas
• Se crea la imagen mutable (reserva de espacio int y) (0,0) del objeto Graphics.
únicamente): int getTranslateX () Obtiene el desplazamiento actual de la
coordenada X.
int getTranslateY () Obtiene el desplazamiento actual de la
coordenada Y.

• Se crea y manipula el contenido de la imagen (mediante


el uso de las herramientas proporcionadas por la clase
Uniéndolo todo
Graphics: rectángulos, triángulos, líneas, arcos, textos, etc.).
Ahora se trata de que experimentes con todos los elementos
Para ello hay que obtener el contexto gráfico de la imagen
y posibilidades que has visto para que te vayas familiarizando
(un objeto de tipo Graphics).
con ellos y puedas ir creando pantallas en las que tú decidirás
todos los detalles (posición de los elementos, colores, dibujos,
imágenes, movimiento, teclas a las que responder, etc.).

Al igual que con la API de alto nivel, te volvemos a recomendar


una adecuada planificación de las pantallas que va a tener tu
aplicación así como del contenido de las propias pantallas.
• Se muestra la imagen en un Canvas (usando el
Dedicar algo de tiempo al diseño de cada pantalla te ayudará a
método drawImage de la clase Graphics dentro del
comprender mejor qué es lo que necesitas realmente y te
método paint del Canvas):
ahorrará bastantes quebraderos de cabeza.

En el caso del trabajo a bajo nivel tendrás que planificar qué


imágenes van a hacer falta, si van a requerir movimiento
algunas de ellas, cuáles son los colores más adecuados, qué
figuras geométricas vas a colocar en cada momento, qué
pulsaciones de teclas (o eventos de puntero si se da el caso)
Recorte de regiones.
será necesario gestionar, si necesitas acciones de juego o no,
Cada vez que se realiza una llamada al método paint, el etc. Todo ello por supuesto añadido a la planificación de
dispositivo tiene que redibujar toda la pantalla. Si pudieras elementos necesarios en alto nivel (formularios, listas, alertas,
dibujar únicamente una parte de la pantalla (la que va a ser elementos de los formularios, etc.). Si todo esto lo piensas y
modificada) podría obtenerse un importante ahorro en tiempo. diseñas tranquilamente antes de comenzar a escribir líneas de
código, ganarás mucho en la calidad de la aplicación que
Algunos de los métodos proporcionados por la
desarrolles ,así como en el tiempo y el esfuerzo que tengas que
clase Graphics para trabajar con el recorte (clip) de regiones de
dedicarle.
la pantalla son:

Método Descripción Una interfaz de usuario para juegos


void setClip (int x, int y, int Establece el rectángulo (región) de A partir del perfil MIDP 2.0 se dispone del
ancho, int alto) recorte. paquete javax.microedition.lcdui.game el cual proporciona un
void clipRect(int x, int y, int Realiza la intersección entre el re- conjunto de herramientas muy útiles para el desarrollo de
width, int height) corte actual y el rectángulo espe- juegos en 2D.
cificado para crear una nueva re-
gión de recorte. Es importante recordar que las clases especializadas para el
int getClipX() Obtiene la coordenada x de la re- desarrollo de juegos fueron incorporadas a partir de la versión
gión de recorte actual. 2.0 del perfil MIDP, así que aquellos móviles que sólo soporten
int getClipY() Obtiene la coordenada y de la re- la versión 1.0 no podrán ejecutarlas.
gión de recorte actual.
int getClipHeight() Obtiene la altura de la región de La idea fundamental que introduce al API especializado para
recorte actual. juegos es que la pantalla está compuesta de capas (layers),
int getClipWidth() Obtiene la anchura de la región de cada una de las cuales puede ser tratada por separado.
recorte actual. También se proporciona una forma de trabajar con vistas
del mapa del juego (los fragmentos que se van a mostrar en Las bibliotecas escritas en lenguaje C incluyen un
cada momento en pantalla) para que sea más fácil gestionar administrador de interfaz gráfica (surface manager),
los movimientos de los personajes a lo largo del mapa. un framework OpenCore, una base de datos relacional SQLite,
una Interfaz de programación de API gráfica OpenGL ES 3.1 3D,
Las clases gráficas de bajo nivel para juegos se encuentran en
un motor de renderizado WebKit, un motor gráfico SGL, SSL
el paquete javax.microedition.lcdui.game. Se trata de cinco
y una biblioteca estándar de C Bionic. El sistema operativo está
nuevas clases cuyo objetivo fundamental es mejorar el
compuesto por 12 millones de líneas de código, incluyendo 3
rendimiento de la aplicación y facilitar el trabajo del
millones de líneas de XML, 2,8 millones de líneas de lenguaje C,
desarrollador de la aplicación:
2,1 millones de líneas de Java y 1,75 millones de líneas de C++.

Clase Descripción
Hasta la versión 5.0, Android utiliza Dalvik como máquina
GameCanvas Subclase de Canvas que proporciona la funciona-
virtual con la compilación just-in-time (JIT) para ejecutar Dalvik
lidad básica para la pantalla de un juego.
"dex-code" (Dalvik ejecutable), que es una traducción de Java
Layer Clase que representa un elemento visual en el
bytecode. Android 4.4 introdujo el ART (Android Runtime) como
juego. Estos elementos pueden ser de las cla-
un nuevo entorno de ejecución, que compila el Java bytecode
ses Sprite o TiledLayer. Proporciona herramien-
durante la instalación de una aplicación. Se convirtió en la única
tas para detectar colisiones entre capas.
opción en tiempo de ejecución en la versión 5.0.
LayerManager Clase que permite gestionar las distintas capas
visibles (objetos Layer) en cada momento en el
Creación de un proyecto en Android Studio.
juego.
Pasos para crear el primer proyecto Android Studio:
Sprite Layer animado que puede estar compuesto de
una o varias imágenes.
Una vez que iniciamos el entorno del Android Studio aparece el
TiledLayer Clase que representa una malla de celdas (o bal-
diálogo principal:
dosas: tiles) que puede servir para representar
escenarios o mapas de juego.

Utilización de la API de juegos


La clase GameCanvas será la clase base para las pantallas
(Canvas) de tu aplicación. Es una clase abstracta que hereda
de Canvas, así que tus pantallas de juego heredarán ahora
de GameCanvas en lugar de hacerlo directamente de Canvas.

Además de los métodos de Canvas, esta clase proporciona


algunas funcionalidades adicionales para el desarrollo de juegos
como:
Paso 1. Crearemos un nuevo proyecto en Android
• Creación de animaciones rápidas y sin parpadeo mediante Studio. Elegimos la opción "Start a New Android Studio project"
una sincronización apropiada de los gráficos en la pantalla.
Ahora aparecerán una serie de ventanas para configurar el
proyecto, el primer diálogo debemos especificar el Nombre de
• Posibilidad de examinar el estado de las teclas del
la aplicación, la url de nuestra empresa (que será el nombre
dispositivo.
del paquete que asigna java para los archivos fuentes) y la
A partir de los objetos de esta clase y todos los demás ubicación en el disco de nuestro proyecto:
componentes del paquete Game (capas, sprites, escenarios,
etc.) podrás comenzar a diseñar pequeños juegos de prueba.

Android
En la primera unidad de este módulo ya hemos
visto Android (arquitectura, versiones, características e
instalación, entre otros aspectos) y vimos que Android es un
sistema operativo basado en el núcleo Linux. Fue diseñado
principalmente para dispositivos móviles con pantalla táctil,
como teléfonos inteligentes, tablets o tabléfonos; y también
para relojes inteligentes, televisores y automóviles.

La estructura del sistema operativo Android se compone de


aplicaciones que se ejecutan en un framework Java de
aplicaciones orientadas a objetos sobre el núcleo de las
bibliotecas de Java en una máquina virtual Dalvik con
compilación en tiempo de ejecución hasta la versión 5.0, luego
cambio al entorno Android Runtime (ART).
Paso 2. Seleccionar los factores y el nivel de API. básico para que nuestra aplicación tenga una ventana). Por lo
tanto, como puedes ver la siguiente pantalla que te mostramos
La siguiente ventana te permite seleccionar los factores de
a continuación te permite seleccionar un tipo de actividad para
forma con el apoyo de su aplicación, tales como teléfono,
añadir a su aplicación. En esta pantalla se muestra un conjunto
tableta, TV, desgaste y Google Glass. Los factores de forma
diferente de actividades para cada uno de los factores de forma
seleccionados se convierten en los módulos de aplicaciones
que seleccionaste en el paso anterior. Si escoges "Add no
dentro del proyecto. Para cada factor de forma, también puede
Activity" debes hacer clic a terminar la creación del proyecto.
seleccionar el nivel de API para esa aplicación. Para obtener
más información, puedes hacer clic en Ayuda para elegir:

En el segundo diálogo procedemos a especificar la versión de


Paso 4. La siguiente pantalla te permite configurar la
Android mínima donde se ejecutará la aplicación que
actividad para agregar a su aplicación, como se muestra en la
desarrollemos. Deberemos escoger la adecuada a la versión que
imagen siguiente para personalizar la pantalla de actividad.
escojamos.
Debes introducir el nombre de la actividad, el nombre de la

La ventana de Distribución de la plataforma Android se presentación y el título de la actividad. A continuación, hacer clic

muestra la distribución de los dispositivos móviles que en Finalizar.

funcionan con cada versión de Android, como se muestra en la


imagen anteriormente mostrada. Debes hacer clic en una API
de nivel para ver una lista de las características introducidas
en la versión correspondiente de Android. Ésto le ayuda a elegir
el nivel mínimo API que tiene todas las características que
necesita de sus aplicaciones, por lo que puede llegar a tantos
dispositivos como sea posible. A continuación, debes seleccionar
los dispositivos y versiones y hacer clic en Aceptar, tal y comoo
se muestra en la imagen que se muestra a continuación.

Paso 5: Desarrolla tu APP. Android Studio crea la estructura


predeterminada para tu proyecto y abre el entorno de
desarrollo. Si tu aplicación es compatible con más de un factor
de forma, Android Studio crea una carpeta con los archivos de
Paso 3.- Añadir una actividad: módulo de fuente completo para cada uno de ellos, tal y como

El tercer diálogo especificamos el esqueleto básico de nuestra se muestra en la imagen siguiente.

aplicación, seleccionaremos "Empty Activity" si tenemos el


Android Studio a partir de la versión 2.0 o "Blank Activity" si
tenemos el Android Studio 1.x (es decir se generará el código
Tenemos finalmente creado nuestro primer proyecto en
Android Studio y podemos ahora ver el entorno del Android
Studio para codificar la aplicación:

Estructura de una aplicación en Android al realizar un Emulador de Android Studio.


proyecto. Android Studio genera todos los directorios y archivos
La estructura de una aplicación en Android al crear un proyecto básicos para iniciar nuestro proyecto, los podemos ver en el
está formada por archivos y directorios y los más importantes lado izquierdo del entorno de desarrollo:
son los siguientes:

AndroidManifest.xml- El archivo Manifest es el más


importante para nuestra aplicación, es la columna vertebral de
nuestro proyecto, en él declaramos todas las actividades del
proyecto, los permisos, versiones del SDK que usamos y un
montón de cosas que vamos a ver más en detalle.

src– Aquí van las clases de nuestra aplicación, es decir los


archivos .java.

gen- Son archivos que genera Java y por ninguna razón los
debemos tocar. Si lo hacemos, ya no van a servir y puede que
ni el proyecto sirva para más adelante. Cada vez que
compilamos, Java se encarga de actualizarlo y de generarlo de
nuevo. Dentro de gen encontramos 2 archivos: el BuildConfig y
R. El archivo R es el archivo que tiene los identificadores de todo
lo que tiene la aplicación, por ejemplo imágenes, campos de
texto, botones, etc. Java le asigna un identificador y nosotros
no tenemos que preocuparnos por él, ya que le colocamos un
nombre común que podamos recordar y Java sabe cómo se
llama para nosotros.

assets- Este directorio contiene recursos de ayuda para la


aplicación, audio, videos, bases de datos, la carpeta "assets" y la
carpeta "res" sirven ambas para guardar recursos, pero la
diferencia es que los que se encuentran en "assets" no generan
un identificar en el archivo R que vimos se encuentra en el
directorio "gen".

bin- Aquí tenemos archivos generados por el mismo Java, que La interfaz visual de nuestro programa para Android se
en realidad no los utilizamos y tampoco debemos manipular, almacena en un archivo XML en la carpeta res, subcarpeta
son archivos binarios como bien dice su nombre. layout y el archivo se llama activity_main.xml.

libs- Se encuentran librerías externas que necesita el proyecto. Al seleccionar este archivo el Android Studio nos permite
visualizar el contenido en "Design" o "Text" (es decir en vista de
res- El directorio "res" contiene todos los recursos de la
diseño o en vista de código):
aplicación.
Vista de diseño:
Para ejecutar la aplicación presionamos el triángulo verde o
seleccionamos del menú de opciones "Run -> Run app" y en este
diálogo procedemos a dejar seleccionado el emulador por
defecto que aparece (Nexus 5X) y presionamos el botón "OK"
Si vemos el código será el siguiente:
(si no tienes ningún emulador puedes crear uno), tal y como se
muestra en la siguiente imagen:

Posteriormente, aparecerá el emulador de Android en pantalla


Android Studio inserta un control de tipo RelativeLayout que (el arranque del emulador puede llevar más de un minuto),
permite ingresar controles visuales alineados a los bordes y a es importante tener en cuenta que una vez que el emulador se
otros controles que haya en la ventana (más adelante ha arrancado no lo debemos cerrar cada vez que hacemos
analizaremos este Layout). cambios en nuestra aplicación o codificamos otras
aplicaciones, sino que volvemos a ejecutar la aplicación con los
Así. por ejemplo, antes de probar la aplicación en
cambios y al estar el emulador corriendo el tiempo que tarda
el emulador de un dispositivo Android procederemos a hacer
hasta que aparece nuestro programa en el emulador es muy
un pequeño cambio a la interfaz que aparece en el móvil, por
reducido.
ejemplo si hemos hecho la práctica de "Hola mundo"
borraremos la label que dice "Hello World" (simplemente Cuando terminó de cargarse el emulador debe aparecer
seleccionando con el mouse dicho elemento y presionando la nuestra aplicación ejecutándose:
tecla delete) y de la "Palette" arrastraremos un "Button" al
centro del móvil y en la ventana "Properties" estando
seleccionado el "Button" cambiaremos la propiedad "text" por la
cadena "Hola Mundo", tal y como se muestra en la siguiente
imagen:
Views. usaremos 40dp como unidad de medida, dp significa:

Una Vista o View es un componente que permite controlar la Densidad de píxeles independientes, una unidad abstracta

interacción del usuario con la aplicación. Éstos son muy que se basa en la densidad física de la pantalla. Esta

similares a los controles SWING de Java, unidad es perfecta para buscar la compatibilidad con

como Labels, Buttons, TextFields, Checkboxes, etc. TODAS las pantallas de móvil o tables, ya que es una
medida proporcional.
LOS VIEWS SON ORGANIZADOS DENTRO DE LOS LAYOUTS PARA QUE EL
Otras Unidades que podemos usar (aunque yo aconsejo dp)
USUARIO COMPRENDA LOS OBJETIVOS DE LA ACTIVIDAD.
px. Píxeles, corresponde a píxeles reales en la pantalla.
Los Grupos de Vistas (Layouts) más utilizados son los en. Cm - basado en el tamaño físico de la pantalla.
siguientes: mm. Milímetros - en función del tamaño físico de la pantalla.
pt. Puntos - 1/72 de una pulgada en función del tamaño físico
• LinearLayout de la pantalla.
o Agrupa los elementos en una sola línea, que sp. Escala de píxeles independientes - esto es como la unidad
puede ser vertical u horizontal. de DP, pero también es escalado por la preferencia del usuario
• RelativeLayout tamaño de la fuente. Se recomienda utilizar esta unidad al
o Los elementos se disponen en relación entre especificar tamaños de fuente, por lo que se ajusta tanto para
ellos y los márgenes. Es la más flexible, y la más la densidad de pantalla y preferencias del usuario.
utilizada.
• ScrollView
• La constante FILL_PARENT que indica que la vista
intentará ser tan grande como su padre (menos el
o Se utiliza para vistas que no caben en pantalla.
padding), es decir se ajusta a tope!
Sólo puede contener una vista o grupo de vistas,
y añade automáticamente las barras de • La constante WRAP_CONTENT que indica que la vista
desplazamiento. intentará ser lo suficientemente grande para mostrar su
contenido (más el padding).
• TableLayout
o Agrupa los elementos en filas y columnas. android:layout_weight: Esta propiedad nos va a permitir dar a
Contiene elementos TableRow, que a su vez los elementos contenidos en el layout unas dimensiones
contienen los elementos de cada celda. proporcionales entre ellas. Si incluimos en un LinearLayout
• FrameLayout vertical dos cuadros de texto (EditText) y a uno de ellos le
o Está pensada para contener una sola vista. Si establecemos un layout_weight=”1” y al otro un
se añaden más, todas se alinean en la esquina layout_weight=”2” conseguiremos como efecto que toda la
superior izquierda, solapándose. superficie del layout quede ocupada por los dos cuadros de texto
• AbsoluteLayout y que además el segundo sea el doble (relación entre sus
o Está desaprobado desde la versión 1.5 de propiedades weight) de alto que el primero, si ponemos 1 para
Android. En este contenedor, los elementos se los dos, el tamaño será exactamente igual. Esto se usa mucho,
referencian con coordenadas absolutas ya que así nos aseguramos una proporcionalidad para todos
partiendo de la esquina superior izquierda. Se ha los tamaños de pantalla.
desaprobado porque no se adapta a pantallas
android:id:
de diferentes tamaños, que se popularizaron a
Se trata de un número entero que sirve para identificar cada
partir de Android 1.5.
objeto view de forma única dentro de nuestro programa,
Los Layouts tienen propiedades comunes a todos. Veamos los cuando lo declaramos a través de un xml de resource podemos
propósitos de los atributos usados con mayor frecuencia al hacer referencia a la clase de recursos R usando una @, esto es
diseñar un layout: imprescindible, ya que si no no podremos identificar nuestros
elementos en nuestro programa para después usarlos y/o
atributos height, width (Altura y Ancho): Representa la
modificarlos, veamos algunos ejemplos:
dimensión de longitud vertical de un View. Puedes asignarle
valores absolutos en dps, si dependiendo de las métricas de • android:id=”@id/boton”. Hace referencia a un id ya existente
diseño que tengas ó usar los asociado a la etiqueta “boton”, esto se usa para cuando
valores match_parent y wrap_content. El primero ajusta la usamos los Layout Relativos, ya que para ubicar los
dimensión a las medidas del contenedor padre y el segundo lo elementos, lo hacemos indicando por ejemplo que un
ajusta al contenido del View, por tanto esta propiedad es botón lo insertamos a la derecha de otro, pues bien ese
importante para determinar el Alto y el Ancho de los controles otro se pone así.
y Layouts, ya que para que Android sepa dibujar un objeto View • android:id=”@+id/boton2”. Esto crea una nueva etiqueta en
debemos proveerle estos datos, y podemos hacerlo de 3 la clase R llamada “boton2”.
formas:
android:layout_gravity="center": Esta propiedad es la que se
• android:layout_width="40dp". Representa el ancho de un usa para centrar, es la 'gravedad' una vez mas cuando estén
view. Indicando un número exacto que definamos, entre las comillas, pulsa Control+Espacio para ver todas las
opciones que te da este control, además las puedes combinar, Frecuentemente usaremos el Relative Layout para nuestros
es decir, Center_Horizontal|Top. <-- Esto te lo centra horizontal proyectos debido a la referencia relativa que podemos asignar
y lo ajusta en vertical arriba. a los componentes hijos. Cuando digo relativa me refiero a que
no expresaremos la ubicación de los componentes de esta
Para el resto de Atributos, cada elemento tendrá los propios,
forma:
basta con poner el cursor dentro de la etiqueta del Layout que
estemos colocando y pulsar las teclas Ctrl+Espacio para que “El botón OK estará ubicado en el punto (200,120) del layout y
Eclipse te recomiende las propiedades del elemento que sus dimensiones son 200×30 dp“
estamos insertando.
A esa definición de atributos se le llama definición absoluta, y
Layouts. describe las medidas numéricas para el View.

¿Qué tipos de Layouts existen en el Desarrollo Android?


A diferencia de esa declaración, dentro de un RelativeLayout

Si queremos combinar varios elementos de tipo vista usaremos expresiones como la siguiente:
tendremos que utilizar un objeto de tipo Layout. Un Layout es
“El botón OK estará ubicado a la izquierda del extremo derecho
un contenedor de una o más vistas y controla su
del TextView 2 y sus dimensiones serán ajustadas al padre“
comportamiento y posición. Hay que destacar que
un Layout puede contener a otro Layout y que es un ¿Qué significa eso?, quiere decir que no importa de qué tamaño
descendiente de la clase View. sea la pantalla o que densidad manejes, el botón se ajustará
relativamente a las condiciones que se le han impuesto, lo cual
Existen varios y su uso depende de la necesidad de cada
permite una mejor experiencia para distintos usuarios sin
persona. Veamos la definición de los más populares y fáciles
importar las características de su dispositivo.
de usar:
Debes conocer
• LinearLayout: El Linear Layout es el más sencillo. Dentro
de él los elementos son ubicados en forma linear a través Para editar los layouts o ficheros de diseño en XML.
de columnas o filas. Posee un atributo que permite
En el explorador del proyecto abre el
modificar su orientación, ya sea para presentar los
fichero res/layout/activity_main.xml. Verás que en la parte
elementos horizontal o Verticalmente. Dispone los
inferior de la ventana central aparecen dos
elementos en una fila o en una columna.
lengüetas: Design y Text. Podrás usar dos tipos de diseño:
• WebView: Este View fue creado para mostrar el contenido
editar directamente el código XML (segunda lengüeta) o
con formato web.
realizar este diseño de forma visual (primera lengüeta).
• RelativeLayout: Este es el layout más recomendado a
Veamos cómo se realizaría el diseño visual. La herramienta de
usar, ya que los componentes dentro de él se pueden
edición de layouts se muestra a continuación:
organizar de forma relativa entre sí. Dispone los
elementos en relación a otro o al padre.

Hay otros Layouts también utilizados en Android como:

• TableLayout: Distribuye los elementos de forma tabular.


• AbsoluteLayout: Posiciona los elementos de forma
absoluta.
• FrameLayout: Permite el cambio dinámico de los
elementos que contiene.

Observa la siguiente imagen:

En el marco derecho se visualiza una lista con todos los


elementos del layout. Este layout tiene solo dos vistas:
un RelativeLayout que contiene un TextView. En el marco
central aparece una representación de cómo se verá el
resultado.

En la parte superior aparecen varios controles para


representar esta vista en diferentes configuraciones. Cuando
diseñamos una vista en Android, hay que tener en cuenta que
desconocemos el dispositivo final donde será visualizada y la
configuración específica elegida por el usuario. Por esta razón, Las definiciones XML para los layouts se guardan dentro del
resulta importante que verifiques que la vista se ve de forma subdirectorio layout.
adecuada en cualquier configuración. En la parte superior, de
izquierda a derecha, encontramos los siguientes botones:
opciones de previsualización en fase de diseño, ipo de dispositivo
(tamaño y resolución de la pantalla), orientación horizontal
(landscape) o vertical (portrait), cómo se verá nuestra vista
tras aplicar un tema, la actividad asociada, la configuración
regional (locale), a versión de Android. etc.
Para crear un layout nuevo, solo presiona click derecho y ve
Para editar un elemento, selecciónalo en el marco de la a New > Layout resource file.
derecha (Outline) o pincha directamente sobre él en la ventana
de previsualización. Al seleccionarlo, puedes modificar alguna de
sus propiedades en el marco Properties, situado debajo
de Outline. Echa un vistazo a las propiedades disponibles
para TextView y modifica alguna de ellas. En muchos casos te
aparecerá un desplegable con las opciones disponibles.

El marco de la izquierda te permite insertar de forma rápida


nuevas vistas al layout. Puedes arrastrar cualquier elemento a
la ventana de previsualización o al marco Outline. En el anexo
D se ha incluido una lista con las vistas disponibles.

¿Cómo crear un Layout?


Debido a la existencia de los recursos en Android, los layouts
pueden ser creados a través de archivos XML o con código Java
de forma programática.

Cuando creas un recurso xml para el layout, mantienes por


separado una gran cantidad de código de tus clases principales.
Lo que aumenta la comprensión de la estructura del proyecto
y además reduce la cantidad de tiempo para el diseño de UI.

En cuanto a la creación dinámica de layouts, debes referirte a


las clases ViewGroup y View. Un ViewGroup es un elemento
visual que contiene a otros views. Por lo que tienes que usar
los métodos apropiados para añadir correctamente los hijos y Normalmente cuando creas un nuevo proyecto en Android
crear el diseño que deseas. Studio con una actividad en blanco, se
genera automáticamente un layout. Algo como:
La mayor parte del tiempo usaremos el estilo declarativo con
XML para crear nuestras interfaces. No obstante, en algunas
ocasiones requeriremos modificar alguna propiedad de los
layouts en tiempo real.

Por ejemplo, si deseáramos desaparecer un texto de la interfaz


que ya no es necesario, deberíamos acudir al estilo
programático para deshabilitar la visibilidad de dicho elemento.

Esto quiere decir, que declarar los elementos de la UI en


archivos XML es útil para crear todo el aspecto visual que se En este caso, tenemos un elemento raíz

usará en la app de forma estática. llamado RelativeLayout, el cual contiene un texto con ciertas
alineaciones establecidas.
Pero si los elementos cambian por algún motivo cuando la app
está en funcionamiento, entonces se requiere código Java para Cada recurso del tipo layout debe ser un archivo XML, donde el

modificar dinámicamente los elementos de la UI. elemento raíz solo puede ser un ViewGroup o un View. Dentro
de este elemento puedes incluir hijos que definan la estructura
Ambos métodos no son excluyentes. del diseño.

Crear Layouts en Android Studio Tal y como hemos visto en el apartado anterior, algunos de los
view groups más populares
Dentro de la estructura de un proyecto en Android
son: LinearLayout, FrameLayout, RelativeLayout, TableLayout
Studio existe el directorio res para el almacenamiento de
y GridLayout.
recursos de la aplicación que se está desarrollando.
Cargar layout XML En Android— Al tener definido tu recurso, ya es TableLayout distribuye los elementos de forma tabular. Se
posible inflar su contenido en la actividad. Para ello usa el utiliza la etiqueta <TableRow> cada vez que queremos insertar
método setContentView() dentro del controlador onCreate(). una nueva línea. Aquí tienes un ejemplo:

Es necesario pasar la referencia del layout que existe dentro del


archivo R.java. En el código anterior, cargamos un layout
llamado actividad_principal.xml.

View group: LinearLayout.

View group: RelativeLayout.

LinearLayout es uno de los Layout más utilizado en la


práctica. Distribuye los elementos uno detrás de otro, bien de
forma horizontal o vertical. Aquí tienes un ejemplo:

RelativeLayout permite comenzar a situar los elementos en


cualquiera de los cuatro lados del contenedor e ir añadiendo
nuevos elementos pegados a estos. Aquí tienes un ejemplo:

View group: TableLayout.


View group: AbsoluteLayout.

AbsoluteLayout permite indicar las coordenadas (x,y) donde


queremos que se visualice cada elemento. No es recomendable
utilizar este tipo de Layout. La aplicación que estamos
diseñando tiene que visualizarse correctamente en dispositivos
con cualquier tamaño de pantalla. Para conseguir esto, no es
una buena idea trabajar con coordenadas absolutas. De hecho, EditText y CheckBox
este tipo de Layout ha sido marcado como obsoleto. Aquí
EditText es un campo de texto (Text field) editor de texto
tienes un ejemplo:
plano. Un campo de texto permite que al usuario escribir texto
en la aplicación. Puede ser de una sola línea o de múltiples
líneas. Si toca un campo de texto muestra automáticamente
el teclado.

Además de escribir, los campos de texto permiten una variedad


de otras actividades, como la selección de texto (cortar, copiar,
pegar) y datos de consulta a través de autorealización.

Jerarquía de EditText.

A veces es importante conocer la jerarquía de un


elemento, pues sus subclases manejan muchos atributos
similares.

Subclases directas de EditText.

View group: FrameLayout.


AutoCompleteTextView, ExtractEditText, SearchEditText.

Subclases indirectas de EditText.

MultiAutoCompleteTextView.

Principales atributos de TextView.

android:width. Permite establecer el ancho del TextView,


los valores del atributo width pueden ser unidades flotantes
(decimales) seguidas de su unidad de medida ejemplo 20.4 dp,
las unidades son px (pixeles), sp (escala basada en el tamaño
FrameLayout posiciona las vistas usando todo el contenedor, de la fuente), dp (densidad independiente de
sin distribuirlas espacialmente. Este Layout suele utilizarse pixeles), in (pulgadas), mm (milimetros), también se puede
cuando queremos que varias vistas ocupen un mismo lugar. usar fill_parent (llenar el tamaño del
Podemos hacer que solo una sea visible, o superponerlas.Para padre), match_parent (llenar el resto del
modificar la visibilidad de un elemento utilizaremos la padre), wrap_content (ajustarse al contenido).
propiedad visibility.

android:height. Permite establecer el alto del TextView,


los valores del atributo height pueden ser unidades flotantes
(decimales) seguidas de su unidad de medida ejemplo 20.4 dp,
las unidades son px (pixeles), sp (escala basada en el tamaño
de la fuente), dp (densidad independiente de android:inputType. Permite establecer el tipo de datos o valores
pixeles), in (pulgadas), mm (milimetros), también se puede que va a admitir el campo de texto, entre los valores del del
usar fill_parent (llenar el tamaño del campo de texto están números enteros, números decimales,
padre), match_parent (llenar el resto del fechas, contraseñas, direcciones de correo electrónico,
padre), wrap_content (ajustarse al contenido). direcciones postales, números de teléfono, etc.

Sirve para optimizar una aplicación ayudando a decidir el


android:hint. Indica el texto a mostrar en el la vista (EditText).
método y tipo de datos que se va a introducir, pues despliega
mientras el campo se encuentra vacío, ejemplo: un campo que
el tipo de teclado necesario para facilitar la usabilidad. Puede
indique "nombre" y al comenzar a escribir el valor "nombre"
ser uno o más tipos de datos separados por el operador "|"
desaparecerá. El valor de este atributo debe ser de tipo String.
("barra") algunos de los valores son: text, textPassword, date,
cuando lo textos son estáticos (predefinidos) la variable String
phone, number.
a mostrar debe hacer referencia a res/strings, aquí existe la
posibilidad de escribir lo que mostrará la aplicación en android:id. Proporciona un nombre identificador para este
diferentes idiomas según la configuración del sistema, esto elemento vista, para luego ser asociado con su correspondiente
para internacionalizar la aplicación. y en Java y poder ser manipulado desde la Activity. La
Suele ser más usado el atributo hint que el atributo text recuperación o asociación de componentes se realiza mediante
porque hint desaparece cuando se comienza a escribir algo el método findViewById().
dentro del campo de texto.
Tema 3. Programación de aplicaciones para dispositivos móviles (II)
Aplicaciones multihilo • Context.startActivity (Intent intent).

En más de una ocasión necesitarás que tu aplicación realice varias • Activity.startActivityForResult (Intent intent, int
tareas al mismo tiempo. También puede darse el caso de que alguna requestCode).
de las actividades que el programa ha de realizar sea bastante
dependiendo de si espere que la actividad le devuelva alguna
costosa en tiempo o necesite esperar a que suceda algo y mientras
información o no.
tanto se puedan realizar otros trabajos para no dejar al resto de la
aplicación detenida. Esto puede conseguirse mediante el uso de Por ejemplo, para iniciar otra actividad:
distintos hilos (o threads o hebras) de ejecución en el programa.

Una aplicación multihilo contendrá al menos dos hilos (flujos de


ejecución diferentes) que podrán ejecutarse de manera más o
menos independiente y simultánea. La plataforma Java
ME proporciona la posibilidad de construir y ejecutar aplicaciones
CONTEXT : ES EL CONTEXTO DEL ESTADO ACTUAL DE LA APLICACIÓN, PERMITE A
multihilo al igual que también lo hacía Java SE, aunque con algunas
LOS NUEVOS OBJETOS ENTENDER QUE ESTÁ SUCEDIENDO. NORMALMENTE SE
limitaciones.
UTILIZA PARA OBTENER INFORMACIÓN DE OTRA PARTE DEL PROGRAMA, ACCEDER

RECURSOS Y CLASES ESPECÍFICOS DE LA APLICACIÓN, LANZAR ACTIVIDADES,


Programación avanzada de aplicaciones para
BROADCASTING Y RECIBIR INTENTS. PUEDE INVOCARSE MEDIANTE
dispositivos móviles. GETAPPLICATIONCONTEXT(), GETCONTEXT(), GETBASECONTEXT() O THIS (DESDE
LA PROGRAMACIÓN DE APLICACIONES PARA DISPOSITIVOS MÓVILES, EN LA ACTIVIDAD).
GENERAL, CONSTA DE MÁS FUNCIONALIDADES QUE UNAS SIMPLES PANTALLAS ,

UNA DE LAS CARACTERÍSTICAS MÁS IMPORTANTES DEL INTERFAZ ES EL PASO DE Tipos de "intents'.
INFORMACIÓN ENTRE ACTIVIDADES. UNA VEZ TENEMOS CLARO EL ESQUEMA DE Hay dos tipos de intents diferentes:
LA APLICACIÓN Y LA TRANSICIÓN ENTRE PANTALLAS, ES EL MOMENTO DE IR MÁS

ALLÁ Y ATRIBUIRLE UNA SERIE DE PARTICULARIDADES ESPECÍFICAS . • Explícitos: donde se especifica el componente
explícitamente por su nombre (el campo nombre del
ANDROID NOS PROPORCIONA UNA API Y UNAS LIBRERÍAS DE CLASES QUE DAN componente del Intent tiene un valor introducido).
AL PROGRAMADOR LA OPORTUNIDAD DE CREAR APLICACIONES COMPLETAS . Generalmente, no podemos saber el nombre específico de
APARTE DE LA PROGRAMACIÓN DE LA INTERFAZ GRÁFICA DE LA APLICACIÓN (CON los componentes de otras aplicaciones, por eso los intents
TODAS SUS COMPLEJIDADES), EXISTEN OTROS ELEMENTOS QUE PUEDEN explícitos generalmente se utilizan para llamar
ENRIQUECER SU APLICACIÓN COMO LA PERSISTENCIA DE DATOS. componentes de nuestra propia aplicación.

EN EL APARTADO "PROGRAMACIÓN AVANZADA" VERÁ TÉCNICAS AVANZADAS DE


PROGRAMACIÓN EN ANDROID Y SE TRATARÁN DIFERENTES FORMAS DE OBTENER
PERSISTENCIA DE DATOS MEDIANTE BASES DE DATOS. Si queremos que nuestra actividad pueda ser llamada
desde otra aplicación deberemos incluir en el manifest
'Intents' un filtro de intent (ver punto 3.3) exponiendo nuestra
Los intents son mensajes enviados entre los diferentes elementos actividad:
que conforman las aplicaciones de Android. Las actividades, servicios
y broadcast receivers (receptores de broadcast) son activados a
través de estos mensajes. Los intents le indican a una actividad que
se inicie, o un servicio que comience o se detenga, o son simplemente
mensajes de broadcast (dirigidos a todas las aplicaciones del
dispositivo). En definitiva, sirven para comunicar componentes de la De esta manera podemos llamar a esta actividad desde
misma aplicación u otros. nuestra aplicación o desde cualquier otra, utilizando el
nombre de la acción indicada en el intent:
Los intents sirven para comunicar diferentes componentes de la
misma aplicación o aplicaciones diferentes. Generalmente hacen
referencia a una acción a realizar y van acompañados de dos
elementos: la acción que ejecutará y la información que ésta
• Implícitos: los intents implícitos especifican la acción que
debe realizarse y opcionalmente datos que proporciona
necesita.
contenido para la acción. Si el intent implícito se envía al
El intent es un objeto de la clase Intent, y contiene la información sistema Android, busca todos los componentes que están
de la operación que se quiere realizar o, en el caso de los broadcast, registrados para la acción específica y el tipo de datos
la descripción de algún evento que ha tenido lugar en el sistema. apropiado. Si sólo se encuentra un componente, Android
Existen diferentes maneras de enviar los intents en función del tipo inicia este componente directamente. Si varios
de componente al que van dirigidos. Para dirigir un intent a una componentes están identificados por el sistema Android
actividad, puede utilizar los métodos:
para esa acción, el usuario obtendrá un diálogo de • Datos (el URI y el tipo de datos).
selección y puede decidir qué componente se debe utilizar.
Ejemplo, declarar un intent para recibir mensajes de texto:
Por ejemplo, el siguiente le indica al sistema Android que
abra una página web. Todos los navegadores web
instalados deben estar registrados a través de un filtro
de intent, éstos no tienen definido el nombre del
componente al que van dirigidos. Generalmente, se usan
para activar componentes de otras aplicaciones.

Como los intents implícitos no especifican cuál es el componente al Para llamar a dicha actividad:

que va dirigido el intent, el sistema tiene que encontrar el mejor


componente para tratar el intent. Por hacerlo, compara los
contenidos definidos en el objeto Intent con unas estructuras
definidas en los componentes que sirven para anunciar al sistema
las capacidades del componente. Estas estructuras sirven para decir
al sistema qué tipo de intents es capaz de tratar el componente y
se llaman intent filters (filtros de intents). Como normalmente tendremos registrados muchas aplicaciones
capaces de recoger un mensaje, Android no mostrará un diálogo
LOS INTENT FILTERS SIRVEN PARA DECIR AL SISTEMA LAS CAPACIDADES DEL
para que elijamos que Actividad queremos que lo habrá:
COMPONENTE. EL SISTEMA SABRÁ QUÉ TIPO DE INTENT PUEDE RESPONDER EL
COMPONENTE COMPARANDO LAS CARACTERÍSTICAS DEL INTENT CON EL FILTRO

DE INTENT.

Filtros de "intents'.
Por ejemplo, puede crear una actividad que sirva para ver páginas
web. Puede anunciar al sistema que su actividad está abierta a
recibir intents para visitar sitios web. La próxima vez que se haga
un intent para visitar una web desde el dispositivo, su actividad será
una de las candidatas para abrir la web (en el caso de que haya
varios componentes para atender intent, el sistema pedirá al
usuario qué quiere usar). Paso de información entre actividades.
Los extras son pares clave-valor que aportan información adicional
Los filtros permiten que los componentes puedan recibir intents
que se ha enviar al componente que trate el intent. Así, diferentes
implícitos. Si un componente no tiene intent filters, únicamente
URI tienen asociados algunos extras en particular. Por ejemplo, la
puede recibir intents explícitos (con su nombre). Un componente que
acción ACTION_HEADSET_PLUG, que corresponde a un cambio en el
define filtros de intents puede recibir intents implícitos y explícitos.
estado de la conexión de los auriculares del dispositivo, tiene un
Un componente tendrá un filtro diferente para cada trabajo que campo extra indicando si los auriculares se han enchufado o no.
puede hacer. Por ejemplo, una aplicación multimedia puede tener
Ejemplo:
diferentes tipos de filtros para anunciar que puede abrir diferentes
tipos de archivos (fotos, audio, vídeo, etc.).

Un filtro es una instancia de la clase IntentFilter. El sistema


comprueba los componentes para ver cuál puede recibir el intent
implícito, de ahí que debe conocer las capacidades del componente
antes de ponerlo en marcha. Por eso los filtros se han definir en el También se puede usar un objeto del tipo Bundle para añadir extras.
archivo AndroidManifest.xml como elementos <intent-filter>. Un objeto de la clase Bundle (literalmente del inglés, 'paquete' o 'haz')
permite almacenar una serie de datos (que pueden ser de distinto
Un filtro tiene campos similares a los del objeto Intent: acción, datos
tipo). Las variables se pueden añadir al Bundle simplemente dando
y categoría. Los intents implícitos se comparan con los campos del
un nombre y la variable que se quiere guardar. En cierto modo,
filtro. Para que el intent sea enviado al componente, la comparación
recuerda los arrays con que se pasan variables entre los
con los tres campos del filtro debe ser válida. Se realiza un test con
formularios en PHP: un array de tuplas [nombre, valor]. Puede
cada campo por separado:
utilizar los métodos putExtras (Bundle extras) y getExtras () para
añadir y obtener extras del intent.
• Acción.
• Categoría.
Interfaz de usuario avanzada.
VAMOS A REVISAR ALGUNOS DE LOS ELEMENTOS DE LA INTERFAZ DE USUARIO
MÁS CARACTERÍSTICOS DE ANDROID, ALGUNOS DE ELLOS SON DE RECIENTES
INTRODUCCIÓN Y HAN TENIDO UNA GRAN ACOGIDA APARECIENDO EN LA MAYORÍA

DE LAS APLICACIONES ACTUALES.

ListViews

Para recoger los datos enviados a través del Intent en la nueva


actividad (en su método OnCreate):

Existen muchos elementos que podemos utilizar para mejorar el UI


Interfaz de usuario en Android, uno de los más utilizados son los
ListView que muestran una serie de elementos con una disposición
Si inicia la actividad con la llamada al método startActivityForResult similar a un LinearLayout.
(), se espera que la retroalimentación de la sub-actividad. Una vez
Cada elemento del ListView está definido en un layout (común a
que termina la sub-actividad, el método onActivityResult () en la
todos los elementos) donde podemos definir qué y cómo se va a
subactividad se llama y se puede realizar acciones basadas en el
mostrar su contenido. Para insertar el contenido en los ListView se
resultado.
utilizan los adapters. Un adaptador gestiona el modelo de datos y lo
En el método startActivityForResult () llama puede especificar un adapta a las entradas individuales en el widget. Un adaptador
código de resultado para determinar qué actividad comenzó. Este extiende la clase BaseAdapter.
código de resultado se devuelve en el método onActivityResult. La
Cada línea en el widget que muestra los datos consiste en un diseño
actividad comenzó también puede establecer un código de resultado
que puede ser tan complejo como usted desee. Una línea típica de
que la persona que llama puede utilizar para determinar si la
una lista
actividad se ha cancelado o no.

tiene una imagen en el lado izquierdo y dos líneas de texto en el


medio como se muestra en el siguiente gráfico.

Normalmente necesitaremos los siguientes elementos para utilizar


La actividad ActivityTwo le devuelve los datos a través del método un ListView:
finish():
• Control ListView en un adaptador.

• Layout de una línea del layout (veáse xml).

• Una clase para almacenar los datos que se mostrarán en


el ListView.
La actividad original puede recibir los datos con el mé
• Un adaptador (una clase que extiende de ArrayAdapter)
todo onActivityResult():
para gestionar la conexión entre los datos y la
presentación.

Un archivo de diseño (layout) de una línea de este tipo podría ser


similar a la siguiente (listview_layout.xml):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/re
s/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/lblTitulo"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee" El adaptador “infla” el diseño para cada fila en su getView() método
android:singleLine="true" y asigna los datos de los puntos de vista individuales en la fila.

android:text="Description"
El adaptador se asigna a la ListView
android:textSize="12sp" /> a través del método setAdapter en el objeto ListView.
<TextView
Desde la actividad principal se crean los datos para el listview y se
android:id="@+id/lblSubtitulo"
le asigna el adapter al listview:
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="Example application"
android:textSize="16sp" />
</RelativeLayout>

Necesitaremos una clase para almacenar los datos de cada fila, por
ejemplo Titular.java:
El resultado final será similar a la siguiente imagen:

Los Adaptadores no sólo son utilizados por ListView sino también


por otros controles que extienden la clase AdapterView como, por
ejemplo: Spinner GridView Gallery y StackView.

Para reaccionar a las selecciones en la lista, establezca un


OnItemClickListener en el ListView:

También necesitaremos una clase adaptador, por ejemplo


AdaptadorTitulares.java. La clase ArrayAdapter puede manejar una
lista o una matriz de objetos Java como entrada. Cada objeto Java
se asigna a una fila.
RecycleView. forma que tiene de optimizar la visualización de la lista reutilizando

Recientemente el componente ListView ha sido sustituido por el las celdas. Os dejo un enlace con un ejemplo.
RecyclerView, por tanto ListView aunque sigue siendo posible
Si quisiéramos modificar el ejemplo anterior, el adaptador quedaría
utilizarlo se ha marcado como obsoleto.
así:

El funcionamiento es muy similar, lo que más cambia es el


adaptador dado que se añade el concepto de ViewHolder, es una

Como puede observarse lo que en el ListView se realizaba en el • Los fragmentos encapsulan vistas y lógica para que sea
getView pasa a realizarse en el ViewHolder, recoge referencias a los más fácil de reutilizar dentro de las Permiten adaptar la
controles de la línea, y en el onBindViewHolder donde se introducen interfaz a todo tipo de pantallas.
los datos en los controles. • Los fragmentos son componentes independientes que
pueden contener vistas, eventos y lógica.
En el MainActivity lo mantendríamos igual, teniendo en cuenta que
en vez de un array hay que pasarle una lista al adaptador y que
debemos indicar el tipo de layout que se va a utilizar en el
RecyclerView, el más habitual en el lineal,

En nuestro caso:

Fragments.
UN FRAGMENTO ES UNA CLASE REUTILIZABLE QUE IMPLEMENTA UNA PARTE DE Creación de un Fragmento (clase que hereda de fragment). Puede
UNA ACTIVIDAD. UN FRAGMENTO NORMALMENTE DEFINE UNA PARTE DE UNA crearse con un asistente New->Fragment->Fragment(Blank):
INTERFAZ DE USUARIO. LOS FRAGMENTOS DEBEN ESTAR INTEGRADOS EN LAS
ACTIVIDADES; NO PUEDEN FUNCIONAR INDEPENDIENTEMENTE DE LAS

ACTIVIDADES.

Para entender los fragmentos:

• Un fragmento es una combinación de un archivo de diseño


XML y una clase de Java similar a una
• Utilizando la biblioteca de soporte, los fragmentos son
compatibles con todas las versiones relevantes de Android
(se introdujeron en el API 11).
Añadir un fragmento de forma estática a una activity a través del Otra forma si deseamos cargar los Fragmentos de forma que se
xml: adapten al tamaño de la pantalla, es crear varios layout con el
mismo nombre y los guardaremos en distintas carpetas, por
ejemplo el activity_main.xml en las carpetas :

• /res/layout : para pantallas de smartphones


• /res/layout-large : para pantallas de tablets
• /res/layout-large-port : para pantalla de tablets en modo
vertical

En cada una de ellas se establecerá un layout diferente con los


fragmentos que consideres.

Ejemplo: activity_main.xml en carpeta /res/layout con solo un


fragmento.
Cargamos el layout en nuestro activity, heredamos de
AppCompatActivity que hereda de FragmentActivity y nos
proporciona compatibilidad con versiones anteriores del API 11, en
caso contrario podemos usar la clase Activity como siempre:

En la actividad principal podemos recoger la referencia al fragmento


mediante:

Debes conocer

Si deseamos cargar el Fragmento de forma dinámica, para por Comunicando fragmentos a través de un listener:
ejemplo adaptarlo a distintos tamaños de pantalla, debemos usar
la clase FragmentManager. En el layour de nuestra actividad Si un fragmento necesita comunicar eventos a la actividad, el

estableceremos una marca donde se insertará el fragmento en fragmento debe definir una interfaz como un tipo interno y es

tiempo de ejecución: necesario que la actividad implemente esta interfaz.

Por ejemplo dentro de nuestro fragmento FragmentListado:

Y en la actividad principal debemos implementar el listener


declarado en el fragmento:

En la actividad principal:
Ahora debes añadir una ToolBar al layout de tu actividad o
fragmento, puedes colocarlo en cualquier sitio del layout. Vamos a
colocarlo en la parte superior de un LinearLayout como haríamos
con el ActionBar:

Como vemos en las últimas líneas, se comprueba si existe otro


fragmento (FragmentDetalle) y en caso que exista se le llama
pasándole unos datos a través de un Intent con extras. En la actividad o fragmento vamos a establecer el ToolBar para que
Podemos comunicar datos entre fragmentos igual que lo hacíamos actúe como un ActionBar llamando a setSupportActionBar(Toolbar):
con las actividades.

ToolBar.
La ToolBar en la sucesora de la AppBar (también conocida como
ActionBar) y se introdujo en el API 21, es uno de los elementos de
diseño más importantes en las actividades, ya que proporciona una
estructura visual y elementos interactivos que son familiares para
los usuarios.

Es un View que puede colocarse en cualquier lugar del layout XML,


la ToolBar es una generalización de la ActionBar, las diferencias más
importantes respecto a esta son:

• ToolBar es un View en un layout


• Al ser un View puede colocarse, animarse y controlarse
más fácilmente
Tenemos que asegurarnos de tener los ítems que utilizamos e un
• Podemos tener varias Toolbar es una
fichero de recursos de menú en un fichero como
Usando una ToolBar como ActionBar res/menu/menu_main.xml que se “infla” en el método
onCreateOptionsMenu:
Debes asegurarte de utilizar la librería de soporte AppCompat-v7,
para ello en el fichero build.gradle:

Debes desactivar el tema por defecto y extender del tema


Theme.AppCompat.NoActionBar dentro del fichero res/styles.xml:
APLICACIÓN EN CUALQUIERA DE LAS DIFERENTES FORMAS QUE TIENE PARA

HACERLO.

Importación en Android Studio

Las imágenes ic_compose e ic_profile son recursos que se han Para facilitar la programación con el Android Studio modificaremos
introducido utilizando New->ImageAsset sobre la carpeta drawable. las preferencias para automatizar las importaciones a medida que
El resultado : vamos escribiendo o que pegamos código, así obtendremos una
mayor agilidad a la hora de escribir código.

Accederemos a las preferencias desde File/Settings y en el submenú


Editor/General/Auto Import tendremos que marcar los checkboxes
El titulo mostrado es la propiedad label de la aplicación
Optimize importes on the fly y Add unambiguous importes on the
(android:label="@string/app_name")dentro del fichero manifest.
fly. Además, podemos modificar el valor de Insert imports donde
Recoger el evento click sobre la Toolbar paste de Ask All (véase la figura).

Podemos hacerlo de forma similar a un botón utilizando la Con estas modificaciones el Android Studio hará todos los
propiedad Android:onClick en el Xml: imports automáticamente, es por eso que siempre tendremos que
comprobar que el import sea el correcto para evitar
comportamientos no deseados.

Persistencia.
Es probable que desee que su aplicación pueda guardar algunos
datos entre las diferentes ejecuciones de la aplicación. Por ejemplo,
puede querer guardar las preferencias de la aplicación para que la
próxima vez que lo ejecute tenga la misma apariencia que la última
vez que se ejecutó. Existen diferentes formas de obtener
persistencia en los datos de la aplicación en diferentes ejecuciones:

• Un mecanismo ligero llamado preferencias compartidas


(shared preferences) para guardar pequeñas cantidades
de datos.
Que codificaríamos en la actividad: • El sistema de archivos tradicionales.
• Una base de datos relacional SQLite.

Para guardar poca información, la mejor forma de hacerlo es con


las preferencias compartidas. Android incorpora el
objeto SharedPreferences, que sirve para guardar y leer datos
persistentes en la forma clave-valor de datos primitivos. Puede
utilizar SharedPreferences para guardar cualquier tipo de datos
Programación avanzada. primitivos: boolean, float, int, long y string. Estos datos se

CUANDO HAYA CREADO EL ESQUEMA DE UNA SENCILLA APLICACIÓN, ES EL mantendrán entre sesiones, aunque su aplicación se haya cerrado.

MOMENTO DE AÑADIR FUNCIONALIDADES AVANZADAS , COMO UNA INTERFAZ DE


Además, serán guardadas automáticamente en un fichero XML.

USUARIO AVANZADA, EL ACCESO A LAS DATOS DE LA APLICACIÓN (MEDIANTE


Para obtener un objectSharedPreferences a su aplicación puede
BASES DE DATOS O PROVEEDORES DE CONTENIDOS) O LA PERSISTENCIA DE LOS
utilizar el método getSharedPreferences() con dos argumentos: el
DATOS DE LA MISMA. ASIMISMO, CUANDO PIENSA QUE TIENE LA APLICACIÓN
nombre del fichero de preferencias y el modo de operación.
SUFICIENTEMENTE DESARROLLADA, ES EL MOMENTO DE PUBLICAR LA
Por ejemplo:
relacionada entre sí, es más adecuado tenerla estructurada en
forma de una base de datos.

Por ejemplo, podría tener una base de datos con información de


diferentes fabricantes relacionada con los diferentes productos que
En caso de que desee utilizar un único archivo de preferencias, puede
estos producen. Haciendo utilizar bases de datos, puede asegurar la
llamar al método getPreferences (), donde no es necesario
integridad de los datos especificando relaciones entre los diferentes
especificar el nombre del archivo.
conjuntos de datos.
Para escribir valores en el archivo de preferencias:
La base de datos que puede crear para su aplicación únicamente
1. Llamar al método edit() para obtener un objeto Editor. estará disponible para la propia aplicación. El resto de aplicaciones
del dispositivo no podrán acceder, por lo tanto no puede utilizar la
2. Agregue valores con los métodos que le permiten escribir
base de datos para compartir datos entre aplicaciones. Trabajar con
valores primitivos, como putBoolean () o putString ().
bases de datos Android puede ser complicado. Por este motivo, debe
Estos métodos tienen dos argumentos. El primero es
de crear una clase que servirá para encapsular el acceso a las bases
un string que define la clave, y el segundo es el valor que
de datos y, así, simplificar el código de su aplicación (que tendrá un
se quiere
acceso a los datos transparente a su implementación, a través de
3. Confirme los valores con commit(). esta clase).

Por ejemplo: Decimos que una aplicación tiene un acceso transparente a los datos
cuando podemos acceder a la información a través de métodos sin
tener que conocer su implementación. Esta manera de trabajar nos
permite modificar la estructura interna de la información sin que
las aplicaciones que la usan deban modificar su código para
funcionar correctamente.

Para leer los valores de preferencias puede utilizar los métodos


Clase DBInterface
análogos getBoolean (), getString () o getInt () de la clase
Cree un nuevo proyecto con los siguientes datos:
SharedPreferences.
• Application name: BasesDeDatos
Estos métodos tienen dos argumentos: el primero es el nombre de
la clave que está buscando en el archivo de preferencias. El segundo Deje el resto de opciones con los valores por defecto.
es el valor por defecto que se utilizará en caso de que no se
encuentre la clave en el archivo de preferencias. Por ejemplo: Ahora creará una clase que le servirá de interfaz con el acceso a las
bases de datos de la aplicación. Esta clase tendrá el nombre
Cargar las preferencias DBInterface. Cree una aplicación, y dentro de esta, cree una nueva
clase haciendo clic con el botón derecho dentro del paquete de su
proyecto y seleccionando New/Class, esta clase creará, abrirá,
utilizará y cerrará una base de datos SQLite.

El lugar más adecuado para cargar y guardar las preferencias de la Creará una base de datos llamada BDClients que contendrá una
aplicación serían los métodos onCreate () y onStop () (el momento única tabla, contactos. Esta tabla tendrá únicamente tres campos:
en que la aplicación se pone en marcha y el momento en que se _id, nombre y email, tal como se puede ver en la tabla:
cierra).

Trabajando con bases de datos.


Android proporciona un sistema de bases de datos relacionales
basado en SQLite que puede utilizar en sus aplicaciones. Cuando la
será necesario tener que repetir por todo el código, con el peligro de
cantidad de datos a guardar es importante, o bien se quieren
equivocarse en algún momento).
realizar búsquedas sobre los datos, o la información está
Concretamente, la constante BD_CREATE contiene la cadena que se
utilizará para crear la tabla contactos dentro de la base de datos.

La clase AyudaBD, la creará más tarde. El constructor de nuestra


clase, lo único que hace es crear un objeto AyudaBD y guardar en
una variable el contexto en que se está ejecutando la clase:

El método getWritableDatabase () crea y / o abre una base de datos.


La primera vez que se llama se abre la base de datos y se llama
onCreate (). Aquí es donde se crea la base de datos, llamando
execSQL () con la cadena de creación de la base de datos. Una vez
Context es una clase implementada por el sistema Android que da
creada, la base de datos queda en caché, y por tanto se puede llamar
acceso a recursos y clases específicos de la aplicación.
este método para abrirla.
En Android existe clase SQLiteOpenHelper, que es una clase que
A continuación define los métodos para modificar la base de datos.
sirve de ayuda para gestionar la creación de bases de datos y
Por insertar un contacto utilizará el método insert (String table,
gestión de versiones.
String nullColumnHack, ContentValues values). Los argumentos son
Creará la clase AyudaBD que hereda de ésta: estos:

• String table: tabla donde se quiere insertar un elemento.


• String nullColumnHack: un argumento opcional que
dejaremos a Puede consultar la Guía del desarrollador de
Android para consultar su uso.
• ContentValues values: un objeto de la clase ContentValues
que sirve para almacenar valores que pueden ser
procesados por un ContentResolver.

Este método devuelve el ID de la fila que se ha insertado o un -1 si


ha habido una error. Nuestro método para insertar un contacto
creará un ContentValues con los valores de la fila insertar y hará la
llamada a insert ().

El constructor de AyudaDB llama al constructor de


SQLiteOpenHelper, el cual crea un objeto de ayuda para crear, abrir
y gestionar la base de datos. Vea la documentación de
SQLiteOpenHelper para obtener ayuda sobre sus métodos.
Para borrar un elemento de la tabla utilizará el método delete
Esta clase se ocupa de abrir la base de datos si esta existe o crearla (String table, String whereClause, String [] whereArgs). El
en caso contrario, y actualizarla si es necesario. El método onCreate significado de los argumentos es el siguiente:
() crea una nueva base de datos. El método onUpgrade () es llamado
cuando se ha de actualizar la base de datos. Lo que hace es • String table: tabla de la que se borrará el registro.

eliminarla (hacer un drop de la tabla) y volverla a crear. • String whereClause: la cláusula WHERE que se aplicará
para borrar de la base de datos. Si se le pasa null, borrará
De vuelta a la clase DBInterface, el siguiente paso es definir los todas las filas de la tabla.
diferentes métodos para abrir y cerrar la base de datos.
• String [] whereArgs: argumentos de la cláusula WHERE.
Este método devuelve la cantidad de filas afectadas por la
cláusula WHERE.

Su método devolverá un valor booleano que indica si se ha borrado


algún elemento o no. Para devolver un contacto utilizará el método
query (), que tiene los siguientes argumentos:
• boolean distinct: será true si desea que cada fila sea Finalmente, para actualizar un registro de la tabla utilizará el
única, o false en caso contrario. método Update () con los siguientes argumentos:

• String table: define la tabla respecto a la que desea


• String table: establece la tabla a actualizar.
ejecutar la sentencia de query.
• ContentValues values: introduce un objeto de la clase
• String [] columns: admite una lista de las columnas de la
ContentValues con el valor de las columnas a actualizar.
tabla que devolverá el método.
• String whereClause: incluye la cláusula WHERE opcional
• String selection: establece un filtro que define qué filas
que se aplicará cuando se hace la actualización. Si se le
devolver, con el formato de cláusula de SQL, WHERE (sin
pasa null actualizará todas las filas
incluir la palabra WHERE en el string).
• String [] whereArgs: establece los argumentos del
• String [] selectionArgs: permite añadir los argumentos de
WHERE.
la selección, si no les ha introducido directamente en la
cadena.
• String groupBy: establece un filtro que define cómo se
agrupan las Tiene el mismo formato que la cláusula de
SQL: GROUP BY (sin incluir las palabras GROUP BY). Si se
le pasa un null, las filas que se devuelvan no estarán
agrupadas.
• String having: establece un filtro que declara qué grupos
de filas incluye el Tiene el mismo formato que la cláusula
de SQL: HAVING (sin incluir la palabra HAVING). Si se le Utilizando la clase DBInterface.
pasa un null, se incluirán todos los grupos. Cuando haya creado la clase que le servirá de ayuda para trabajar
• String orderby: indica cómo ordenar las filas. Tiene el con las bases de datos, es el momento de usarla. El siguiente
mismo formato que la cláusula de SQL: ORDER BY (sin ejemplo muestra una aplicación que utiliza la clase de interfaz de
incluir las palabras ORDER BY). Si se le pasa base de datos. Es muy sencilla, pero sirve para ilustrar la forma de
un null devuelve las filas con el orden predeterminado. uso de esta clase.

• String
La actividad principal tiene un layout con cinco botones, uno para
límite: especifica el límite de filas devueltas
cada opción de la aplicación:
por query, con el formato de la cláusula de SQL: LIMIT. Si
se le pasa un null, no existe límite. • Añadir.
• Obtener.
Este método devuelve un objeto de la clase Cursor, que proporciona
acceso de lectura y escritura en el resultado devuelto por una • Obtener Todos.

consulta (un query) en la base de datos. • Actualizar.


• Borrar.

La aplicación activará una actividad para cada uno de los botones,


equivalente a las diferentes "pantallas" de la aplicación. Estas
diferentes actividades deben estar definidas en el
AndroidManifest.xml.

Para obtener todos los contactos, utiliza otra versión de query que
no incluya el primer booleano.

La actividad principal implementa la interfaz OnClickListener para


gestionar las acciones que se derivan cuando el usuario pulsa los
botones. A continuación tenéis el código parcial que corresponde a la
definición de la clase y de las variables que usaremos a la actividad:

Tenga en cuenta que Android utiliza un objeto de la clase Cursor por


valor de retorno de las consultas a la base de datos
El método onCreate () sencillamente carga el layout, crea el objeto
(las queries). Considere el Cursor como un apuntador al conjunto de
de interfaz de la base de datos y crea los listeners de los botones.
resultados obtenidos de la consulta en la base de datos. El uso de
un Cursor permite Android gestionar de una forma más eficiente A continuación implementaremos cada una de las acciones que
las filas y las columnas. corresponden a los botones en una actividad separada.Vamos a ver
algunas de estas acciones en detalle.
Añadir

En primer lugar, hay que definir el listener del botón:

Esto lanzará un intento para la actividad que permitirá añadir


elementos en la base de datos. Esta actividad tiene un diseño muy
sencillo, incorpora tan sólo los widgets necesarios para añadir un
nuevo elemento, como se puede ver en la figura:

Lo que hemos hecho es:


Básicamente, lo que se hace es:
1. Abrir la base de datos.
• Abrir la base de datos. 2. Obtener el identificador que está escrito en la caja de
• Insertar un elemento con los datos que obtiene de las texto.
cajas de Fijaros como comprueba el valor de retorno de 3. Llamar la base de datos.
insertarContacto() para saber si se ha podido insertar el 4. Comprobar si ha habido algún resultado.
contacto nuevo correctamente o no. 5. Mostrar el contacto.
• Cerrar la base de datos. 6. Cerrar la actividad.

• Cerrar la actividad. 7. Cerrar la base de datos.

Obtener Lo normal sería mostrar el contacto a través de otra actividad


diseñada para mostrar contactos, pero para hacer el programa más
Se compone de una actividad en la que los únicos widgets serán una sencillo hemos optado por mostrar el contacto con un mensaje por
caja de texto y el botón para obtener el contacto, como se puede ver pantalla.
en la figura.
Obtener todos

La opción Obtener todos mostrará todos los contactos por pantalla,


uno a uno. En una aplicación lo más habitual sería introducir los
contactos en un ListView o un ContentProvider, pero en este ejemplo
queremos lo haremos a través de un toast. Dado que el programa
no necesita ninguna otra información extra para mostrar todos los
contactos, esta opción no abrirá otra actividad, sino que mostrará
El código de la actividad lo único que se programa es el listener del
los contactos uno a uno por pantalla. Dentro del listener del botón
botón Obtener. El código es el siguiente:
obtenemos todos los contactos de la base de datos y hacemos un
recorrido con el cursor que se nos retorna para ir mostrando los
contactos.
El formato PNG

El formato Portable Network Graphics (PNG) es un formato de


imagen libre (sin patentes) y sin pérdida de calidad que, además,
permite transparencia. Por estas razones es el formato de imagen
recomendado para las aplicaciones Android, frente a otros formatos
populares como el JPEG.

Una vez preparada la imagen hay que incorporarla a su aplicación.


En primer lugar debe crear un nuevo proyecto Android (puede
llamarlo multimedia1 y dejar el resto por defecto). A continuación,
cambie la vista del proyecto de Android Project y despliegue la
carpeta de su proyecto. Acceda a continuación en la
subcarpeta /app/src/main/res (res viene de resources, 'recursos', es
decir, los datos y ficheros adicionales tales como imágenes o sonidos
Lo que hemos hecho es: de una aplicación) y, dentro de él, en la subcarpeta drawable- hdpi
(véase la figura).
1. Abrir la base de datos.
2. Llamar la BD para obtener todos los contactos. Cuando haya encontrado esta subcarpeta, debe arrastrar la imagen
3. Mover el cursor en la primera posición. que desea ver para que pase a formar parte de la aplicación.
4. Mostrar los contactos mientras se pueda. Aparecerá un diálogo para modificar el nombre de la imagen y su
5. Cerrar la base de datos. ruta; es necesario que acepte.
6. Mostrar un mensaje por pantalla cuando se haya
terminado de mostrar los contactos. Resolución de imágenes

Como puede observarse, hay varias subcarpetas el nombre de las


Visualización de imágenes.
cuales comienza con drawable ('dibujable'). ¿Por qué? Una aplicación
EXISTEN DIFERENTES TÉCNICAS DISPONIBLES PARA VISUALIZAR IMÁGENES EN
Android se puede ejecutar en muchos modelos diferentes de
LAS APLICACIONES ANDROID. COMENZAREMOS CON LA TÉCNICA MÁS SENCILLA,
dispositivos, con pantallas de tamaños y resoluciones muy
PARA VISUALIZAR UNA IMAGEN FIJA, YA CONTINUACIÓN VEREMOS CÓMO
diferentes. Cuando creamos las imágenes para nuestras
CAMBIAR LA IMAGEN DINÁMICAMENTE, ES DECIR, COMO RESPUESTA A UNA
aplicaciones es recomendable crearlas para resoluciones diferentes:
ACCIÓN DEL USUARIO. FINALMENTE, IREMOS MÁS ALLÁ Y VISUALIZAREMOS TODO
muy altas, altas, medias y bajas, para dispositivos con estas
UN CONJUNTO DE IMÁGENES UTILIZANDO UNA GALERÍA DE IMÁGENES , QUE NOS
resoluciones. De esta manera se garantiza que la visualización será
PERMITIRÁ NAVEGAR ENTRE TODAS LAS IMÁGENES DE UNA COLECCIÓN .
óptima en todos los casos. Precisamente esto representan
las subcarpetas: imágenes de baja resolución (ldpi, low dotes para
Con el fin de utilizar imágenes en sus aplicaciones, es necesario que
inch, puntos por pulgada de la imagen), media (MDPI, medium dotes
primero las agregue como recursos de la aplicación. Pero antes es
para inch), alta (hdpi, high dotes para inch), extra alta ( xhdpi, extra
conveniente prepararlas para que después no te den ningún tipo de
high dotes para inch) y extra extra alta (xxhdpi, extra extra high
problemas. Concretamente, lo que hay que hacer es lo siguiente:
dotes para inch). De momento sólo usaremos la alta resolución, por
• Se recomienda utilizar imágenes en formato PNG. no alargar los ejemplos.

• El nombre de los archivos de imagen sólo puede contener


letras minúsculas y números, sin espacios, signos, letras
mayúsculas ni con acento, cedilla u otros caracteres no
ingleses.
• Al utilizar muchas imágenes a una aplicación hay que
fijarse bien en cuál es su resolución (puntos de ancho y
de alto). En general, es conveniente que todas las
imágenes tengan un tamaño similar.

De este modo, la imagen ya está incorporada a su aplicación, pero


esto no implica que sea visible. La forma más sencilla de visualizar
una imagen en una aplicación Android es mediante el control
ImageView que se encuentra en la sección widgets
de la paleta del editor del layout (abre la carpeta de la
aplicación y vaya a /res/layout/ activity_main.xml). En la figura se
puede ver este control.
Añádase el ImageView a su aplicación en la esquina superior Debe añadir lo siguiente a las importaciones que hace Java para
izquierda, el ImageView no ocupará toda la pantalla, para que lo haga que pueda trabajar con OnClickListener y poder acceder a la
tendremos que hacer clic en los botones layout_width y asignar el ImageView:
valor match_parent y en layout_height el valor match_parent o
cambie al XML las propiedades android:layout_width y
android:layout_height a match_parent haciendo doble clic sobre el
ImageView aparecerá un diálogo con la propiedad src, seleccione la
imagen que ha copiado antes desde el directorio Drawable en la Lo primero que necesita hacer es asociar el clic en la imagen con
pestaña Project. una respuesta por parte de la aplicación añadiendo el siguiente
código al método onCreate().
Si hace clic en la imagen o por posiciones en ImageView del XML,
verá que se muestra un aviso (el icono de una bombilla amarilla). Si
hace clic en el aviso, verá que este nos dice que falta la propiedad
contentDescription en la imagen. Esta propiedad es una descripción
textual de las imágenes, necesaria, por ejemplo, para personas con A continuación hay que crear el método de respuesta al clic, que lo
discapacidades visuales que usan asistentes de voz. que hará es acceder al ImageView y cambiar el recurso
al que está asociado:
Para acabar de ajustar la aplicación, hay que añadir una cadena
explicando lo que hay en la imagen (el archivo strings. Xml), con el
nombre descripcion_imagen por ejemplo, y que regrese al main.xml
y agregue la línea del contentDescription (la última línea del
siguiente código) de forma que la parte del ImageView quede de la
siguiente manera:

Como puede ver, por cada recurso que añadimos al proyecto se crea
un identificador (en realidad, un entero) que nos permite identificar
los recursos y trabajar fácilmente. En este caso es R.drawable.pollo

Con las cadenas de texto y los controles del layout pasa lo mismo.
Puede mirar (pero no cambiar) los archivos R.java que se encuentran
en la carpeta /app/build/source/r/, accesible desde la vista project del
explorador del proyecto.

Si intenta ejecutar el programa, comprobaréis como al principio se


ve la imagen inicial, pero cuando la toque cambia por la segunda
Selección dinámica de imágenes imagen.

Acaba de ver cómo mostrar una imagen seleccionando el archivo de Animaciones.


imagen con el editor de layouts de Android. Esta es la manera más
UN ASPECTO FUNDAMENTAL PARA DAR UNA APARIENCIA MÁS DINÁMICA A LAS
sencilla y directa de mostrar una imagen al usuario. Pero ¿como se
APLICACIONES CON FUERTE CONTENIDO MULTIMEDIA SON LAS ANIMACIONES, QUE
puede cambiar esta imagen dinámicamente, es decir, en respuesta
CON RESPECTO AL DESARROLLO PARA ANDROID PODEMOS CLASIFICAR EN DOS
a una acción del usuario?
TIPOS: ANIMACIONES POR INTERPOLACIÓN (TWEEN) Y ANIMACIONES POR

En este ejemplo modificarás la aplicación anterior para que la FOTOGRAMAS (FRAMES).VERÁ EJEMPLOS DE AMBOS TIPOS.

imagen visualizada cambie cuando el usuario la toque. Para que la


POR CIERTO, UNA VEZ QUE HAYA EXPERIMENTADO CON LOS DOS TIPOS DE
nueva aplicación tenga un nombre diferente, vaya al archivo strings.
ANIMACIÓN VERÁ QUE ES POSIBLE COMBINARLOS PARA CREAR EFECTOS AÚN
Xml y cambie la cadena app_name
MÁS ESPECIALES.

A continuación añada una nueva imagen en la carpeta


Animaciones por interpolación
/res/drawable- hdpi De momento, si ejecuta la aplicación sólo verá
la imagen inicial. ¿Cómo puede hacer que el programa reaccione y En primer lugar definiremos qué quiere decir eso de interpolación,
muestre otra imagen cuando la toque? para entender cómo funcionan este tipo de animaciones.

Abra el código del programa (archivo MainActivity. Java) para añadir De manera intuitiva, podemos definir interpolación como la acción
el código de respuesta al clic en la imagen. En primer lugar, añadir de encontrar los valores que tiene una función entre dos puntos
la modificación tal como sigue a la declaración de la clase para que conocidos. Nosotros hacemos interpolaciones cuando, por ejemplo,
pueda detectar clics: unimos dos puntos con una línea, o tres puntos con una curva.

Tween en inglés significa lo mismo que between, es decir, entre


(entre dos puntos, por ejemplo). Se llaman animaciones tween
porque encuentran lo que se debe hacer entre dos puntos
determinados por el programador.
Para crear una animación por interpolación sólo debe definir el Como veis, la animación se repite de forma indefinida. Existe la
estado inicial de una imagen (de hecho, se puede aplicar a cualquier opción de decirle que se detenga al terminar, si desea probarla vaya
otra vista, como un texto) y el estado final deseado, y cuánto debe al archivo que define la animación (brickfilm. Xml en este ejemplo)
tardar la imagen en pasar del estado inicial al final, y el interpolador y añada la siguiente opción en la etiqueta animation-list para que
ya hace el resto. Los parámetros que puede cambiar de nuestra quede:
imagen son la escala, la posición y la rotación. Es decir, podemos
hacer que la imagen vaya cambiado de tamaño, que se vaya
moviendo o que vaya girando. O que se produzca más de un efecto
al mismo tiempo, como verá enseguida.
Las animaciones se pueden aplicar a botones y otros controles. Esto
También se puede cambiar la transparencia de la imagen, lo que se suele utilizar en juegos y aplicaciones similares, para darles un
permite hacer que las imágenes aparezcan o desaparezcan de aspecto más lúdico y dinámico.
forma gradual.
Las animaciones por fotogramas se pueden aplicar tanto a
Para poder experimentar con las animaciones, hay que crear un la view que estamos usando como a su fondo (background). Piense
nuevo proyecto Android (la llamaremos Multimedia3), añadiéndole que, como las imágenes en formato PNG pueden tener
una imagen y un ImageView que nos la muestre. Le recomendamos transparencia, sería posible tener una imagen de fondo y una
que utilice una imagen en formato PNG con un motivo claro (una animación encima, o incluso una animación encima de la otra. Eso
pelota, un coche, un animal ...) y con el fondo transparente, de modo sí, hay que ser un poco artista porque estéticamente esto quede
que no se vea más que el motivo principal. En general será más fácil bien!
si se trata de un dibujo que de una foto. Centráis la imagen en la
parte superior del layout, como se ve en la figura. Sonido y música.
A CONTINUACIÓN VERÁ CÓMO AÑADIR ARCHIVOS DE SONIDO O MÚSICA EN SUS
Para apreciar mejor las animaciones, hará que se inicien cuando
APLICACIONES Y CÓMO REPRODUCIRLOS DE MANERA MÁS O MENOS
hacemos clic en la imagen. Hay que añadir un método como el
SOFISTICADA. TAMBIÉN SE PUEDEN REPRODUCIR LOS ARCHIVOS DE AUDIO QUE
siguiente en la clase MainActivity que llenaréis más adelante con
HAY ALMACENADOS EN EL DISPOSITIVO, O INCLUSO ACCEDER A AUDIO ONLINE.
las instrucciones pertinentes:
LOS FORMATOS DE AUDIO SOPORTADOS POR ANDROID SON, PRINCIPALMENTE:
MP3, OGG, FLAC Y WAV.

Si sólo quiere que su aplicación reproduzca una pista de audio


Y a continuación, debe buscar la propiedad de la imagen onClick, y cuando se pone en marcha, los pasos a seguir son sencillos.
escribir onClick para que al hacer clic en la imagen se llame el
En primer lugar se debe añadir un archivo de audio con un formato
método que acabamos de escribir. Esto genera un atributo en la
compatible con Android y un nombre de archivo compatible con Java.
definición del elemento en el layout. Xml que hace que cuando el
Hay que crear una nueva carpeta en res/ que se llame raw/ y
usuario pulse el ImageView se llame el método onClick().
arrastrar el archivo de audio a esta carpeta.

Para escuchar audio no hay que añadir ningún control en


el layout, creará dinámicamente un MediaPlayer que es el objeto
encargado de reproducir audio y vídeo. Para ello sólo hay que añadir
las siguientes líneas al final del onCreate() de la activity principal
nompista que es el nombre del archivo de audio que hemos añadido,
sin la extensión):

Puede comprobar que al abrir la aplicación se reproduce el archivo


de sonido. Eso sí, sin opción a detenerlo ni nada parecido.
Hay mucho que mejorar!
Haremos cuatro animaciones diferentes. (Mirar final del tema)
Aunque es posible controlar el MediaPlayer con botones y otros
A menudo, las transformaciones que nos proporciona Android no
elementos estándar, hay un widget específico para esta tarea que
nos sirven para conseguir la animación que necesitamos en nuestra
llamado MediaController. Este tipo de control se puede añadir desde
aplicación y es necesario generarlas con programas específicos de
el editor del layout, pero es más sencillo añadirlo desde el código
animación. En este punto tenemos diferentes posibilidades, como
directamente.
generar un vídeo o también, si la animación no es demasiado larga,
podemos generar un conjunto de imágenes con los diferentes pasos Primeramente, añada a la actividad el siguiente:
de la animación, de modo que pasando rápidamente las imágenes
del usuario tenga la sensación de presenciar movimiento.
Con este código indicar que su actividad llevará a cabo las funciones Si al show() se le pasa un número entero n, los controles de audio
de control de sonido que ofrece MediaController. Hay que permanecerán visibles durante n segundos; si le pasas un cero, los
añadir un import con android. Widget. MediaController. Entonces, el controles no se esconderán. Ahora sí, si está ejecutando la aplicación
Android Studio indicará un error a la actividad, diciendo que debe ya se deberían ver los controles de audio como se muestran en la
definir los métodos abstractos de MediaPlayerControl La manera figura.
más sencilla de resolverlo es situarse sobre el error y hacer clic en
la opción Implement methods de la bombilla de error (o pulsar Alt
+ Enter entonces añadirá automáticamente
diez métodos (canPause hasta start), que iréis utilizando para
añadir funcionalidad a su aplicación.

También hay que ir al archivo main. Xml y asignarle al


De momento, estos controles no hacen gran cosa porque tiene que
RelativeLayout un ID, por ejemplo vista_controls_so, como podéis
rellenar sus métodos con código que haga lo que desee. Comience
ver a continuación:
por lo más básico: hacer que el botón de reproducir (play) ponga en
marcha la música. Por eso hay que ir al método start (creado
automáticamente para implementar
MediaController.MediaPlayerControl) y añadir:

Con lo que le decís al reproductor de audio que comience a reproducir


la pista de audio. También hay que ir al método canPause () y hacer
que vuelva true para que el botón de reproducir sea activo. Con esto
ya debería escuchar cuando toque el play. Compruébelo.

Para que la interacción entre los controles y el reproductor de audio


A continuación, añadir los siguientes atributos a la clase de la sea más completa, hay que ir llenando los métodos que se han
actividad: generado para que queden como se ve a continuación:

A continuación, vaya al onCreate() elimine las líneas que voy añadir


para la primera versión del programa de reproducción de sonido y
añadir las siguientes líneas:

Con estas instrucciones, lo que está haciendo es


crear MediaPlayer para reproducir la pista de audio, y luego
crear MediaController para disponer de unos controles para el audio.
A continuación asociar el código del MediaController a la actividad
actual, y finalmente defina a qué vista de la aplicación se debe
dibujar el MediaController.

Que aún no se ve nada? Esto es porque, por defecto,


el MediaController esconde automáticamente. Es necesario que el
obliguéis a mostrarse cuando toque la pantalla, añadiendo el
siguiente método a su actividad:
La duración de las pistas multimedia y las posiciones se miden en Desgraciadamente, la reproducción de vídeo no siempre funciona en
milisegundos. el emulador de Android, por lo que es posible que sólo escuche el
sonido del vídeo. En este caso necesitará pasar la aplicación a un
El resto de métodos se pueden dejar tal como están ahora mismo.
dispositivo real para poder visualizarlo.
Con esto ya tendrá un reproductor de audio con funciones de repr
oducción, pausa y búsqueda de una posición. Geolocalización.
GPS SIGNIFICA GLOBAL POSITIONING SYSTEM, ES DECIR, SISTEMA DE
Video.
POSICIONAMIENTO GLOBAL, Y ES UN SISTEMA QUE PERMITE CONOCER LA
REPRODUCIR VÍDEOS A LOS DISPOSITIVOS ANDROID ES MUY SENCILLO, COMO
SITUACIÓN PROPIA (LONGITUD Y LATITUD) MEDIANTE SEÑALES ENVIADAS POR
VERÉIS EN ESTE APARTADO. PARA EMPEZAR, AÑADIREMOS LOS VÍDEOS COMO
SATÉLITES.
RECURSOS A SU APLICACIÓN DE EJEMPLO. TAMBIÉN SE PUEDEN REPRODUCIR
VÍDEOS DE INTERNET O ALMACENADOS EN EL DISPOSITIVO. Una de las características más interesantes de los dispositivos
móviles es su capacidad de detectar su posición geográfica para dar
Los formatos de video más ampliamente soportados por los
al usuario una serie de información, tales como mostrar su
dispositivos Android son el MPEG -4 (.mp4) y el 3GPP (.3gp).
situación en un mapa, listar los recursos más cercanos

Para visualizar un vídeo en sus aplicaciones, es necesario que (restaurantes, gasolineras ...), registrar su recorrido mientras
agregue el siguiente elemento en nuestra aplicación: dentro de la marcha por la montaña, etc. El método más conocido de
pestaña, al editor gráfico de layouts Images & Media, agregue un geolocalización, es decir, de obtención de la posición geográfica, es el

VideoView, que será la pantalla con la que se verá el vídeo. sistema GPS.

Para añadir un vídeo como recurso, debe crear una carpeta llamada Hay otros métodos de geolocalización, como detectar las redes Wi-
raw/ dentro de la carpeta res/ y arrastrar un vídeo con el formato Fi presentes y deducir a partir de las redes identificadas cuál es la
adecuado y con un nombre compatible con Java, como es habitual. posición geográfica. Este método no es tan preciso como el GPS, pero

A su ejemplo, este vídeo se llamará castell.3gp. por otra parte puede funcionar bajo tierra o dentro de edificios,
donde la señal GPS generalmente no llega.
A continuación hay que ir al código de la actividad y añadir lo
siguiente: En este apartado verá cómo obtener las coordenadas GPS y luego
las utilizará para visualizar su posición en un mapa.

Comunicaciones.
CADA VEZ ES MÁS HABITUAL QUE LAS APLICACIONES HAGAN USO DE LA
COMUNICACIÓN PARA INTERNET, INTEGRÁNDOSE EN LAS REDES SOCIALES O

UTILIZANDO SERVICIOS WEB. EN ESTE APARTADO VERÁ CÓMO HACER USO DE


LAS COMUNICACIONES DEL DISPOSITIVO PARA COMUNICARSE CON EL EXTERIOR.

Dado que Android es un sistema diseñado para dispositivos móviles,


Tenga en cuenta que el VideoView no acepta directamente un
la conexión continua es uno de sus aspectos fundamentales. Véase
identificador de recurso como vídeo, por ello hay que acceder con la
comunicación web y los servicios de mensajería instantánea y
expresión que forma una dirección URI (Universal Resource
multimedia. Para seguir los contenidos de este módulo, es
Identifier, identificador universal de recursos) accediendo a los
conveniente ir haciendo las actividades. Aunque las unidades tienen
recursos empaquetados al aplicación que está construyendo.
un contenido importante desde el punto de vista conceptual, siempre
A continuación se asocia este recurso al VideoView, se crea ha procurado darles un enfoque práctico en las actividades
un MediaController para tener controles de reproducción y, propuestas.
finalmente, se hace que el vídeo comience. Claro que para que todo
esto funcione hay que añadir los importes adecuados.
Hacer botar la pelota

La primera animación que haréis consiste en hacer botar el balón. Para


añadir una animación a su aplicación, en primer lugar hay que crear
una nueva carpeta llamada anim/ dentro de la carpeta res/ (pulsando
el botón derecho sobre res/ y New/Android resource directory y
escogiendo de tipo anim). A continuación hay que añadir unnuevo
archivo XML que contendrá las especificaciones de la animación. Pulse
el botón derecho sobre la carpeta res/ y vaya a New
/ New resource file. Llámelo, por ejemplo, botar.xml. El tipo de
recurso debe ser Animation, y el Root Element puede ser set, como
se puede ver en la figura. Enseguida veremos qué significan estas
opciones, de momento es suficiente saber que son los diferentes tipos
de animaciones disponibles.

Figura Añadiendo un archivo de animación


El contenido del archivo botar.xml debe ser el siguiente:
<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate

android:interpolator="@android:anim/accelerate_interpolator"

android:repeatCount="infinite"

android:repeatMode="reverse"

android:fromYDelta="0%p"

android:toYDelta="80%p"

android:duration="1000"/>

</set>

Vemos rápidamente qué significan estas líneas:

• set permite definir una animación compuesta de más de una


transformación. En este caso no se aplica, pero de esta manera
se puede ampliar fácilmente más adelante.

• xmlns:android define el espacio de nombres android para que las


líneas siguientes puedan usarlo como prefijo para el resto de
opciones.

• translate indica que comienza una transformación de posición,


queremos mover la imagen.

• interpolator es la función que se utilizará para calcular los valores


intermedios. Si es linear quiere decir que los valores cambian a
ritmo constante, y si es accelerate quiere decir que los valores
cambian cada vez más rápido. En este caso es el que nosinteresa,
porque la pelota caiga cada vez más rápido.

• repeatCount define cuántas veces se debe repetir el efecto. En este


caso, queremos que se repita para siempre.

• repeatMode tiene dos posibles valores: restart haría que al caer la


pelota esta volviera directamente al comienzo, mientras que
reverse haría que el balón pudiera hacer el recorrido inversoal que
ha hecho para caer.

• fromYDelta establece en qué punto (de la y, es decir, la coordenada


vertical) comienza la animación. Se puede poner un valor
absoluto en píxeles ("140"), un valor relativo a la vista
("40%") oa su pariente, en el layout de la aplicación, indicandoun
valor tipo "50% p".

• toYDelta establece en qué punto de la coordenada vertical


termina la animación. El formato es el mismo que para fromYDelta

• duration establece la duración de la animación en milisegundos.

Para las animaciones de tipo translate también están las opciones


fromXDelta y toXDelta equivalentes a los de la Y que acabamosde ver.

Para que la animación se visualice, hay cargarla, asociarla a la imageny


ponerla en marcha. Vaya al método onClick() que ha escrito antes y
añadir lo siguiente:
ImageView imagen = (ImageView)findViewById(R.id.imageView1);

Animation animacionPelota = AnimationUtils.loadAnimation(this, R.anim.botar);

imagen.startAnimation(animacionPelota);

Tenga en cuenta que se ha creado un identificador para la animación con


el nombre del archivo donde la ha definido.
Magia: una pelota que aparece
Menudo utilizará animaciones para hacer aparecer imágenes yotros elementos
de forma gradual, para dar un aspecto más interesante a su aplicación. Hacerlo
es muy sencillo, basta con que cree otro archivo llamado
res/anim/aparecer.xml y que ledeis el siguiente contenido:
<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android">

<alpha

android:fromAlpha="0"

android:toAlpha="1"

android:duration="2000"/>

</set>
La estructura es muy similar a la del archivo de animación anterior,
pero en este caso nos encontramos algunos elementos nuevos:

• alpha se refiere al grado de transparencia de una imagen, o sea


que las animaciones de este tipo lo que harán es cambiar la
transparencia con el tiempo.

• fromAlpha es el valor inicial de transparencia; 0 significa


transparente del todo, es decir, invisible.

• toAlpha es el valor final de transparencia; 1 significa opaco del


todo.

Para que su aplicación utilice esta nueva animación, hay que editar el
método onClick() y hacer que el
orden loadAnimation cargue R.anim.apararecer que es la nueva
animación. Intente ejecutar la aplicación para notar el efecto. Como
veis, en este caso la animación no se repite si no vuelva a hacer clic en
la imagen.
Hacer rodar el balón

Si lo que desea ver es el balón rodando de un lado a otro, hay que


añadir un nuevo archivo de animación llamado rodar. Xml con el
siguiente contenido:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="4000"/>

<translate
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:fromXDelta="-50%p"
android:toXDelta="50%p"
android:duration="4000"/>
</set>

La novedad, en este caso, es que hay dos transformaciones: la pelota


gira (rotate) pero también se desplaza. Por eso hay que escribir dos
entradas. En la sección rotate hay algunas opciones nuevas:
El orden en el que se escriben las diferentes transformaciones en un fichero
de animaciones es fundamental, porque es el orden en el que se
aplican. Pruebe a cambiar de orden el rotate y el translate y comprobaréis
la diferencia.

• fromDegrees en qué ángulo (medido en grados) comienza la rotación.

• toDegrees en qué ángulo termina la rotación. En este ejemplo


termina a los 360 ° para que haga la vuelta completa. Si quieres
que dé más vueltas puede poner un valor mayor que 360, por
ejemplo 720 para que dé dos vueltas cada vez.

• pivotX y pivotY indican el punto de la imagen respecto al cual esta


girará: no es lo mismo girar respecto del centro, como en este
caso, que girar respecto de un lado.

Por cierto, recordad que editar el onClick() para que cargue esta
animación y no otra.
Hacer venir de lejos el balón

Finalmente, creará una animación que muestre la imagen que viene de


lejos y al mismo tiempo girando. Esta animación quedaría muy bonita
con la imagen de una portada de periódico, como en las películas
antiguas donde se acostumbraba a hacer efecto cuando salía alguna
noticia en los periódicos.

Agregue otro archivo de animación que puede llamar venir.xml y


escriba el siguiente código:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:interpolator="@android:anim/linear_interpolator"
android:duration="500"
android:repeatCount="8"
android:pivotX="40%"
android:pivotY="40%"
android:fromDegrees="0"
android:toDegrees="360"/>

<scale
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="0"
android:duration="4000"
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="0"
android:toXScale="1"
android:fromYScale="0"
android:toYScale="1"/>
</set>

Ahora, en el rotate hemos cambiado el punto de pivote para que la imagen


gire un poco más y no sólo dé vueltas sobre sí misma.
Reproducción de animaciones

Desde la versión de Android Honeycomb (3.x) se puede añadir una opción en la


etiqueta set para reproducir las operaciones de animación una detrás de la otra y no
todas a la vez: <set android:ordering = “sequentially”>

En cuanto a la transformación de escala scale sus parámetros son


bastante evidentes después de haber trabajado con las otras
transformaciones. Básicamente hay que indicar la escala inicial yfinal
en X e Y.

Podemos asociar una misma animación a varios visores al mismo tiempo,


para dar un estilo uniforme en nuestra aplicación.
Fotogramas a alta velocidad

Cada una de las imágenes de la secuencia recibe el nombre de fotograma, y al fin y


al cabo el cine, la televisión y los videojuegos, lo que hacen es mostrar fotogramas a
alta velocidad (como mínimo, 25 por segundo) para dar -nos la impresión de que
estamos viendo movimiento continuo.

Comience un nuevo proyecto Android (en el ejemplo lo llamaremos


Multimedia4). Encuentre un conjunto de imágenes utilizables como
animación, como por ejemplo el que se muestra en la figura.

Figura Conjunto de imágenes para crear una animación por fotogramas


Con ocho o diez imágenes es suficiente para apreciar el efecto, no debe
utilizar muchas más imágenes.
Aunque el efecto es más evidente si las imágenes forman parte de una secuencia,
también puede utilizar imágenes independientes.

En primer lugar, asegúrese de que los archivos de imagen tienen un nombre


adecuado para añadirlos a los proyectos: nombres en minúscula y sin espacios
y sin signos que no sean guiones bajos. A continuación, arrastre las imágenes
en la carpeta res/drawable. A continuación, añadir un nuevo archivo XML de
tipo (Resource Type) drawable y escriba en Root Elemento: animation-list
que es una lista de animación. Tal como muestra la figura, darle un nombre
válido en el archivo y continúe.
Figura Creación de una lista de animación

A continuación, edite el archivo XML para que quede de la siguiente manera


(en vez de "frameXX", deberá escribir el nombre de las imágenes que ha
agregado previamente, sin la extensión):
<?xml version="1.0"encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/frame00" android:duration="200"/>
<item android:drawable="@drawable/frame01" android:duration="200"/>
<item android:drawable="@drawable/frame02" android:duration="200"/>
<item android:drawable="@drawable/frame03" android:duration="200"/>
<item android:drawable="@drawable/frame04" android:duration="200"/>
<item android:drawable="@drawable/frame05" android:duration="200"/>
<item android:drawable="@drawable/frame06" android:duration="200"/>
<item android:drawable="@drawable/frame07" android:duration="200"/>
</animation-list>

Con este archivo se define cuál es la secuencia de imágenes que formarán la


animación y cuál es la duración de cada fotograma, en milisegundos.
A continuación hay que crear un ImageView el layout y, a la propiedad
src, asociarle la animación que acaba de crear (en este ejemplo se llama
brickfilm), como se muestra en la figura.

Figura Seleccionar la animación para fotogramas


Si intenta ejecutar la aplicación sólo verá el primer fotograma; hay que
decirle a la animación que se ejecute. Para ello, vaya al MainActivity.
Java y añadir las siguientes instrucciones al final del método onCreate()
ImageView imatge = (ImageView) findViewById(R.id.imageView);

AnimationDrawable animacio = (AnimationDrawable) imatge.getDrawable();

animacio.start();

Compruebe que se han añadido los imports y ejecute laaplicación.


Básicamente lo que ha hecho con estas instrucciones es acceder a la
imagen, obtener lo que está dibujando con getDrawable() (en
este caso, la animación, porque así se lo ha indicado), y decirle a esta
animación que empiece a ejecutarse. Enla figura puede ver una captura
de la aplicación en funcionamiento.
5.Geolocalización
GPS significa Global Positioning System, es decir, sistema de posicionamiento global,
y es un sistema que permite conocer la situación propia (longitud y latitud) mediante
señales enviadas por satélites.

Una de las características más interesantes de los dispositivos móviles


es su capacidad de detectar su posición geográfica para dar al usuario
una serie de información, tales como mostrar su situación en un mapa,
listar los recursos más cercanos (restaurantes, gasolineras ...),
registrar su recorrido mientras marcha por la montaña, etc. El método
más conocido de geolocalización, es decir, de obtención de la posición
geográfica, es el sistema GPS.

Hay otros métodos de geolocalización, como detectar las redes Wi-Fi


presentes y deducir a partir de las redes identificadas cuál es la
posición geográfica. Este método no es tan preciso como el GPS, pero
por otra parte puede funcionar bajo tierra o dentro de edificios, donde
la señal GPS generalmente no llega.

En este apartado verá cómo obtener las coordenadas GPS y luego las
utilizará para visualizar su posición en un mapa.

5.1. Coordenadas GPS

Primeramente hay que dar permiso a su aplicación para acceder al


sensor GPS. Para ello, después de crear una nueva aplicación Android,
edite el archivo AndroidManifest. Xml y añada la siguiente línea justo
antes de la etiqueta <application>:
La posición del usuario del dispositivo es un aspecto más de su privacidad, por eso
es necesario que la aplicación solicite permiso para acceder al GPS.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Para que su actividad pueda recibir las actualizaciones de la posición


del dispositivo, es necesario que implemente LocationListener,
entonces edite la cabecera de la actividad para que quede de la
siguiente manera:
public class MainActivity extends ActionBarActivity implements LocationListener {

Añádase el import android. location. LocationListener si no lo ha


hecho ya el Android Studio. Verá que nos marca como error el nombre
de la clase que no implementa los métodos del LocationListener; al

25
mensaje que aparece, haga clic en "Implemento methods" para que
los añada.Concretamente, los métodos que debemos implementar son:

 onLocationChanged: es llamado cuando la localización GPS


cambia, típicamente para que el dispositivo se desplaza a otra
posición. Sirve para actualizar la posición a la aplicación.

 onProviderDisabled: se hace la llamada cuando el usuario ha


deshabilitado el servicio GPS.

 onProviderEnabled: al contrario que el anterior, es llamado


cuando el usuario ha habilitado el servicio.

 onStatusChanged: es llamado cuando el servicio cambia de


estado, de activado a desactivado o viceversa. Nos sirve para
saber cuándo el provider es incapaz de obtener la información de
posición o si la recupera después de un tiempo de inactividad. El
parámetro status nos informa de este hecho, toma tres
valores: OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE y AV
AILABLE.

Antes de completar los métodos anteriores hay que activar el sistema


de localización añadiendo el siguiente código al onCreate () de su
actividad:
LocationManager gestorLoc =

(LocationManager)getSystemService(Context.LOCATION_SERVICE);

gestorLoc.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000,1,this);

Como siempre, habrá que añadir los importes que les diga el Android
Studio. Con estas instrucciones se accede al servicio GPS y se pide que
las actualizaciones de posición se envíen a esta actividad. Los
argumentos numéricos son, respectivamente, el tiempo aproximado
(en milisegundos) entre actualizaciones y la distancia mínima (en
metros) que debe cambiar la posición para recibir una actualización. En
una aplicación real habrá que poner valores adecuados.

Lo más importante es el método onLocationChanged (), que es llamado


cada vez que la posición del dispositivo cambia. Edite el fin de que
quede como sigue:

26
@Override

public void onLocationChanged(Location location) {

String text = "Posicion actual:\n" +

"Latitud = " + location.getLatitude() + "\n" +

"Longitud = " + location.getLongitude();

Toast.makeText(getApplicationContext(), text,

Toast.LENGTH_LONG).show();

El método recibe la posición actual y lo que hace es formar un texto


con la latitud y longitud, que son los parámetros que definen la posición
geográfica.

Las toast (tostadas) son pequeños mensajes temporales que podemos utilizar para
mostrar información al usuario.

También llenaréis los métodos siguientes para que la aplicación


notifique al usuario cuando la cobertura GPS se pierde o se recupera:
@Override
public void onProviderDisabled(String provider) {
Toast.makeText(getApplicationContext(),
"GPS desactivado por el usuario”,
Toast.LENGTH_LONG ).show();
}

@Override
public void onProviderEnabled(String provider) {
Toast.makeText(getApplicationContext(),
"GPS activado por el usuario”,

Toast.LENGTH_LONG).show();
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

String missatge = "";


switch (status) {
case LocationProvider.OUT_OF_SERVICE:

27
missatge = "GPS status: Out of service";
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
missatge = "GPS status: Temporarily unavailable";
break;
case LocationProvider.AVAILABLE:
missatge = "GPS status: Available";
break;
}

Toast.makeText(getApplicationContext(),
missatge,
Toast.LENGTH_LONG).show();
}

Ahora bien, ¿cómo puede probar esta aplicación? La primera opción y


la más realista es ejecutarla en un dispositivo real, pero si esto no es
posible, tiene dos opciones que no son demasiado realistas pero que le
permitirán probarla.

En primer lugar, ejecute la aplicación en el emulador. Con la aplicación


en marcha, abra una ventana del terminal y escriba lo siguiente:
telnet localhost 5554

geo fix 30.1 28.4

Con ello, lo que haga es conectarse al emulador y enviarle la longitud


y la latitud. El número 5554 es el puerto por defecto por el que puede
conectarse al emulador. El puerto específico que utiliza su emulador se
muestra en su barra de aplicación, vea por ejemplo el 5554 en
la figura. Si miráis la pantalla del emulador, verá como aparece la
información que le ha enviado, similar a la toast de la figura . Cuidado,
que desaparece en unos segundos!

La latitud es la distancia (angular) de un punto en el ecuador, es decir, en el eje sur-


norte. La longitud es la distancia (angular) de un punto en el meridiano de
Greenwich, es decir, en el eje este-oeste.

Figura Toast con las coordenadas GPS

28
La otra opción, sólo disponible si utiliza el entorno Android Studio, es
ir a Tools/Android/Android Device Monitor, concretamente en la
pestaña Emulator Control, que contiene diferentes herramientas para
controlar el emulador: para simular una llamada telefónica, la
recepción de un SMS ... Si sigue bajando dentro de esta ventana
encontrará una herramienta llamada Location Controles que permite
enviar la posición en el emulador (pestaña GPS), y que se muestra en
la figura.

Figura Herramienta para enviar coordenadas GPS

Si introduce una coordenada y pulse el botón Send la enviarás al


emulador. Pero esta herramienta le ofrece una manera mucho mejor
de introducir coordenadas GPS: usar archivos GPX.

Los archivos GPX (GPS eXchange formato) son archivos XML que
contienen recorridos expresados como secuencias de coordenadas
GPS, con un tiempo de llegada asociado a cada punto.

Si abre la pestaña GPX puede cargar un archivo en este formato y


enviarlo al emulador con el botón Play. También hay un control
llamado Speed que le permite enviar las coordenadas más
rápidamente si desea, sobre todo porque a menudo estos archivos se
han registrado caminando o en bicicleta y, por tanto, se mueven
lentamente por el mapa. En la figura .14 podéis ver un ejemplo.

29
Figura Carga de una ruta GPX

De esta manera se puede ver cómo se van actualizando las coordenadas a la


aplicación, haciendo el recorrido del mapa.
La otra opción, KML (Keyhole Markup Language), es equivalente a GPX con la
diferencia que es el formato que utiliza Google Earth.
Puede descargar gratis archivos GPX o KML en la web http://es. Wikiloc.com.

5.2. Visualización de mapas

Apenas se ha visto cómo leer la posición geográfica del dispositivo (y


del usuario, seguramente!) Mediante el servicio GPS, pero sólo lo ha
podido utilizar para mostrar las coordenadas por pantalla con una
simple toast. Su aplicación sería más atractiva si en vez de escribir
unos números por pantalla dibujara el mapa con la situación actual y
la fuera actualizando a medida que se mueve. Esto es lo que haréis en
este apartado.

Como probablemente ya saben, la fuente más importante de mapas e


imágenes por satélite es Google Maps/Earth, y dado que Google ha
sido la creadora de Android lo ha puesto bastante fácil porque utilice
sus mapas a las aplicaciones Android.

Para poder hacerlo, es necesario configurar su proyecto para que utilice


las Google API y los Google Play services, es decir, el conjunto de
bibliotecas y servicios para acceder a las aplicaciones y recursos de
Google.

30
En primer lugar configuraremos un emulador para trabajar con las
google APIs. Tendremos que ir al Android SDK Manager y descargar la
última versión de:

 Google APIs Intel x86 Atom System Image de la última


versión del SDK.

 Google APIs de la última versión del SDK.

 Google Play services, que encontrará en la sección extras.

Figura Android SDK Manager con Google API instalado


Con el fin de ejecutar un proyecto que utilice las Google APIs necesitamos
disponer de un AVD con las librerías y aplicaciones necesarias. Así,
compruebe que el target de su emulador sea "Google APIs", como puede
observarse en la figura .

Figura Emulador con Google APIs

31
Ahora que ya lo tenemos todo listo para poder crear el proyecto, introduzca
la siguiente configuración:
 Application name: MultimediaMaps
 Compañero domain: cat.xtec.IOC
 Minium SDK: API 10: Android 2.3.3 (Gingerbread)
En la selección de actividades elija Google Maps Activity y deje la
configuración predeterminada.

Figura Selección de la actividad


En el directorio /res/values encontraremos un archivo
llamado google_maps_api.xml con un contenido similar al siguiente:
<resources>
<!--
TODO: Before you run your application, you need a Google Maps API key.

To get one, follow this link, follow the directions and press "Create" at the
end:

https://console.developers.google.com/flows/enableapi?apiid=maps_android_back
end&keyType=CLIENT_SIDE_ANDROID&r=FF:47:8C:B5:B3:8A:XX:13:79:XX:E5:DB:4F:XX:8
F:6A:A5:BD:FF:F5%3Bioc.xtec.cat.multimediamaps

You can also add your credentials to an existing key, using this line:
FF:47:8C:B5:B3:8A:XX:13:79:XX:E5:DB:4F:XX:8F:6A:A5:BD:FF:F5;ioc.xtec.cat.mult
imediamaps

32
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" translatable="false"
templateMergeStrategy="preserve">
YOUR_KEY_HERE
</string>
</resources>

Figura Creación de la clave

Acceda a la URL que se os propone desde un navegador web y,


después de crear un nuevo proyecto, se le permitirá crear una clave
para poder utilizar la API de Google Maps (como puede verse en
la figura). Para añadir más aplicaciones ligadas a una misma clave
debemos añadir líneas con el SHA1, un ";" y el package name de la
aplicación. Una vez generada, copie el valor del campo CLAVE DE
LA API y péguelo en el XML como contenido de la
etiqueta string (sustituiremos el texto YOUR_KEY_HERE).

33
Con estos pasos ya podremos ejecutar el emulador para comprobar el
funcionamiento del mapa, pero antes observamos cuáles son las
modificaciones que ha hecho el Android Studio para que todo
funcione. Si abre el archivo AndroidManifest.xml podrá observar el
siguiente contenido:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ioc.xtec.cat.multimediamaps" >

<uses-permission android:name="android.permission.INTERNET" />


<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but are recommended.
-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="@string/google_maps_key" />

<activity
android:name=".MapsActivity"
android:label="@string/title_activity_maps" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />


</intent-filter>
</activity>
</application>

</manifest>

Podemos observar que añade los siguientes permisos:


 android.permission.INTERNET: para acceder a Internet,
necesario para descargar los mapas.

34
 android.permission.ACCESS_NETWORK_STATE: para detectar
cambios en la red.

 android.permission.WRITE_EXTERNAL_STORAGE: permite
escribir el almacenamiento del teléfono, en este caso es
necesario para descargar archivos para generar la caché de los
mapas.

 com.google.android.providers.gsf.permission.READ_GSERVICES
: permite al API acceder a los servicios de Google.

 android.permission.ACCESS_COARSE_LOCATION: acceso a la
posición aproximada mediante la red: wifi y torres de telefonía.

 android.permission.ACCESS_FINE_LOCATION: acceso a la
 posición más precisa, utilizando además el GPS para obtenerla.
Y además, añade dos etiquetas meta-data con la información sobre la
versión de los Google Play Services y la clave que hemos añadido al
archivo google_maps_api.xml.

El layout de la aplicación constará de un fragmento que será


manipulado desde el fichero MapsActivity. Java. Si nos fijamos con el
código de la actividad, podemos ver como acaba llamando a setUpMap
() que añade un marcador en el mapa en la coordenada (0,0) con el
título de "Marker".

Ejecute el proyecto y visualizará un mapa del mundo con un marcador


en la coordenada geográfica (0,0), es decir, a la latitud y longitud 0. Si
hace clic sobre la marca os saldrá el texto "Marker" tal y como se puede
ver la figura.

35
Figura Google Maps en el emulador

Para añadir controles de zoom a su aplicación de forma que pueda


acercaros desde el emulador de una manera cómoda necesario que
cambie el método setUpMapIfNeeded () para que quede de la siguiente
manera:
private void setUpMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the map.
if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
setUpMap();
UiSettings settings = mMap.getUiSettings();
settings.setZoomControlsEnabled(true);
}
}
}

Es decir, añadimos las líneas UiSettings settings = mMap.getUiSettings


(); y settings.setZoomControlsEnabled (true); para obtener la
configuración del mapa y modificar el valor de ZoomControlsEnabled para
ponerlo a true.

Podrá encontrar más métodos para configurar su mapa mediante la


variable mmap; por ejemplo, si desea cambiar el tipo de mapa puede
acceder al método mMap.setMapType (int type), que recibe como

36
argumento el tipo de Pruebe los diferentes valores y observe los
cambios que se producen en la representación.

5.3. Seguimiento de su posición

Si queremos que el mapa se actualice con nuestra posición deberemos


implementar LocationListener y, cada vez que se produzca un cambio
de éstos, actualizar el mapa.

Así, haga que la clase MapsActivity implemente LocationListener y


recuerde añadir los métodos que Android Studio nos exige.
public class MapsActivity extends FragmentActivity implements
LocationListener {
...
@Override
public void onLocationChanged(Location location) {

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

@Override
public void onProviderEnabled(String provider) {

@Override
public void onProviderDisabled(String provider) {

}
...
}

Desde los métodos onStatusChanged, onProviderEnabled y


onProviderDisabled podríamos informar sobre los cambios que se
produzcan en el servicio GPS; ahora, sin embargo, nos centraremos en
el método onLocationChanged, donde tendremos que:

 Obtener la posición a partir del parámetro location.

 Crear la cámara (área del mapa que se visualiza en un momento


determinado) con la posición y un nivel de zoom.

37
 Desplazar la cámara en el punto propuesto.
LatLng almacena un par de coordenadas, la latitud y la longitud.

Así, añadiremos el siguiente código a onLocationChanged:

@Override

public void onLocationChanged (Location location) {

// Creamos un LatLng a partir de la posición

LatLng posicion = new LatLng (location.getLatitude(),

location.getLongitude ());

// Añadimos la cámara con el punto generado antes y un nivel de zoom

CameraUpdate cámara = CameraUpdateFactory.newLatLngZoom(posicion, 19);

// Desplazamos la cámara en el nuevo punto

mmap.moveCamera (camara);

Una vez hemos obtenido la posición, hacemos la llamada al


método newLatLngZomm (LatLng latLng, float
zoom) de CameraUpdateFactory y que recibe dos parámetros: el primero
determinará el punto donde se situará la cámara y el segundo
especificará el nivel de zoom, que deberá tomar un valor entre 2.0fi
21.0f. Finalmente, actualizamos la cámara del mapa.

Para que el listener comience a controlar los cambios de posición,


recuerde añadir al onCreate el siguiente código:
LocationManager gestorLoc = (LocationManager) getSystemService

(Context.LOCATION_SERVICE);

gestorLoc.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000, 1, this);

Si intenta enviar unas coordenadas GPS en el emulador, comprobaréis


cómo cambia la vista. Lógicamente, cuanto más zoom aplicamos, más
evidente será el movimiento. Para ver el efecto es recomendable
cargar un fichero GPX con el DDMS (Tools / Android / Android Device
Monitor), y que disfrute del paseo!

38
5.4. Añadiendo marcas en el mapa

A menudo las aplicaciones que muestran mapas añaden marcas para


señalar al usuario los puntos que le sean interesantes, como los
restaurantes más cercanos, las paradas de metro o las ciudades que
ha visitado. En este apartado verá cómo añadir estas señales (iconos)
en su mapa, y cómo colocarlos donde desee. Para ello continuará con
la aplicación anterior.

Para ir añadiendo las marcas a medida que va cambiando la posición


tendremos que modificar el método onLocationChanged. El
procedimiento será el siguiente:

 Creamos un elemento del tipo MarkerOptions.

 Añadimos las propiedades deseadas:


Título (title), posición (position), descripción (snippet) e
icono (icon).

 Añadimos el marcador en el mapa.

 Si hemos de manipular el marcador posteriormente, podemos


guardar el objeto Marker que devuelve la función addMarker; una
posible utilidad de guardarlo es hacer la llamada a showInfoWindow
() para hacer que se muestre la información del marcador sin tener
que hacer clic sobre él.

El código del método onLocationChanged quedará de la siguiente


manera:
@Override

public void onLocationChanged (Location location) {

// Creamos un LatLng a partir de la posición

LatLng posicion = new LatLng (location.getLatitude (),

location.getLongitude ());

// Añadimos la cámara con el punto generado antes y un nivel de zoom

CameraUpdate camara = CameraUpdateFactory.newLatLngZoom (posicion, 19);

// Desplazamos la cámara en el nuevo punto

39
mmap.moveCamera (camara);

// Creamos y añadimos el marcador

MarkerOptions opciones = new MarkerOptions ();

opciones.title"Título");

opciones.position(posicipn);

opciones.snippet("Descripción del marcador");

opciones.icon (BitmapDescriptorFactory.defaultMarker
(BitmapDescriptorFactory.HUE_ORANGE));

// Aquí el añadimos el mapa y nos lo guardamos en una variable (ya se ve el


icono en el mapa pero hay que hacer clic para ver la información)

Marker marca = mmap.addMarker (opciones);

// Hacemos que se muestre la información sin tener que hacer clic

marca.showInfoWindow();

Si ejecutamos la aplicación y vamos enviando


diferentes posiciones utilizando el Android
Device Manager veremos un resultado similar
al que se muestra en la figura .

Figura Google Maps con marcadores

40
1. Programación de comunicaciones

Cada vez es más habitual que las aplicaciones hagan uso de la


comunicación para Internet, integrándose en las redes sociales o
utilizando servicios web. En este apartado verá cómo hacer uso de las
comunicaciones del dispositivo para comunicarse con el exterior.

Dado que Android es un sistema diseñado para dispositivos móviles, la


conexión continua es uno de sus aspectos fundamentales. Véase
comunicación web y los servicios de mensajería instantánea y
multimedia. Para seguir los contenidos de este módulo, es conveniente
ir haciendo las actividades. Aunque las unidades tienen un contenido
importante desde el punto de vista conceptual, siempre ha procurado
darles un enfoque práctico en las actividades propuestas.

1.1. Comunicaciones en Android

El primer paso para trabajar con la API de comunicaciones en Android


es pedir los permisos para su aplicación al documento
de manifiesto. Esto lo puede hacer añadiendo las siguientes líneas en
dicho documento:

1 <uses-permission android:name =
"android.permission.INTERNET"/>
2 <uses-permission android:name=
"android.permission.ACCESS_NETWORK_STATE "/>

Esto permite a su aplicación tener acceso a Internet (abrir sockets de


red) y información del estado de la red (por ejemplo, para saber si el
Wi-Fi está activado o desactivado).
Antes de hacer un intento de conexión a la red, deberá verificar si el
dispositivo tiene una conexión de red disponible y funcionando
correctamente. Por ello debe utilizar objetos de la clase
ConnectivityManager y NetworkingInfo de la siguiente manera:

4
1 // Obtenemos un gestor de las conexiones de red
2 ConnectivityManager connMgr = (ConnectivityManager)
getSystemService (Contexto.CONNECTIVITY_SERVICE);
3
4 // Obtenemos el estado de la red
5 NetworkInfo networkInfo = connMgr.getActiveNetworkInfo ();
6
7 // Si está conectado
8 if (networkInfo! = null && networkInfo.isConnected ()) {
9 // Red OK
10 Toast.makeText (this, "Red ok", Toast.LENGTH_LONG) .show ();
11 } else {
12 // Red no disponible
13 Toast.makeText (this, "Red no disponible",
Toast.LENGTH_LONG).show ();
14 }

Con getSystemService () obtiene un objeto ConnectivityManager que


sirve para gestionar la conexión de red. Con este objeto tiene una
forma objeto NetworkInfo, con getActiveNetworkInfo () que devuelve
una instancia de NetworkInfo, que representa la primera interfaz de
red que puede encontrar o un null si ninguna conexión está conectada.

A NetworkInfo tiene el método isConnected (), que indica si existe


conectados actividad en la red y si es posible establecer conexiones.
En caso de que desee obtener información individual de los diferentes
tipos de redes, lo puede hacer de forma individual con
getNetworkingInfo (), con un argumento que es una constante que
indica el tipo de red que desea comprobar, como se puede ver en el
siguiente código:

1 // Obtenemos el estado de la red móvil


2 NetworkInfo networkInfo = connMgr.getNetworkInfo
(ConnectivityManager.TYPE_MOBILE);
3 boolean connectat3G = networkInfo.isConnected();
4
5 // Obtenemos el estado de la red wifi
6 networkInfo = connMgr.getNetworkInfo (
ConnectivityManager.TYPE_WIFI);
7 boolean connectatWifi = networkInfo.isConnected();

Es muy importante que siempre compruebe la conexión a la red y no


de por hecho que la red está disponible. Piense que las operaciones de
red a un dispositivo móvil suelen ser móviles, 3G o Wi-Fi, y existen

5
posibilidades reales de caída de la conexión. Por lo tanto, debe hacer
estas comprobaciones cuando enciende la aplicación o cuando vuelva
después de haber salido. El lugar ideal para hacerlo sería el método
onStart () de su aplicación.

Imagine que enciende la aplicación y se hace la comprobación de


conexión al comienzo, y encuentra que existe conexión. Si el usuario
ejecuta la aplicación de configuración del dispositivo y activa el modo
de avión, su aplicación seguirá funcionando correctamente, ya que
cuando el usuario vuelva a su aplicación onStart() volverá a ser
llamado y detectará que la red está desconectada.

1.2. BroadcastReceiver

Pero ¿qué pasa cuando la conexión a Internet cambia de estado


mientras está dentro de su aplicación? Esto puede ocurrir porque el
dispositivo pierda la cobertura o la conexión a la red. Como no se
vuelve a llamar onStart (), no podría comprobar la conectividad. Por
eso, lo que necesita hacer es registrar
un BroadcastReceiver (receptor multidifusión) que escuchará los
cambios en el estado de la conexión y comprobará cuál es el estado de
la red.

En primer lugar, cree una clase que herede de BroadcastReceiver e


implemente el método abstracto onReceive ().

1 public class ReceptorRed extends BroadcastReceiver {


2
3@Override
4 public void onReceive (Context arg0, Intent arg1) {
5 // Actualizar el estado de la red
6 ActualizaEstadoRed();
7 }
8}

6
Si esta clase se utilizará dentro de su actividad no debe crear la clase
como un archivo .java independiente en su proyecto, puede ser
definida dentro de la clase de su aplicación. Dentro del método
onReceive () actualizar el estado de la red a su aplicación.

El siguiente paso es registrar el receptor de broadcast para que


escuche los mensajes relacionados con los cambios de la conectividad
de la red. Para ello, cree una instancia de la clase receptora y
registrarse con un filtro de intent para escuchar únicamente los
mensajes de broadcast sabrir la conectividad del dispositivo.

1 private ReceptorRed receptor;


2
3 @Override
4 public void onCreate (Bundle savedInstanceState) {
5 (...) // Resto de código onCreate ()
6
7 IntentFilter filter = new IntentFilter
(ConnectivityManager.CONNECTIVITY_ACTION);
8
9 receptor = new ReceptorRed ();
10 this .registerReceiver (receptor, filter);

El método registerReceiver () registra un receptor de broadcast para


ejecutarse el hilo principal de la aplicación. El receptor
de broadcast escuchará cualquier intent de broadcast que coincida con
el filtro definido en el hilo principal de la aplicación.

En este momento, los mensajes de broadcast que anuncien los


cambios en la conectividad de la red serán recibidos por su receptor,
el cual actualizará el estado de la red de la aplicación. Tener un
receptor de broadcast que es llamado constantemente o
innecesariamente puede derivar en un consumo excesivo de los
recursos del sistema (entre otros, de la batería). Si declara el receptor
de broadcast en el documento de manifiesto, este puede poner en
marcha la aplicación automáticamente, aunque esta no esté en
ejecución.

7
Por este motivo, lo más adecuado sería registrar el receptor
de broadcast cuando se crea la aplicación onCreate () y darlo de baja
al método onDestroy ().

1 public void onDestroy () {


2 super.onDestroy ();
3
4 // Damos de baja el receptor de broadcast cuando se destruye la
// aplicación
5 if (receptor! = null) {
6 this.unregisterReceiver(receptor);
7 }
8}

1.3. Permisos a partir de la versión 6

A partir de Android 6 Marshmallow (API 23) los permisos deben estar


en el manifest pero el usuario debe aprobar cada uno de los permisos
explicitamente durante la ejecución. En la siguiente web se explica el
tema:
https://developer.android.com/training/permissions/requesting.html

Por ejemplo, si deseamos objener el permiso de comunicación a través


de Internet para cargar una imagen en segundo plano, habrá que
incluir este código:

void permisosCargaImagen(){
//Comprobamos que la versión sea mayor que Marshmallow y
todavía no tenemos asignado el permiso de Internet
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getActivity().checkSelfPermission(Manifest.permission.INTERNET)
!= PackageManager.PERMISSION_GRANTED) {
//Solicitamos el permiso, al finalizar llamará
a la función onRequestPermissionsResult
requestPermissions(new String[]{Manifest.permission.INTERNET},
MY_PERMISSIONS_REQUEST_INTERNET);
}
else {
//El permiso está concedido y procedemos a cargar la imagen
en segundo plano
CargaImagen cargaImagen = new CargaImagen();
cargaImagen.execute("http://4.bp.blogspot.com/-
SiQNg1gmn5o/UyDYG1mMjjI/AAAAAAAAATs/wqi8OvtYvME/s1600/periodico.png");
}
}

8
@Override
public void onRequestPermissionsResult(int requestCode,String permissions[],
int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_INTERNET: {
// Si el permiso se ha concedido volvemos a llamar
a la función para cargar la imagen
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getActivity(),
"Permiso concedido", Toast.LENGTH_LONG).show();
permisosCargaImagen();
} else {
Toast.makeText(getActivity(),
"Permiso denegado", Toast.LENGTH_LONG).show();
}
return;
}
}
}

1.4. Mostrar páginas web con un 'widget'

Posiblemente, el servicio de comunicación más utilizado hoy en día son


las páginas web, por lo que comenzaremos mostrando cómo podemos
visualizarlas dentro de las nuestras aplicaciones.
El procedimiento es muy sencillo porque hay un componente
llamado WebView que ya hace casi todo el trabajo, sólo hay que
decirle qué dirección queremos visitar.
Cree un nuevo proyecto Android. Como que su aplicación accederá,
lógicamente, en Internet, debe añadir el permiso correspondiente al
fichero AndroidManifest.xml (a continuación del uses-sdk):

1<uses-permission android:name="android.permission.INTERNET" />

9
Guarde y vaya al layout.xml para añadir el componente WebView,
además de un EditText para introducir la dirección web y un botón para
ir:
1<?xml version="1.0" encoding="utf 8"?>
2<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <LinearLayout
8 android:id="@+id/linearLayout1"
9 android:layout_width="match_parent"
10 android:layout_height="wrap_content"
11 android:orientation="horizontal">
12
13 <EditText
14 android:id="@+id/editText1"
15 android:layout_width="264dp"
16 android:layout_height="wrap_content"
17 android:hint="Introduce la dirección web">
18
19 <requestFocus/>
20 </EditText>
21
22 <Button
23 android:id="@+id/button1"
24 android:layout_width="wrap_content"
25 android:layout_height="wrap_content"
26 android:onClick="onClickIr"
27 android:text="Ir"/>
28
29 </LinearLayout>
30
31 <WebView
32 android:id="@+id/webView1"
33 android:layout_width="match_parent"
34 android:layout_height="match_parent"/>
35
36 </LinearLayout>

Sólo hay que ir al código de la actividad para añadir la funcionalidad


necesaria. Básicamente, se trata de escribir el método onClickIr() que
se activará cuando pulsamos el botón, y que tiene que decir al
WebView que abra una página:

1 public void onClickIr (View v) {


2 WebView webView = (WebView) findViewById(R.id.webView1);
3 EditText editText = (EditText) findViewById (R.id.editText1);
4
5 String direccion = editText.getText().ToString ();
6 webView.loadUrl(direccion);

10
7}

Con ello podemos ejecutar la aplicación, indicar una web y ir, tal como
se puede ver en la figura:

Observará que, si escribe una


dirección sin el prefijo "http://", la
aplicación da un error. Esto es porque
realmente la dirección completa debe
incluir este prefijo (que es el
protocolo de aplicación). Por lo tanto,
puede añadir el siguiente código para
que lo haga, editando el método
onClickIr():

1 String direccion = editText.getText().ToString ();


2
3 if (!direccion.startsWith ("http://") &&
!direccion.startsWith ("https://")) {
4 direccion = "http://" + direccion;
5 editText.setText (direccion);
6 }
7
8 webView.loadUrl(direccion);

Puede comprobar como haciendo doble clic en la página la vista se


acerca (hace zoom) y le permite arrastrarla para irse desplazando por
todos sus contenidos según necesite, así que puede pulsar enlaces y
hacer cualquier otra actividad con las páginas que vaya abriendo.

11
2. Proveedores de contenidos

Los content providers (proveedores de contenidos) dan acceso a una


serie estructurada de datos y sirven de interfaz de datos estándar para
conectar datos de un proceso con el código que se está ejecutando en
otro proceso. Los proveedores de contenidos son la forma
recomendada de compartir datos entre aplicaciones. Son
almacenes de datos que sirven para compartir datos entre
aplicaciones. Se comportan de forma similar a una base de datos
(puede hacer consultas, editar el contenido, añadir y borrar), pero a
diferencia de estas utilizan diferentes formas para almacenar sus
datos. Estas pueden estar en una base de datos, en ficheros o incluso
en la red, pero para el proceso que las utiliza esto es transparente (y
nos resulta indiferente).

Android utiliza una serie de proveedores de contenidos, estándares del


sistema, que pueden utilizar el resto de aplicaciones, por ejemplo:

• Browser: almacena datos como los marcadores del navegador, el


historial de navegación, etc.
• CallLog: almacena datos como llamadas perdidas, detalle de las
llamadas, etc.
• ContactsContract: almacena información de los contactos:
nombre, email, teléfono, fotos, etc.
• MediaStore: almacena datos multimedia como imágenes, audio y
vídeo.
• Settings: almacena datos de configuración y preferencias del
dispositivo como el bluetooth, wifi, etc.

Puede encontrar una lista de proveedores (del paquete


android.provider) en la Guía del desarrollador de Android, que puede
encontrar aquí:

12
http://developer.android.com/reference/android/provider/package-
summary.html .

Aparte de estos proveedores de contenidos, puede crear sus propios.


Cuando quiera acceder a los datos de un proveedor de contenidos
deberá servir un objeto ContentResolver en el contexto de su
aplicación. Este objeto trabaja como cliente de un objeto proveedor
ContentProvider, que trabaja como servidor. El proveedor recibe los
datos de los clientes, realiza la tarea y devuelve los resultados. Debe
crear un ContentProvider si desea compartir datos de su aplicación con
otros, pero para utilizar datos de otras aplicaciones simplemente
necesita un ContentResolver.

2.1.1. Accediendo al proveedor de contenidos

El ContentProvider presenta datos a las aplicaciones, como una o más


tablas similares a las que encontramos en las bases de datos
relacionales. Cada fila representa un elemento del proveedor de
contenidos, y cada columna un dato concreto del mismo elemento de
la fila.
La aplicación accede a los datos del proveedor a través del objeto
cliente ContentResolver. Este objeto contiene métodos que llaman
otros métodos (por cierto, con el mismo nombre) en la clase
ContentProvider. Por ejemplo, para obtener una lista de contactos
puede llamar al método ContentResolver.query(), y este llamará al
método query() de ContentProvider. El método query() tiene una serie
de argumentos que sirven para definir la consulta que se quiere hacer
al proveedor. Estos argumentos tienen un paralelismo con las opciones
de una consulta query a una base de datos:

• Uri uri: URI que representa la tabla de donde se obtendrán los datos.

13
• String [] projection: lista de las columnas que se devolverán por cada
fila de la tabla.
• String selection: filtro que indica qué filas devolver con el mismo
formato que la cláusula WHERE de SQL (sin la palabra "WHERE"). Si se
pasa un null devolverá todas las filas de la URI.
• String [] selectionArgs: en el argumento anterior (selection), en
lugar incluir el valor de las columnas, directamente puede indicar "=?"
en la selección. Los "?" Serán sustituidos por los valores
del array selectionArgs en el orden en que aparecen en selection.
• String sortOrder: establece como ordenan las filas, con el mismo
formato que la cláusula de SQL ORDER BY (excluyendo las palabras
"ORDER BY "). Si se pasa null se utilizará el orden predeterminado. Se
pueden consultar los argumentos y su equivalente en un SELECT de
SQL en la tabla.

Equivalencia de argumentos entre query () y una consulta SELECT de


SQL :
Argumento de query () Equivalente en SELECT de SQL

Uri uri FROM tabla


String [] projection columna, columna, columna
String selection WHERE col = valor
String [] selectionArgs No existe un equivalente
String sortOrder ORDER BY col, col, ...

El método devuelve un objeto de la clase Cursor, posicionado antes de


la primera entrada o null en caso de encontrar algún problema. El URI
especifica los datos en el proveedor. Los URI incluyen el nombre del
proveedor (llamado authority, autoridad) y un nombre que apunta a
una tabla (path). El ContentProvider utiliza el path del URI para

14
escoger la mesa a la que accederá (tiene un path para cada tabla). La
estructura de un URI es ésta:

1 <prefijo>://<authority>/<path>/<id>

donde:

• El prefijo para los proveedores de contenidos siempre es: content://.


• authority especifica el nombre del proveedor de contenidos. Para los
estándares del sistema, por ejemplo: media, call_log, browser. Para
proveedores de terceros, mejor introducir un nombre de dominio
completo como: com.prueba.aplicacion.
• El path especifica la tabla dentro del proveedor.
• El id es un identificador único para una fila concreta de la tabla
especificada.

Algunos ejemplos de proveedores de contenidos del sistema.


String Descripción
content://media/internal/images Lista de las imágenes en la memoria
del dispositivo
content://media/external /images Lista de las imágenes en la memoria externa
del dispositivo (por ejemplo, en la tarjeta SD)
content://call_log/calls Lista de llamadas hechas con el dispositivo
content://browser/bookmarks Lista de los marcadores del navegador web

Por otra parte, muchos proveedores de contenidos tienen una


constante con el URI de acceso a ellos mismos. Por ejemplo, en lugar
de crear vosotros el URI para acceder el diccionario de palabras del
dispositivo puede utilizar lo que él mismo tiene definido:
UserDictionary.Words.CONTENT_URI. O para acceder a la lista de
contactos, ContactsContract.Contacts.CONTENT_URI.

15
Para obtener datos de un proveedor, su aplicación necesita de un
permiso de lectura por parte del proveedor. No se puede pedir este
permiso durante la ejecución, así que para obtenerlo será necesario
que utilice el elemento <uses-permission> en el su archivo
de manifiesto especificando el permiso que desea obtener del
proveedor.

Al quedar definido en el manifiesto, cuando el usuario instala esta


aplicación le está dando los permisos implícitamente. Para saber
exactamente de qué permisos consta el proveedor así como su
nombre, consulte la documentación del proveedor. Por ejemplo, para
pedir que su aplicación tenga permisos de lectura sobre el proveedor
de contactos del dispositivo debe indicarlo en el archivo
de manifiesto de la siguiente manera, antes de la etiqueta
<application>:

1<uses-permission android:name="android.permission.READ_CONTACTS">
2</uses-permission>

El siguiente paso es realizar una consulta al proveedor para obtener


sus datos. El siguiente código muestra cómo hacer una consulta para
obtener todos los contactos de la lista de contactos:

1 // Las columnas que queremos obtener para cada elemento.


2 String [] projection = new String [] {
3 ContactsContract.Contacts._ID,
4 ContactsContract.Contacts.DISPLAY_NAME,
5 ContactsContract.Contacts.HAS_PHONE_NUMBER
6 };
7
8 // Condición: queremos obtener todas las filas (por eso es null).
9 String where = null;
10 String [] whereArgs = null;
11
12 // Orden: que estén ordenados de forma ascendente.
13 String sortOrder = ContactsContract.Contacts.DISPLAY_NAME +
"COLLATE localized ASC ";
14
15 Cursor c = getContentResolver().Query
16 (
17 ContactsContract.Contacts.CONTENT_URI,
18 projection, // Columnas para obtener de cada fila
19 where,// Criterio de selección

16
20 whereArgs, // Criterio de selección
21 sortOrder // Orden
22 );

Este código obtendrá toda la lista de contactos y devolverá un cursor


en el resultado, la variable c. Cuando haya obtenido el cursor, debe
mirar su valor para saber si Ha habido un error, o si el cursor tiene
datos o no. Lo puede comprobar del siguiente modo:

1 // Si ha habido un error
2 if (c == null) {
3 // Código para tratar el error, escribir logs, etc.
4
5 }
6 // Si el cursor está vacío, el proveedor no encontró resultados.
7 elseif (c.getCount () <1) {
8 // El cursor está vacío, el contento provider no tiene elementos.
9 Toast.makeText (this, "No hay datos",
Toast.LENGTH_SHORT).show ();
10} else {
11 // Datos obtenidos
12 Toast.makeText (this, "OK", Toast.LENGTH_SHORT) .show ();
13}

En estos momentos tiene un cursor a los datos obtenidos del


proveedor, así que podría recorrer los datos con el cursor y
trabajar. Por ejemplo, el siguiente código muestra cómo obtener el
identificador y el nombre, y como saber si el contacto tiene número de
teléfono:

1 // Mientras tenemos un nuevo elemento en el cursor


2 while (c.moveToNext ()) {
3 // Obtener ID
4 String contactId = c.getString (c.getColumnIndex
(ContactsContract.Contacts._ID));
5
6 // Obtener nombre
7 String nommbreContacto = c.getString (c.getColumnIndex
(ContactsContract.Contacts.DISPLAY_NAME));
8
9 // Saber si tiene teléfono
10 String hasPhone = c.getString(c.getColumnIndex
(ContactsContract.Contacts.HAS_PHONE_NUMBER));
11
12 // Mostrar
13 Toast.makeText (this, "id:" + contactId + "\ n" + "Nombre:"
+ nombreContacto + "\ n Tiene teléfono: "
+ hasPhone, Toast.LENGTH_SHORT) .show ();

17
14}
15
16 c.close ();

Para obtener una columna a partir del cursor debe utilizar el método
Cursor.getString(int ColumnIndex), al que se le pasa el índice de la
columna que desea obtener. Para obtener el número de columna a
partir del identificador del campo que desea, utilice
Cursor.getColumnIndex(String columnName). Todo esto lo puede
hacer a la vez:

1 String nombreContacto = c.getString(c.getColumnIndex


(ContactsContract.Contacts.//DISPLAY_NAME//));

El código anterior le muestra por pantalla la información de todos los


contactos del proveedor tal como están en el cursor. Pero, ¿qué ocurre
si desea filtrar esta información? Trabajando con SQL puede hacerlo
con una sentencia WHERE columna = valor.

Esto lo puede definir el método query () con los argumentos where y


whereArgs. En realidad, podría obtener fácilmente la misma sentencia
WHERE modificando valor del argumento where. Para obtener el cursor
únicamente los contactos que tengan teléfono, por ejemplo, puede
definir el argumento como:

1 String where = ContactsContract.Contacts.HAS_PHONE_NUMBER + "= 1";

O, para obtener únicamente los contactos con nombre Juan:

1 String where = ContactsContract.Contacts.DISPLAY_NAME + "= 'Juan";

Por cuestiones de seguridad y para evitar que el usuario pueda


introducir código SQL malicioso, se puede utilizar el carácter "?" en la
variable where dentro de la sentencia.

18
Los caracteres "?" Serán sustituidos con los valores
del array de strings whereArgs.

Por ejemplo, la sentencia anterior sería equivalente a:

1 String where = ContactsContract.Contacts.DISPLAY_NAME + "=?";


2 String [] whereArgs = {"Juan"};

SQL Injection
El objetivo de whereArgs y de la sustitución de los "?" es impedir la inclusión de
código SQL malicioso dentro de la sentencia (ver enlace en
la Wikiquipedia ). Imagínese que cree un código para acceder a todos los contactos
que tengan un nombre igual a una variable que introduce el usuario ("WHERE name
= valor_variable"). Y este usuario, en lugar de introducir el nombre de una persona,
introduce: "nothing; DROP TABLE * ". La sentencia SQL resultante, en seres
ejecutada: "WHERE name = nothing. DROP TABLE * "lograría borrar todas las tablas
de la base de datos (si tuviera permiso para hacerlo).

Hasta ahora se ha mostrado el nombre, el identificador y una variable


que indica si el contacto tiene o no teléfono. Pero obtener el teléfono
y el correo electrónico es un poco más complicado. Dado que en la
lista un contacto puede tener varios números de teléfono y varias
direcciones de correo electrónico, éstas no están almacenadas como
variables estáticas sino como un proveedor de contenidos dentro del
proveedor de contenidos de los contactos. Por lo tanto, para obtenerlas
se debe hacer otra consulta y obtener un cursor en la lista de teléfonos
y correos electrónicos, respectivamente. El siguiente código recorre la
lista de teléfonos y correos y les guarda en una variable de tipo String
(se podrían mostrar o hacer cualquier otra cosa con ellos). Al final, la
variable se queda con el último teléfono y correo que será el que se
muestre del contacto (aunque se podría mostrar el primero, o todos).

1 String telefono = null;


2 String email = null;
3

19
4 if (hasPhone.compareTo("1") == 0) {
5 // Obtenemos los teléfonos
6 Cursor telefonos = getContentResolver().query (
7 ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
8 null,
9 ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "="
+ contactId,
10 null,
11 null);
12
13 // Recorremos los teléfonos
14 while (telefonos.moveToNext ()) {
15 telefono = phones.getString(phones.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.NUMBER));
16 }
17
18 // Cerramos el cursor
19 telefonos.close ();
20}
21
22 // Obtener cursor correos
23 Cursor emails = getContentResolver().query
(ContactsContract.CommonDataKinds. Email.CONTENT_URI,
null, ContactsContract.CommonDataKinds.Email.CONTACT_ID+
"=" + ContactId, null, null);
24
25// Recorremos los correos
26 while (emails.moveToNext ()) {
27 email = emails.getString emails.getColumnIndex
(ContactsContract.CommonDataKinds.Email.DATA));
28}
29 // Cerramos el cursor
30 emails.close ();
31
32 // Mostrar
33 Toast.makeText (this, "id:" + contactId + "\ n" + "Nombre:" +
nomContacto + "\ n
Teléfono: "+ telefono +"\n email: "+ email,
Toast.LENGTH_SHORT).show ();

2.1.2. Insertando datos

La forma de insertar datos a un proveedor de contenidos es similar a


la consulta. Por hacerlo, existe el método
ContentResolver.insert(). Este método inserta una nueva fila al
proveedor de contenidos y devuelve la URI de la fila creada. Los valores
de la fila quedan definidos con un objeto de la clase ContentValues. Por
ejemplo, el siguiente código sirve para insertar una nueva palabra al
diccionario personal del usuario del dispositivo. Para que funcione, se

20
deben dar permisos de escritura el diccionario de datos del dispositivo
insertando la siguiente sentencia en el documento de manifiesto:

1 <uses-permission android:name =
"android.permission.WRITE_USER_DICTIONARY">
2</uses-permission>

El siguiente fragmento de código muestra cómo se inserta un nuevo


valor al proveedor de contenidos:

1 Uri UriNueva;
2
3 ContentValues Valores = new ContentValues();
4
5 // Creamos el valor de la nueva entrada
6 Valors.put (UserDictionary.Words.APP_ID, "com.android.ioc");
7 Valors.put (UserDictionary.Words.LOCALE, "es_ES ");
8 Valors.put (UserDictionary.Words.WORD, "Hospitalet");
9 Valors.put (UserDictionary.Words.FREQUENCY, "100");
10
11 // Insertamos
12 UriNueva = getContentResolver (). Insert (
13 UserDictionary.Words.CONTENT_URI,
14 Valores
15);

21
3. Modelo de los hilos

Cuando se pone en marcha una aplicación, el sistema Android pone en


marcha un nuevo proceso para la aplicación con un único hilo de
ejecución.

Hilos de ejecución
Un hilo de ejecución es la unidad de procesamiento más pequeña que se puede
programar en un sistema operativo. Pueden existir múltiples hilos dentro de un
proceso y compartir recursos (como memoria, código y contexto), lo que no sucede
entre diferentes procesos.
Cuando se utiliza un widget como WebView no es necesario un AsyncTask , Porque
el WebView ya genera un hilo propio internamiento.

Por defecto, todos los componentes de la aplicación se ejecutan en este


hilo de ejecución. Pero en algunos casos puede ser conveniente
ejecutar diferentes tareas de la aplicación en hilos de ejecución
independientes.

Cuando una aplicación accede a Internet a otros recursos externos, se


produce un retraso de duración variable pero que puede llegar a ser de
muchos segundos, o incluso de algunos minutos, desde que la
aplicación pide los datos hasta que éstas llegan.

Si la aplicación funciona con un único hilo de ejecución, éste se puede


quedar parado en la llamada a un método (por ejemplo, alguna que
descargue datos de un servidor externo, el cual puede tardar en enviar
la información). En este caso, la ejecución de la aplicación se quedará
bloqueada hasta que este método devuelva los datos y así se continúe
ejecutando el resto del código.

22
3.1. AsyncTask

El mecanismo que permite que las aplicaciones puedan hacer dos o


más operaciones al mismo tiempo son los hilos (threads, en inglés).
Crear y sincronizar dos hilos es una tarea relativamente compleja, y
por este motivo la librería de Android nos facilita la clase AsyncTask,
que se hace cargo de la mayor parte del trabajo por nosotros.

Lo que hace el AsyncTask es crear otro hilo que puede utilizar para
cualquier operación que prevéis que pueda tener una duración notable
y que, por tanto, pueda bloquear la interfaz de
usuario. Concretamente, cualquier operación de acceso a Internet
debería hacerse en un hilo separado obligatoriamente, y la manera más
sencilla es usar una AsyncTask.
Una tarea asíncrona se define como una tarea que se ejecuta en el hilo
de background (hilo BG, por background, es decir, que se ejecuta por
detrás) y los resultados de la que se muestran en el hilo de la interfaz
de usuario (lo llamaremos hilo UI, de User Interface).

Para crear una AsyncTask debe crear una nueva clase que derive de
AsyncTask, e implementar los métodos descritos a continuación. Lo
más importante es saber que algunos métodos se ejecutan al hilo UI y
otros al hilo que hace la operación en el background. Esto es
fundamental porque sólo se pueden modificar las vistas de la aplicación
(escribir valores, mostrar mensajes, hacer avanzar barras de progreso
o mostrar otro tipo de contenidos) desde el hilo UI.

Un AsyncTask se define utilizando tres tipos genéricos:


 Params: el tipo de los parámetros enviados a la tarea durante
la ejecución.
 Progress: el tipo de unidades de progreso publicadas durante la
computación en el background. Esto sirve por si desea mostrar

23
un progreso de la tarea realizada mientras ésta se ejecuta en
el background. Por ejemplo, podría animar una barra para
mostrar el progreso de la carga de un fichero.
 Result: el tipo del resultado de la computación en
la background.

No siempre se utilizan todos los tipos en una tarea asíncrona. Para


marcar un tipo que no se utilizará, simplemente utilice el tipo void. Por
ejemplo, la siguiente clase asíncrona define que tendrá un argumento
de tipo String y devolverá un Bitmap. No utilizará ningún tipo de
progreso de la tarea:

1 private class CargaImagen extends AsyncTask


<String, Void, Bitmap> {
2 ...
3 }

Cuando se ejecuta una tarea de forma asíncrona, ésta realiza cuatro


pasos:

 onPreExecute () (UI): ejecutado al UI inmediatamente después


de que la tarea es ejecutada. Permite inicializar los elementos de
la interfaz que sean necesarios para la tarea, tales como crear
una barra de progreso.
 doInBackground (Params ...) (BG): es llamada después de
que onPreExecute () acaba de ejecutarse. Pone en marcha la
operación que desea ejecutar en otro hilo en el background. Este
método recibe los parámetros necesarios para realizar la
operación asíncrona. El resultado de la ejecución se debe
devolver y se pasará de vuelta en el último paso. Dentro de
doInBackground () se puede llamar publishProgress (Progress
...) por publicar una o más unidades de resultado. Estos valores
se publican en el hilo UI, a onProgressUpdate (Progres ...).

24
 onProgressUpdate (Progress ...) (UI): es llamado en el hilo
UI después de haber llamado publishProgress (Progress ...). Le
permite actualizar algún elemento de la interfaz para mostrar el
progreso de la operación mientras la operación en
el background todavía está en ejecución, tales como actualizar
una animación o hacer avanzar una barra de progreso.
 onPostExecute (Result) (UI): este método es llamado cuando
la ejecución en el background finaliza. Recibe el resultado de la
operación que ha
ejecutado el AsyncTask
y como se ejecuta en el
hilo UI permite
visualizar los resultados
obtenidos (como una
imagen, una página web
o una lista de tweets).

Para utilizar AsyncTask necesita crear su clase, que herede de


AsyncTask e implemente el método de callback doInBackground (),
que se ejecuta en un hilo en el background. Para actualizar su interfaz
de usuario (o UI, user interface) debe implementar el método
onPostExecute () que incorpora los resultados de haber ejecutado
doInBackground () y se ejecuta en el hilo UI, y por tanto puede
actualizar el estado de su interfaz sin problemas. Cuando tenga la clase
creada, para lanzar el hilo de la AsyncTask debe elaborar un nuevo
objeto de la subclase que ha creado y llamar al método execute (),
pasándole la información necesaria para que pueda realizar la
operación.

25
Por ejemplo, la siguiente clase descarga una imagen de la red,
mediante el método propio Bitmap DescargaImagenDeLaRed (String
url), y usa el Bitmap para mostrarlo en un widget:

private class CargaImagen extends AsyncTask<String, Void, Bitmap> {


@Override
protected Bitmap doInBackground(String... param) {
Bitmap bitmap = null;
try {
// Descargamos la imagen desde una URL
InputStream input = new java.net.URL(param[0]).openStream();
// Decodificamos el Bitmap
bitmap = BitmapFactory.decodeStream(input);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}

protected void onPostExecute(Bitmap result) {


// Recibimos el resultado de doInBackground (el Bitmap)
// y lo usamos para cargar la imagen a la UI
ImageView iv = (ImageView) findViewById(R.id.miImagen);
iv.setImageBitmap(result);
}
}

3.2. Thread

Podemos utilizar los hilos como en Java, lo que debemos tener en


cuenta que desde los hilos que creemos no podemos acceder a la
interfaz de usuario, para solucionar esto, Android proporciona varias
alternativas, entre ellas la utilización del método post() para actuar
sobre cada control de la interfaz, o la llamada al
método runOnUiThread() para “enviar” operaciones al hilo principal
desde el hilo secundario

new Thread(new Runnable() {


public void run() {
pbarProgreso.post(new Runnable() {
public void run() {
pbarProgreso.setProgress(0);
}
});

for(int i=1; i<=10; i++) {


tareaLarga();
pbarProgreso.post(new Runnable() {
public void run() {
pbarProgreso.incrementProgressBy(10);

26
}
});
}

runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
});
}
}).start();

3.3. Handler

Un Handle permite enviar y procesar mensajes y objetos ejecutables


asociados con el MessageQueue de un hilo. Cada instancia del Handler
está asociada con un único hilo y la cola de mensajes de ese hilo.
Cuando crea un nuevo Handler, está vinculado a la fila / cola de
mensajes del subproceso que lo está creando. A partir de ese
momento, entregará los mensajes y los ejecutables a esa cola de
mensajes y los ejecutará a medida que salgan de la cola de mensajes.

Hay dos usos principales para un controlador: programar mensajes y


runnables para que se ejecuten como un punto en el futuro; y para
poner en cola una acción que se realizará en un hilo diferente al suyo.
Por ejemplo para ejecutar un código

Handler handler = new Handler();


handler.postDelayed(new Runnable() {
public void run() {
//TODO
}
}, milisegundos);

27
4. Conexiones HTTP

El protocolo HTTP (HyperText Transfer Protocol, protocolo de


transferencia de hipertext) es un protocolo estándar de Internet que
sirve para la transferencia de hipertexto. Es el protocolo base sabrir el
que está fundamentada la WWW (World Wide Web, 'red de extensión
mundial'). Utilizando HTTP a su aplicación podrá realizar diversas
tareas, como descargar información de texto y binaria (como páginas
web o imágenes, respectivamente).

Un hipertexto es un texto mostrado en un ordenador u otro dispositivo


electrónico con enlaces (hyperlinks) a otros textos, que son de acceso
inmediato por el lector a través de un clic del ratón o de la pantalla
táctil. Aparte de texto, puede contener tablas, imágenes y otros
formatos de representación de contenidos multimedia.

También se puede utilizar el protocolo HTTPS (HTTP Secure, HTTP


seguro) para comunicaciones seguras a través de una red de
ordenadores. HTTPS proporciona autentificación del sitio web y
encriptación bidireccional en las comunicaciones entre el cliente y el
servidor.

Android incluye dos clientes HTTP que puede utilizar a su


aplicación: HttpURLConnection y el cliente de Apache
HttpClient. Ambos soportan HTTPS, flujos de subida y descarga y
IPv6. Según artículos de la propia Google, el cliente HTTP de Apache
es más estable en las versiones de Android Eclair (versión 2.1) y Froyo
(versión 2.2). Pero para las versiones Gingerbread (versión 2.3) y
posteriores recomienda utilizar HttpURLConnection, en lo que enfocará
sus esfuerzos futuros.

28
Una conexión HttpURLConnection permite enviar y recibir datos por
la web. Los datos pueden ser de cualquier tipo y tamaño, incluso puede
enviar y recibir datos de las que no conoce el tamaño
previamente. Para usar esta clase debe seguir los siguientes pasos:

 Obtenga un objeto nuevo HttpURLConnection llamando el


método URL.openConnection() y forzando su resultado en
HttpURLConnection.
 Prepare la petición. La principal propiedad de la petición es su
URI. Las cabeceras de la petición pueden tener otros metadatos
como credenciales, tipo de contenido, cookies de sesión, etc.
 Si va a subir datos a un servidor, la instancia debe estar
configurada con setDoOutput(true). Debe transmitir datos
escribiendo al flujo de datos devuelto por getOutputStream().
 Lea la respuesta. Las cabeceras de la respuesta generalmente
incluyen metadatos como el tipo de datos de la respuesta y su
tamaño, la fecha de modificación o cookies de sesión, entre
otros. El cuerpo de la respuesta se puede leer del flujo devuelto
por getInputStream (). Si la respuesta no tiene cuerpo (Body en
inglés), el método devuelve un flujo vacío.
 Desconecte. Cuando haya leído la respuesta, debe cerrar el
HttpURLConnection con disconnect ().

Por ejemplo, con el siguiente código estaríais leyendo el contenido de


la web del diario el pais:

1 URL url = new URL ("http://www.elpais.es/");


2 HttpURLConnection urlConnection=
(HttpURLConnection) url.openConnection ();
3 try {
4 InputStream in = new buffered InputStream
(urlConnection.getInputStream ());

29
5 trabajarConElFlujoDeDatos (in);
6 finally {
7 urlConnection.disconnect ();
8 }
9}

5. Fuentes

 Introducció a la programació de dispositius mòbils


Eduard García Sacristan, Joan Climent Balaguer
Institut Obert de Catalunya
Licencia : Creative Commons Compartir Igual

 Developers Android
Licencia: Creative Commons Reconocimiento.

 CodePath
Licencia: cc-wiki with attribution required

30
Tema 4. Utilización de bibliotecas multimedia integradas
Aplicaciones multimedia Bibliotecas multimedia
Puede decirse que una aplicación multimedia es aquella que es capaz Para poder desarrollar aplicaciones multimedia, con cierta facilidad,
de procesar, reproducir, transmitir, almacenar, etc. contenidos resulta muy útil disponer de herramientas que nos ayuden en esa
multimedia. Como ya has visto en el caso de los mensajes labor ofreciendo una interfaz de programación de aplicaciones
multimedia para dispositivos móviles, un contenido intuitiva y sencilla de utilizar, así como una arquitectura modular
multimedia está compuesto por diversos medios entre los cuales que permita añadir soporte para nuevos códecs, formatos
destacan: contenedores y protocolos de transmisión.

• Texto, que puede estar sin formato o con formato (por Existen diversos frameworks o bibliotecas que proporcionan APIs

ejemplo mediante un lenguaje de marcas). El texto orientadas al trabajo con multimedia. Algunos ejemplos de

además puede ser esos frameworks o bibliotecas multimedia son:

ser lineal o bien hipertexto (con enlaces a otros textos).


• Directshow/Media Foundation. Conjunto
• Imágenes, formados por puntos o píxeles, cada uno de un
de APIs orientadas a multimedia desarrolladas por
color. Normalmente obtenidas mediante algún medio de
Microsoft. Incluye una biblioteca en tiempo de ejecución
digitalización (fotografía digital, escáner, etc.). Existen
con varias DLL que pueden ser distribuidas con las
múltiples formatos para representar imágenes.
aplicaciones. También proporcionan un kit de
• Audio, conteniendo voz, música o cualquier otro tipo de desarrollo. Directshow era
sonido registrado (obtenido mediante algún medio de el framework multimedia utilizado en versiones
grabación) o sintetizado (generado por ordenador). anteriores de Windows. Hoy día se utiliza Media
• Vídeo, sucesión de imágenes que son proyectadas a gran Foundation.
velocidad dando la sensación de movimiento. • Java Media Framework (JMF). Es el entorno de desarrollo
• Animaciones, similar al caso del vídeo pero con imágenes multimedia para Java desarrollado por Sun (hoy día
generadas por ordenador en lugar de con imágenes parte de Oracle). Se trata de otro conjunto de bibliotecas
captadas de la realidad. y APIs, en este caso para la plataforma Java, que
• Cualquier otro medio de representación de contenidos que proporciona herramientas para la captura, procesamiento,
vaya siendo inventado. almacenamiento, reproducción y transmisión de datos
multimedia.
En general, para cada tipo de medio no hay una única forma de
• Quitcktime. Desarrollado por Apple. Incorpora, además del
representar y almacenar la información, sino que existen diversos
conocido reproductor, un conjunto de bibliotecas que
tipos de formato, cada uno con sus características específicas
permiten reproducir y transmitir contenidos multimedia
de estructura, codificación, compresión, etc.
a través de Internet y otros dispositivos.
Además del propio formato en el que se representa la información, • Real Media. Desarrollado por RealNetworks. Consiste
otro aspecto que debe ser tenido en cuenta es que esencialmente en un formato contenedor capaz de
muchos contenidos multimedia están íntimamente relacionados almacenar información multimedia (vídeo, audio,
con el tiempo, en el sentido de que pueden ir cambiando conforme subtítulos, capítulos, meta-datos, información de
éste avanza (por ejemplo, en el caso de audio, vídeo o animaciones). sincronización, etc.) junto con la API necesaria para
Este tipo de restricciones da lugar a que esos contenidos tengan que manipularla.
ser generados o procesados dentro de unas condiciones muy
También es interesante saber que, además de las bibliotecas
estrictas de tiempo, para que el flujo de información sea el
multimedia que ofrecen APIs para el programador, existen las
apropiado, por ejemplo: en el caso de la reproducción de un
llamadas "herramientas de autor", que ofrecen interfaces sencillas
fragmento de audio o de vídeo.
y visuales que permiten desarrollar pequeñas aplicaciones
A cada uno de los medios de los que está compuesto un contenido multimedia sin necesidad de programación ni de profundos
multimedia se le suele llamar pista, cada una de las cuales tendrá conocimientos técnicos. Pueden resultar muy útiles para
un determinado formato que definirá cómo están estructurados los presentaciones o para desarrollo de prototipos. Entre las más
datos que contiene. conocidas se encuentran Macromedia Director, ToolBook
Instructor y Macromedia Authorware. Por último, también habría
Otra característica que las aplicaciones multimedia suelen
que hacer una referencia a la plataforma Adobe Flash, utilizada
incorporar es la interactividad, es decir, la posibilidad de interactuar
también de manera masiva para el desarrollo de pequeñas
con la aplicación para decidir y seleccionar la tarea que deseamos
aplicaciones multimedia.
realizar, rompiendo la estructura lineal de la información. De este
modo el usuario puede realizar una observación no secuencial de los
contenidos en función de las decisiones que vaya tomando durante
la ejecución de la aplicación.
Elección de una alternativa: Java Media Framework Resumiendo, JMF permite a los programadores realizar las
siguientes funciones:
(JMF)
Una de las opciones para escoger JMF es la que ya nos viene • Reproducir contenidos multimedia.
ofreciendo la plataforma Java a lo largo de este curso: la gran
• Capturar audio y vídeo mediante micrófonos y cámaras.
capacidad de portabilidad a cualquier plataforma que disponga de
• Realizar operaciones de streaming o flujo multimedia a
una máquina virtual de Java ("write once, run anywhere", es decir,
través de una red (Intranet o Internet).
"escriba el código una sola vez y ejecútelo en cualquier parte").
• Procesar contenidos multimedia (aplicación de filtros,
Por otro lado, la plataforma Java ofrece además soporte de conversiones, modificaciones, etc.).
capacidades multimedia a través de un conjunto de paquetes • Almacenar contenidos multimedia en archivos.
opcionales para el trabajo con gráficos, procesamiento de imágenes
y, especialmente, datos basados en el tiempo (time-based media). De este modo, podrás desarrollar con facilidad programas

Esos paquetes constituyen lo que se suele denominar las "Java en Java (aplicaciones de escritorio, applets, etc.) que presenten,

Media APIs", constituidas esencialmente por: capturen, manipulen y almacenen flujos de información basados en
el tiempo. Además de eso, la API también proporcionará a los
• Java 2D. Para el trabajo con imágenes en dos programadores avanzados y a los fabricantes de tecnología la
dimensiones, combinado con textos, imágenes y demás posibilidad de realizar procesamientos más específicos así como
elementos relacionados con la composición. incorporar nuevas funcionalidades, soportar nuevos tipos de
• Java 3D. Para el trabajo en tres dimensiones, contenidos y formatos, optimizar la gestión de los ya existentes, etc.
incorporando la posibilidad de trabajar con escenarios,
renderización, construcción de modelos, etc.
El modelo de JMF.
• Java Advanced Imaging. Para la manipulación de
imágenes.
• Java Binding for OpenGL. Proporciona una API que
implementa la especificación OpenGL, integrada con las
bibliotecas de interfaz gráfica de Java.
• Java Image I/O. Para trabajar con imágenes almacenadas
tanto en archivos locales como remotos a los cuales se
pueda acceder por red. Incorpora extensiones adicionales
para nuevos tipos de formatos y flujos de datos.
• Java Media Framework. Permite a las JMF imita el modelo de comportamiento de los dispositivos físicos
aplicaciones Java reproducir, procesar, manipular, utilizados en el mundo de la multimedia: dispositivos como
transmitir y almacenar audio, vídeo y otros tipos de datos reproductores de CD, DVD o BD con los que se graban, procesan y
basados en el tiempo. presentan datos basados en el tiempo (normalmente flujos de audio
o vídeo o ambos a la vez). La información que estos aparatos
En esta unidad nos centraremos en hacer un repaso general de las
muestran al usuario fue previamente capturada, registrada,
posibilidades que puede ofrecerte la API Java Media Framework.
procesada y almacenada en un estudio de grabación utilizando otros

Arquitectura de la API JMF dispositivos (micrófonos, cámaras, digitalizadores, mesas de


mezclas, etc.). Por ejemplo, en el caso de un reproductor de DVD, a
Java Media Framework (JMF) es una biblioteca que proporciona a
éste se le proporciona un flujo de datos multimedia mediante la
las aplicaciones Java (aplicaciones de escritorio y applets) la
inserción y lectura de un disco. El reproductor, al realizar la
capacidad de trabajar con diversos contenidos multimedia. Se trata
lectura, interpreta y procesa la información contenida en el disco
de un paquete opcional con el que se pueden reproducir, capturar,
para, a continuación, enviar las señales de salida apropiadas (audio
transmitir y transcodificar en tiempo real múltiples tipos de
y vídeo) a una pantalla y un conjunto de altavoces o unos auriculares
contenidos (audio, vídeo, animaciones, etc.) y en diversos formatos.
(dispositivos de salida), de manera que el usuario pueda observar un
Esta biblioteca amplía la plataforma Java SE y permite el desarrollo
resultado final (la proyección de una película).
de aplicaciones multimedia multiplataforma.
La API de JMF ha construido un modelo de funcionamiento similar
En el entorno de la multimedia existe una gran variedad
al ejemplo anterior: una fuente de datos encapsula un flujo de
de formatos, cada uno de los cuales tiene sus propios
contenido multimedia de una manera similar a como lo haría un
requerimientos tanto software como hardware. El objetivo
disco digital (por ejemplo un DVD, o una vieja cinta de un VCR) y un
de JMF es precisamente abstraer al programador de ese tipo de
reproductor apropiado proporcionará los mecanismos de control y
problemas proporcionando una API sencilla que permita trabajar
procesamiento similares a los que tiene un aparato reproductor
con esos contenidos de manera independiente al formato en el que
de DVD.
se encuentren empaquetados. Por otro lado, JMF también permite
la creación y utilización de extensiones o plug-ins, para que no sea Las fuentes de datos y los reproductores son partes
necesario adaptar las aplicaciones según vayan apareciendo nuevos fundamentales de la API de alto nivel de JMF para gestionar la
formatos y tipos de codificación. captura, presentación y procesamiento de información basada en el
tiempo. Por otro lado, JMF también proporciona una API de bajo protocolo RTP (Real-time Transport Protocol). Para
nivel que permite una fácil integración de componentes y el VOD se utiliza el protocolo MediaBase. En JMF dispones
extensiones para ampliar las posibilidades del sistema. Este modelo de dos tipos de fuentes de datos
de capas proporciona a los desarrolladores una API que permite "push": PushDataSource y PushBufferDataSource, que
incorporar con facilidad características multimedia a sus programas también utilizan un objeto de tipo Buffer como unidad de
a la vez que se mantiene un nivel de flexibilidad y ampliabilidad transferencia.
suficiente como para poder soportar las nuevas tecnologías
La clase DataSource es abstracta y se encuentra en el
multimedia que vayan apareciendo y que podrán ir siendo
paquete javax.media.protocol. Son clases descendientes
incorporadas.
suyas: PullBufferDataSource, PullDataSource, PushBufferDataSourc
Aquí tienes un esquema de la arquitectura de alto nivel de JMF: e, PushDataSource, que también son abstractas.

Un DataSource es identificado por un objeto de tipo MediaLocator o


bien con una URL. Un MediaLocator es similar a una URL y puede
ser construido a partir de ella, aunque su manejador de protocolo
no esté instalado en el sistema (cosa que no podría hacerse con
una URL).

Una fuente de datos o DataSource gestiona un conjunto de


objetos SourceStream, utilizándose un array de bytes como unidad
de transferencia. A continuación tienes un esquema de la jerarquía
Lo que vamos a hacer ahora es estudiar algunos de los conceptos y de clases utilizada para las fuentes de datos en JMF:
términos fundamentales que incorpora el modelo de JMF: fuente de
datos, dispositivo de
captura, reproductor, procesador, formatos, manager, etc.

Fuentes de datos.
Como ya has visto, una fuente de datos encapsula un flujo de
información de un tipo de contenido a semejanza de como lo hace
por ejemplo un CD de audio. En JMF, un objeto de tipo DataSource,
representa un medio de audio, vídeo o ambos a la vez.
Un DataSource puede ser un archivo o bien un flujo de entrada que
provenga de una conexión de red. Una vez que se determina su
ubicación (en el caso de un archivo) o su protocolo (en el caso de un
flujo transmitido por la red), el objeto encapsula tanto
la ubicación como el protocolo y el software necesario para recibir Formatos.
la información. Una vez creado, el objeto DataSource, éste puede Para representar formato de un contenido multimedia se utiliza la
alimentar a un reproductor o un procesador para que lo gestione clase Format, la cual no contiene información sobre parámetros de
independientemente de cuál haya sido el origen o fuente de esa codificación o de temporización. Simplemente proporciona el nombre
información (de eso se encarga el DataSource). del formato o codificación utilizado y el tipo de dato que requiere ese
formato.
Los datos se pueden obtener de diversas fuentes (archivos locales
o remotos, streaming a través de la red, etc.). Una fuente de La clase Format tiene a su vez dos subclases utilizadas para definir
datos se puede clasificar en función de cómo se inicie la formatos de audio y vídeo: AudioFormat y VideoFormat. En el caso
transferencia de información: del audio, se describen los atributos específicos para un formato de
audio: la tasa de transferencia (bit rate), el número de bits por
• Pull data Source, si la inicia el cliente. En este caso él es
muestra, o el número de canales. En el caso del vídeo, se proporciona
quien controla el flujo de datos que son obtenidos de la
la información necesaria para poder trabajar con los datos de vídeo.
fuente de datos "pull". Algunos ejemplos de protocolos para
Dada la amplia gama de posibles formatos de vídeo, existe un nuevo
este tipo de fuente de datos podrían
conjunto de subclases que heredan de VideoFormat:
ser HTTP y FILE. JMF proporciona dos tipos de fuentes de
datos pull: PullDataSource y PullBufferDataSource, que • IndexedColorFormat.
utilizan un objeto de tipo Buffer como unidad de • RGBFormat.
transferencia.
• YUVFormat.
• Push data Source, si la inicia el servidor. En este caso es
• JPEGFormat.
el servidor quien controla el flujo de datos. Entre las
• H261Format.
fuentes de tipo "push" puedes encontrar:
el broadcast multimedia, el multicast multimedia y
• H263Format.

el vídeo bajo demanda (video-on-demand o VOD). Para las


transmisiones de tipo broadcast se utiliza el
Aquí tienes un esquema de la jerarquía de clases utilizada para los Componentes para la interfaz de usuario.
formatos en JMF: Un objeto de tipo Control puede proporcionar acceso a un elemento
de la interfaz de usuario que ofrezca el control de su
comportamiento al usuario de la aplicación. Como ya verás más
adelante, puedes obtener el componente de interfaz de usuario de
un objeto a través del método getControlPanelComponent, el cual
devuelve un objeto de tipo Component de la API AWT, el cual podrías
usar en la interfaz gráfica de tu aplicación.

Del mismo modo que puedes obtener un componente gráfico de


control de un objeto multimedia, también puedes obtener un
componente que sea la representación visual de ese contenido
multimedia. Para ello podrás usar el método getVisualComponent.
Controles.
Esto lo usarás más adelante en elementos de
La interfaz Control proporciona un mecanismo para establecer y
tipo reproductor (objetos que implementen la interfaz Player) para
obtener los atributos de control de un objeto
mostrar contenidos de vídeo.
(método getControlComponent). También ofrece la posibilidad de
acceder los componentes de la interfaz de usuario que permite el Para entender esto mejor, imagínate un ejemplo en el que deseas
control de un objeto al usuario. Entre los objetos JMF que reproducir un contenido multimedia que consiste en un fragmento
proporcionan esta funcionalidad se encuentran los objetos de de vídeo:
tipo DataSink o Multiplexer con los que puedes leer los contenidos
de un DataSource y escribirlos en un archivo. • La obtención del componente de
control (getControlPanelComponent) te devolvería un
Cualquier objeto JMF que pretenda proporcionar acceso a sus objeto de tipo Component que podrías incrustar en
objetos de control (objetos Control) puede hacerlo mediante la algún componente gráfico de interfaz de usuario
implementación de la interfaz Controls, la cual especifica los (Frame, Panel, etc.) y que te proporcionaría controles en
métodos necesarios para obtener los objetos de forma de botón para iniciar la reproducción, pausarla, etc.
tipo Control asociados a un • La obtención del componente
objeto JMF (métodos getControl y getControls). visual (getVisualComponent) te devolvería otro
objeto Component que representaría al flujo de imágenes
Existe una gran variedad de subinterfaces de Control. Por ejemplo,
en movimiento (la reproducción del vídeo) y que también
la interfaz StreamWriteControl, cuya implementación
tendrías que incrustar en algún componente gráfico de la
(métodos getStreamSize y setStreamSizeLimit) hace que el
interfaz de la aplicación.
usuario pueda limitar el tamaño del flujo que se va a crear a partir
de objetos de tipo DataSink o Multiplexer. Esto será lo que tendrás que hacer cuando instancies un objeto de
tipo Player y quieras que el flujo multimedia con el que va a ser
A continuación puedes observar un esquema de todas las
alimentado a partir de un DataSource pueda ser visualizado y
subinterfaces de Control disponibles en JMF:
controlado.

Presentación y tratamiento de los datos.


Reproductores y procesadores.
En JMF, el proceso de presentación es modelado por la
interfaz Controller, la cual define el mecanismo de control de
aquellos objetos que controlan, presentan o capturan datos basados
en el tiempo. Define las fases por las que pasa un controlador y
proporciona un mecanismo para el controlar las transiciones entre
esas fases.

Un controlador (Controller) puede enviar diversos eventos para


notificar sus cambios de estado. Para recibir esa información de un
controlador (por ejemplo un objeto de tipo Player) tendrás que
implementar la interfaz ControllerListener, que ya verás más
adelante.

La API JMF proporciona dos tipos de


controladores: reproductores (interfaz Player)
y procesadores (interfaz Processor). Tanto un reproductor como
un procesador se construirán a partir de una fuente de datos. A
continuación puedes observar el esquema de la jerarquía casos la fuente de datos (objeto DataSource) contendrá
de controladores, en donde se encuentran los reproductores y varios SourceStream para representar a cada uno de esos flujos
los procesadores: como ya viste en el esquema de clases sobre fuentes de datos.

Almacenamiento y transmisión de datos. Objetos


DataSink.
Como ya has visto al estudiar los controles, puedes realizar
el almacenamiento de datos con la ayuda de la interfaz DataSink,
que es capaz de leer los contenidos multimedia proporcionados por
un DataSource y enviarlos a cualquier tipo de destino: un dispositivo
de salida, un archivo, algún dispositivo conectado a la red, realizar
una difusión o broadcast de ese contenido, etc.

Los DataSink pueden utilizar un StreamWriterControl para


Como ya has visto al describir el modelo de JMF, proporcionar un control adicional sobre cómo serán los datos
un reproductor (objeto que implementa la interfaz Player) toma un escritos en un archivo.
flujo de entrada de audio o vídeo (o ambos) y lo interpreta
Para poder seguir el estado de un DataSink, éste es capaz de
apropiadamente para presentarlo finalmente de una forma que
generar eventos de tipo DataSinkEvent, los cuales pueden ser
pueda ser percibida por el ser humano (por ejemplo en un altavoz o
recibidos si implementas la interfaz DataSinkListener.
en una pantalla o ambos a la vez), de manera similar a como un
reproductor de CD lee una pista de un CD y envía las señales de Los DataSink se construyen a partir de la clase Manager, del
audio a los altavoces. mismo modo que se hace con los reproductores y los procesadores,
como verás más adelante. La clase Manager es una clase final que
Un procesador (Processor) es un tipo especializado de reproductor y
proporciona abundantes métodos estáticos "factoría" para crear
por tanto una subinterfaz de Player. Un procesador, además de las
instancias de objetos DataSource, Player, Processor y DataSink. Ya
posibilidades que presenta un reproductor (enviar las señales al
irás viendo cómo utilizar algunos de ellos a lo largo de la unidad.
dispositivo de salida) puede también enviar su señal de salida hacia
un reproductor o bien hacia otro procesador para que las procese, Instalación de la biblioteca multimedia JMF
manipule, filtre, la convierta a otro formato, etc.
Indicaciones con los requerimientos hardware mínimos y unas
instrucciones básicas de instalación:
Dispositivos o fuentes de captura.
Un dispositivo o fuente de captura representa el hardware utilizado 1. Pulsar sobre el botón "Download" en la web de descarga
para obtener o capturar la información (micrófono, sensor, cámara del producto.
fotográfica, cámara de vídeo, etc.). La información capturada puede 2. Descargar el paquete de la biblioteca JMF que necesites
ser más tarde proporcionada a un reproductor o para tu sistema operativo. Ese paquete es un archivo
un procesador para que la reproduzca, procese (para, por ejemplo, ejecutable de Windows o bien un script autoextraíble
aplicarle un filtro de conversión) o almacene para su uso posterior. comprimido para Linux o para Solaris. En cualquier otro
caso, se tratará de un archivo comprimido zip.
En JMF los dispositivos de captura son representados por objetos
3. Si estás realizando la instalación para Windows, basta
del tipo DataSource, que ya has visto al estudiar las fuentes de
con ejecutar el archivo de instalación descargado.
datos. Por tanto, los dispositivos de captura pueden ser también
4. Si estás realizando la instalación para Linux, tendrás que
clasificados como de fuentes push (push source) o pull (pull source):
mover el archivo al directorio de instalación y ejecutarlo
• Pull Source, donde es el usuario quien controla cuándo se allí. Necesitarás permisos de superusuario.
captura la información (por ejemplo una imagen es
capturada al pulsar un botón y tomar una fotografía).
Datos basados en el tiempo
Con JMF vas a procesar información multimedia almacenada en
• Push Source, donde el usuario no tiene ese control (por
archivos, capturada por un dispositivo de entrada o recibida a través
ejemplo un micrófono al proporcionar de manera continua
de la red. En algunos casos puede que incluso tengas que enviar y
un flujo de información de audio al controlador).
recibir contenidos en directo tales como emisiones de radio y
Cualquier subclase de DataSource puede se utilizada para televisión, o teleconferencias en tiempo real a través de Internet o
representar un dispositivo de de una intranet. En cualquier caso se trata, como acabas de ver, de
captura: PushDataSource, PushBufferDataSource, PullDataSource, P información que va fluyendo, y por tanto cambiando, a lo largo del
ullBufferDataSource. Por ejemplo, un dispositivo que proporcione tiempo.
información cada cierto tiempo podría ser representado por un
Cualquier tipo de información que va cambiando según va
objeto PushDataSource.
transcurriendo el tiempo puede ser considerada como "datos
Es posible que algunos dispositivos proporcionen varios flujos de basados en el tiempo" (time-based media). Los clips de audio o
datos diferentes, por ejemplo: en una videoconferencia donde se van de vídeo, las secuencias MIDI y las animaciones son los tipos más
proporcionando un flujo de audio y otro de vídeo paralelos. En estos habituales de datos basados en el tiempo. Este tipo de información
puede ser obtenida a partir de fuentes diversas como por ejemplo si ese archivo se encuentra alojado en un servidor web, podría
archivos (locales o remotos), cámaras, micrófonos o recepción accederse a él mediante el protocolo HTTP. Un localizador o ubicador
de streaming de audio o vídeo (radio o televisión por Internet, multimedia (media locator) proporciona una forma de indicar la
películas, videoconferencias, llamadas telefónicas por Internet, etc.). ubicación de un flujo multimedia.

Aquí tienes un esquema del modelo de procesamiento de datos Los flujos multimedia se pueden clasificar en función de cómo la
basados en el tiempo con algunos de los ejemplos de los que hemos información es entregada de una manera similar a como hemos
estado hablando: clasificado las fuentes de datos y la captura:

• Pull, en los que la transferencia es iniciada y controlada


desde el cliente. Los protocolos HTTP y FILE son
protocolos pull. Recuerda que en inglés pull significa "tirar".
• Push, en los cuales es el servidor quien inicia la
transferencia y controla el flujo de datos. Por ejemplo el
protocolo RTP (Real-time Transport Protocol) es un
protocolo de tipo push utilizado para el streaming. El
protocolo SGI MediaBase utilizado para el vídeo bajo
demanda o VOD (video-on-demand) es también de
Flujo de datos multimedia (Streaming media) tipo push. Recuerda que en inglés push significa "empujar".
Una de las características fundamentales del trabajo con datos
basados en el tiempo es que tienen unas evidentes restricciones en
Tipos de contenidos y formatos multimedia más
cuanto al tiempo requerido para la entrega y el procesamiento de habituales
la información. Una vez que comienza el flujo de información, se El formato en el que la información multimedia es almacenada
fijan unas restricciones temporales que deben cumplirse para poder suele ser conocido como tipo de contenido (content type). Algunos
garantizar una apropiada recepción y presentación de esa ejemplos de tipos de contenidos multimedia podrían
información. Por esta razón, a los datos basados en el tiempo (time- ser: QuickTime, MPEG o WAV.
based media) también se les suele conocer con la expresión flujo
A la hora de elegir un formato es importante tener en cuenta
multimedia (streaming media en inglés), flujo de datos
características del formato así como el entorno final y las posibles
multimedia, flujo de contenidos multimedia, etc. Se trata de una
expectativas de los potenciales receptores. Por ejemplo, si los
transferencia de información que debe tener un flujo estable y
contenidos van a ser transferidos a través de la web, habrá que
regular para que el funcionamiento de las aplicaciones produzca
poner especial atención en los requerimientos de ancho de banda.
unos resultados aceptables en lo que a la percepción final del usuario
se refiere. En el siguiente diagrama tienes una tabla los formatos de
vídeo más comunes:
Por ejemplo, si se está reproduciendo una película y los datos no
llegan lo suficientemente rápido, se producirán retardos en esa
presentación. Por otro lado, si la información que llega no puede ser
recibida y procesada con la velocidad suficiente, la película dará
pequeños saltos, produciéndose pérdidas de cuadros de imagen para
intentar mantener el ritmo adecuado de reproducción.

Un flujo multimedia consiste en un conjunto de datos obtenidos a


partir de un archivo local, adquiridos a través de la red o capturados
por algún hardware (cámara, micrófono, etc.). Los flujos multimedia
suelen contener múltiples canales conocidos como pistas. Por
ejemplo, un archivo Quicktime puede contener una pista de audio y
otra de vídeo. Los archivos multimedia que contienen varias pistas
En este otro diagrama puedes observar los formatos de audio más
son conocidos como flujos de datos complejos o multiplexados. El
habituales:
proceso de extracción de cada pista individual es conocido
como demultiplexación.

El tipo de una pista identifica el tipo de datos que contiene, como


por ejemplo audio o vídeo. El formato de una pista define cómo
están estructurados los datos de la pista.

Un flujo multimedia puede ser identificado por su ubicación y


el protocolo utilizado para acceder a él. Por ejemplo, puede utilizarse
una URL para describir la ubicación de un archivo Quicktime que se
encuentre en un archivo local o remoto del sistema. Si el archivo es
local, se puede acceder a él a través del protocolo FILE. Por otro lado,
Algunos formatos se han diseñado para aplicaciones específicas con es capaz de reproducir contenidos. De hecho, la mayoría de los
unos requerimientos precisos en cuanto a necesidades de métodos de un Player sólo pueden ser utilizados cuando éste se
procesamiento, ancho de banda, etc. Por ejemplo, los formatos de encuentre en el estado Realized.
alta calidad y alto ancho de banda suelen tener como objetivo
Para garantizar que un reproductor se encuentra en
aplicaciones que van a reproducir contenidos que se encuentran
estado Realized, puedes utilizar el método createRealizedPlayer de
almacenados localmente (disco duro local, CD, DVD, etc.). H.261 y
la clase Manager (existen también tres versiones de este método
H.263 suelen ser utilizados para aplicaciones de videoconferencia y
dependiendo del tipo de fuente) a la hora de instanciar
están optimizados para el flujo de señales de vídeo en las que no
el reproductor. A través de ese método se creará un objeto Player y
hay mucho movimiento. De manera similar, G.723 se suele utilizar
se pasará a estado Realized automáticamente. Ahora bien, debes
para aplicaciones de telefonía donde la voz se transmite con una
tener en cuenta que este método es bloqueante, es decir, que el hilo
menor calidad para ocupar menor ancho de banda.
de ejecución se quedará detenido hasta que el objeto Player sea

Reproducción de contenidos multimedia. creado y esté en estado Realized.

En JMF, los responsables de presentar al usuario un flujo de datos Aquí tienes un ejemplo de creación de un Player utilizando el
multimedia basado en el tiempo (como por ejemplo audio o vídeo) a método createRealizedPlayer:
través del hardware disponible en el equipo son los reproductores.
Ya has visto que para llevar a cabo esta tarea es necesario un objeto
que implemente la interfaz Player. La reproducción de la
información podrá ser controlada directamente desde tu aplicación
o bien podrás ofrecer un componente visual que muestre un panel
de control con el que el usuario podrá interactuar. Si vas a trabajar
con varios flujos de datos, tendrás que crear un reproductor para
cada uno de ellos.

Una vez que hayas creado un objeto de tipo reproductor, podrás


obtener sus componentes visuales, los cuales podrás incrustar en
alguno de los componentes gráficos de la aplicación que estés
desarrollando.

Ahora bien, si un objeto Player es el que realiza la reproducción de


Estados de un reproductor.
un contenido, ¿cómo se representa ese contenido para alimentar a
Un objeto de tipo reproductor en JMF (objeto que implementa la
ese Player? Como ya has visto al estudiar los componentes
interfaz Player) puede pasar por una serie de estados. Cada uno de
principales de JMF, la clase DataSource es la utilizada para ofrecer
esos estados tendrá sus peculiaridades y permitirá la llamada a
una fuente de datos. Ésa será la entrada al reproductor. El destino
unos u otros métodos del Player:
final en el que se presente el contenido dependerá de la naturaleza
del propio contenido (audio, vídeo, animación, etc.).
• Unrealized. Estado en el que se encuentra

Como verás más adelante, un procesador (Processsor) es un tipo el reproductor inmediatamente después de ser creado.

especial de reproductor (Player) que permite aplicar determinadas Aún no tiene información acerca de la fuente de

operaciones de procesamiento a los datos antes de ser presentados. datos multimedia.


• Realizing. El reproductor se encuentra en proceso de
Creación de un reproductor determinación de los recursos que va a necesitar
La primera pregunta que puedes hacerte es: ¿cómo puedo yo crear (recuerda que "realize" en inglés significa "darse cuenta
un objeto que implemente la interfaz Player? Puedes hacerlo a de").
través del método estático createPlayer de la • Realized. Una vez que el reproductor finaliza su fase de
clase Manager (método "factoría"). Esta clase es la responsable de reconocimiento de recursos requeridos, pasa a este
proporcionar los recursos dependientes del sistema estado, en el cual ya conoce el tipo de fuente y sabe
(reproductores, fuentes de datos, etc.) y proporciona tres versiones qué recursos específicos del sistema va a necesitar para
sobrecargadas del método createPlayer: su reproducción. Dado que un Player en
estado Realized sabe cómo "renderizar" o presentar los
• createPlayer(DataSource source).
contenidos que va a reproducir, puede
• createPlayer(MediaLocator sourceLocator). proporcionar controles y componentes visuales para su
• createPlayer(java.net.URL sourceURL). manipulación.

La diferencia entre cada uno de esos métodos estriba en el tipo de


• Prefetching. Cuando el método prefetch es invocado,
el reproductor pasa del estado Realized a Prefetching,
fuente que recibe como parámetro: un DataSource,
durante el cual se preparará para la reproducción, leyendo
un MediaLocator o una URL.
datos de la fuente multimedia para tener información
Un reproductor instanciado mediante un método createPlayer se suficiente como para empezar, obteniendo aquellos
encontrará en el estado Unrealized. Un Player en ese estado aún no recursos de uso no exclusivo vaya a necesitar y llevando a
cabo todas la tareas necesarias para estar en condiciones Los métodos más habituales que podrás utilizar a la hora de
de reproducir. controlar la reproducción de un Player serán:
• Prefetched. Una vez finalizada la fase de Prefetching,
• Método play. Con este método podrás iniciar la
el reproductor se encuentra listo para reproducir el
reproducción del contenido multimedia.
contenido.
• Método stop (heredado de Clock), con el cual podrás
• Started. El usuario ha dado la orden de comienzo de la
detener la reproducción.
reproducción. Se ha llamado al método start.
• Método deallocate (heredado de Controller), con el que
podrás liberar el dispositivo de salida para que
otros Players puedan usarlo.
• Método close (heredado de Controller), para liberar todos
los recursos del Player y destruirlo (recuerda que no ha
sido creado con un constructor).

Interfaz gráfica de un reproductor

Principales métodos de un reproductor.


Entre los principales métodos de la interfaz Player puedes
encontrar:

Método Descripción
void start () Inicia el reproductor en cuanto sea posible
(en cuanto se encuentre en el estado Pre-
fetched).
Component getVisualCom- Obtiene el componente visual (objeto de
ponent () tipo javax.awt.Component) para mostrar
el reproductor.
Un Player dispone generalmente de dos tipos de componentes para
Component getControlPa- Obtiene el componente (objeto ja-
construir su interfaz de usuario:
nelComponent () vax.awt.Component) que proporciona el in-
terfaz de usuario por defecto para contro-
1. Componente visual. Permite reproducir el contenido
lar el reproductor.
multimedia del flujo que alimenta al Player (por ejemplo,
GainControl getGainControl Obtiene el objeto que permite controlar
si se trata de un vídeo, sería el marco en el cual se
() la regulación de sonido del reproductor.
representaría visualmente el vídeo). En algunos casos
podría no existir, por ejemplo: en el caso de un flujo de
Además de esos métodos, también dispones audio.
de addController y removeController, junto a todos los métodos 2. Componente de control. Panel de control que permite al
heredados de las usuario interactuar con el contenido multimedia: botones
interfaces Controller, Clock, MediaHandler y Duration. de reproducir, pausar, detener, etc.

Debes tener en cuenta que no puedes llamar a cualquier método Puedes obtener esos componentes mediante los métodos del
de Player desde cualquier estado. objeto Player que ya viste en el apartado anterior:

Aquí tienes un esquema de la jerarquía de interfaces que heredan • getVisualComponent.


de Controller junto con sus métodos. La interfaz Player tendrá • getControlPanelComponent.
todos esos métodos:
Para poder visualizar ambos componentes tendrás que añadirlos
a la interfaz gráfica de tu aplicación. Por ejemplo:

Algunas implementaciones de reproductores pueden incorporar


también componentes adicionales como por ejemplo el control de
volumen o barras de progreso de descarga. Por ejemplo,
los Players que soportan control de volumen implementan el
interfaz GainControl, el cual proporciona los métodos setLevel o
setMute. Para trabajar con esta interfaz habría que proceder de
manera análoga:

1. Obtener el control mediante el método apropiado, en este


caso getGainControl. Si se obtiene el valor null es que el
reproductor no soporta esa interfaz.
2. Llamar al
método getControlGainComponent del GainControl que
acabas de obtener.
3. Añadir ese componente de control recién obtenido a la
interfaz gráfica de tu aplicación.

En general, dependiendo del tipo de reproductor, podrás tener acceso


a otras propiedades con las que el usuario podrá interactuar. Por
ejemplo, un reproductor de vídeo podría permitir el ajuste de brillo y
contraste, algo que un reproductor "genérico" no va a incorporar.
Para averiguar de qué controles específicos dispone un reproductor,
puedes utilizar el método getControls. Por ejemplo:

Gestión de eventos multimedia.


Durante la reproducción de un contenido multimedia pueden
producirse diversas incidencias que hagan necesario que la aplicación El método controllerUpdate recibe como único parámetro un objeto
tenga que tomar unas u otras decisiones. Como ya has visto en de tipo ControllerEvent, que es la clase genérica que se utiliza para
otras unidades, el sistema informa a una aplicación de la ocurrencia representar los eventos generados por un controlador (objetos de
de un determinado suceso a través de los eventos. tipo Controller). Los eventos originados por un objeto de
tipo Controller (un Player o reproductor, o
Los eventos que pueden generarse durante una reproducción un Processor o procesador) se pueden clasificar en tres categorías:
multimedia pueden aparecer en cualquier momento, de manera que
tu aplicación debe estar lista para poder atenderlos de manera • Notificaciones de cambios (por
asíncrona. Las herramientas que proporciona la API JMF para el ejemplo RateChangeEvent o DurationUpdateEvent).
control de eventos son similares a las que ya has utilizado Indican que algún atributo del controlador ha cambiado,
anteriormente en otras aplicaciones Java: mediante la creación habitualmente como respuesta a la invocación de un
de oyentes o listeners que implementan una interfaz cuyos método.
métodos son llamados cuando se produzca un determinado evento. • Eventos de transición (TransitionEvent). Permiten a la
aplicación responder ante cambios de estado del
En este caso se trata de la interfaz ControllerListener, para la cual
controlador. Habrá por tanto un buen número de eventos
habrá que implementar el método controllerUpdate. Todo objeto que
relacionados con todas las transiciones posibles de
quieras que sea informado de la generación de un evento
estados.
multimedia producido por un objeto de tipo Player tendrá que
implementar esa interfaz. Por supuesto tendrás también que
• Eventos de cierre (ControllerCloseEvent). Generados
cuando el controlador es cerrado. Podrán generarse uno u
registrar ese objeto como oyente de ese Player. Por ejemplo:
otro tipo de evento de cierre en función de la razón por la
que el controlador ha sido cerrado (por pérdida de datos,
por errores, por la no disponibilidad de algún recurso, etc.).
Su uso apropiado puede ayudar a minimizar el alcance de
posibles errores generados durante la reproducción o el
Dado que el objeto panel debe haber implementado la procesamiento de los contenidos.
interfaz ControllerListener, cuando se produzca algún evento
relacionado con el objeto reproductor, se producirá una llamada al En la figura anterior puedes observar un diagrama de clases en el

método controllerUpdate del objeto panel, pues ha sido registrado que se detallan todos los eventos posibles.

como oyente. En ese método podrás decidir lo que hacer en caso de


que se produzcan unos u otros eventos (gestión del evento).
Aplicación práctica: ejemplos de reproductores introducción a la API JMF y se crea la

Una vez conocida la estructura general de funcionamiento de un clase paneldevideo.java, que será encargada de manejar

objeto Player, vamos a intentar desarrollar un pequeño ejemplo de la API así como de interactuar con la interfaz. Por el

aplicación con un reproductor completo. momento esta clase solo se encargará de crear un
objeto MediaPlayer especificando la URL de un archivo de
video, y su reproducción será automática. Los controles de
reproducción se dejaran para más tarde.

• Los controles de reproducción. En este tercer videotutorial


se introducen elementos de control de la reproducción
como PLAY, STOP o MUTE, así como la regulación de
volumen. Se implantarán estos objetos de control con
ayuda de las herramientas gráficas de Netbeans.

Reproductor de vídeo avanzado (II).


En un apartado anterior has creado un Player que era capaz de Puedes continuar añadiendo elementos a tu reproductor de prueba
reproducir un archivo de audio. En aquel caso no tuviste que indicar para darle un aspecto más profesional:
el tipo o el formato del contenido que iba a ser reproducido. Una de
las grandes ventajas de JMF es que no es necesario conocer a priori
• Uso de la lista de reproducción.

qué tipo de contenido multimedia va a ser utilizado para alimentar


• código de la clase playlistclass.java, encargada de la
el reproductor, de manera que no tendrás que encargarte de
gestión de una lista de reproducción utilizando vectores.
configurarlo. Ése es un trabajo que se realizará internamente al
Puedes descargar el proyecto completo, incluyendo la
cargar el contenido y detectar de qué tipo es.
gestión de listas de reproducción y algunos vídeos de

La clase que vas a desarrollar recibirá un archivo (un objeto File) que prueba, desde el siguiente enlace.
debe almacenar un contenido multimedia y a continuación creará
• Inclusión de una barra de progreso. En este quinto y
una pequeña interfaz gráfica de usuario con unos componentes
último videotutorial se va a incluir una barra de
mínimos que te permitan trabajar con ese contenido. Esos
progreso de la reproducción añadiendo un par de nuevos
componentes los obtendrás a partir de un objeto Player. Al tratarse
métodos a la
de una aplicación gráfica, necesitarás incluir algunas clases de
clase paneldevideo: startAnimation y stopAnimation.
la API de la interfaces gráficas de
usuario Swing y AWT (JFrame, JPanel, GridbagLayout, etc.). Reproductor MP3

Nuestro reproductor multimedia será definido como


una ventana diferenciada en el escritorio. Cualquier clase cliente que
cree una instancia de nuestra clase podrá visualizar el reproductor
a través del método show heredado de la clase JFrame. En cualquier
caso, la clase que vamos a desarrollar podrá funcionar de manera
autónoma al disponer de su propio método main, el cual obtendrá Procesamiento de contenidos multimedia
el nombre de un archivo a través de la línea de órdenes de la consola.
Un procesador (Processor) es un tipo especializado de reproductor y
Es decir, que es suficiente con esa clase Java para poder crear una
por tanto una subinterfaz de Player. Recibe a su entrada una fuente
aplicación completa.
de datos multimedia (DataSource), realiza un conjunto

Reproductor de vídeo avanzado (I) de operaciones de procesamiento sobre esos datos (manipulaciones,
filtrados, conversiones, etc.) y proporciona una salida de ese
En la siguiente colección de videotutoriales puedes observar cómo
contenido multimedia procesado. Esa salida podría ser finalmente
desarrollar un reproductor de vídeo, algo más sofisticado, que
enviada a un reproductor o bien a otro procesador que realizará un
incluye más controles:
nuevo conjunto de operaciones sobre la información. En cualquier
• Explicación de cómo realizar el reproductor. En este primer caso, la salida de un procesador volverá a ser una nueva fuente de
tutorial (sin vídeo) se da una idea general de los datos.
componentes que va a ser necesario que construyas. Se
incluye el código fuente en java.

• Construcción de la interfaz. En este primer videotutorial


no se llega a utilizar todavía JMF. Lo primero que se hace
es crear y el proyecto y construir la interfaz gráfica de la
aplicación (uso de Swing y AWT). Además de los seis estados en los que se puede encontrar
un reproductor, un procesador cuenta con otros dos estados
• Utilización de la API JMF para el reproductor de vídeo. En posibles:
este segundo videotutorial se hace una pequeña
• Configuring.
• Configured.

Un procesador puede ser creado de manera similar a como se crea


un Player: mediante el uso de alguna de las versiones del método
estático createProcessor de la clase Manager.

También puede crearse un procesador mediante el uso de


un ProcessorModel, que define los requerimientos de entrada y de
salida para el procesador que se desea crear, de manera que
el Manager intenta ajustarse a esos requerimientos a la hora de
crear el procesador. En tal caso tendrás que utilizar el
método createRealizedProcessor de la clase Manager.
Funcionamiento de un procesador.
Métodos y estados de un procesador. Un procesador puede enviar sus datos de salida a un dispositivo de
Como ya has visto, un procesador incorpora dos presentación final (como haría un Player) o bien a una nueva fuente
nuevos estados, Configuring y Configured, que tienen lugar antes de de datos (DataSource). Si la información es enviada a
entrar en el estado Realizing: un DataSource, éste podrá ser nuevamente utilizado como entrada
para otro Player o Processor, o bien como entrada para
• Un procesador pasa al estado Configuring cuando se
un DataSink (para, por ejemplo, almacenar en disco la información
produce una llamada al método configure. Mientras
generada).
el procesador se encuentre en ese estado, se conecta a
la fuente de datos (DataSource), demultiplexa el flujo de Mientras que el procesamiento que lleva a cabo un Player es un
entrada y accede a la información relativa al formato. comportamiento que está predefinido en la implementación de la
• El procesador pasará al estado Configured cuando esté biblioteca JMF, un Processor permite al desarrollador de la
conectado a la fuente de datos y haya determinado aplicación definir qué tipo de procesamiento se desea aplicar a los
el formato de la información que almacena el contenido datos de entrada. De esta manera podrás incluir en tus aplicaciones
multimedia. Cuando esto suceda, se generará un evento todo tipo efectos, mezclas, filtros, composiciones, etc. los cuales
de tipo ConfigureCompleteEvent. podrán ser aplicados sobre los contenidos multimedia de entrada.
• Cuando se produzca una llamada al método realize,
el procesador pasará al estado Realized. Una vez llegados
a este estado, puede decirse que el procesador se
encuentra preparado para empezar a trabajar con él.

Mientras un procesador se encuentre en el estado Configured, se


puede llamar al método getTrackControls para obtener
objetos TrackControl que representan a cada pista del flujo
multimedia (por ejemplo para el audio y el vídeo). Estos objetos de
tipo TrackControl te permitirán especificar las operaciones de
procesamiento que deseas que el procesador lleve a cabo.
El desarrollo de un procesamiento puede dividirse en varias fases:
Sin embargo, si llamas directamente al método Realize desde el
• Demultiplexación. Es el análisis del flujo de entrada que se
estado Unrealized, se pasará automáticamente al estado Realized,
lleva a cabo para separar cada una de las pistas que
de manera que no podrías configurar las opciones de procesamiento
contiene ese flujo (por ejemplo la separación de pistas de
de los TrackControls y se utilizarán las opciones por omisión.
audio y de vídeo). Esta operación se realiza de manera
automática si la entrada contiene datos multiplexados.
• Preprocesamiento (o "preprocessing") Consiste en el
proceso de aplicación de algoritmos (efectos, filtros, etc.) a
las pistas que se han extraído en la fase anterior.
• Transcodificación. En este paso se realiza la conversión de
cada pista de datos de un formato original de entrada a
otro formato. Cuando un flujo de datos es convertido de
un formato que tiene compresión a otro que no está
Del mismo modo que sucede con un Player, tampoco puedes llamar comprimido se suele hablar de decodificación (o
a cualquier método de Processor desde cualquier estado. "decoding"). El proceso inverso se conoce
como codificación (o "encoding"). A los algoritmos que
Aquí tienes un diagrama de las clases Processor y Player realizan estas conversiones se les suele llamar códecs (de
"codificador-decodificador").
• Postprocesamiento. Aplicación de algoritmos a las pistas seleccionar los plug-ins de efectos (Effect) o códec (Codec)
ya decodificadas. que serán utilizados por el procesador.

• Multiplexación. Es el proceso en el que se vuelven a • Utilizando el método setRenderer de los


entrelazar las pistas de los contenidos multimedia en un objetos TrackControl que representan a las pistas para
único flujo de salida. seleccionar el plug-ins de presentación (Renderer)

• Presentación (o "rendering" o "renderización"). En esta


Configuración y opciones de procesamiento
última fase se presenta la información final al usuario.
Como ya has visto al estudiar los estados de un procesador, existen
Cada una de esas etapas de procesamiento es realizada por un sendos estados Configuring y Configured por los que se pasa antes
componente diferente. Estos componentes de procesamiento son de entrar en la fase Unrealized, donde el diagrama de estados vuelve
conocidos como plug-ins JMF. Si el procesador permite la utilización a coincidir con el de un Player.
de TrackControls, puedes decidir qué tipo de plug-in quieres utilizar
Mientras un procesador se encuentra en el estado Configuring, se
para cada pista.
dedica a obtener toda la información que necesita para construir los
Hay cinco tipos de plug-ins en JMF: objetos TrackControl de cada una de las pistas de la fuente de
datos. Una vez finalizadas estas operaciones, pasará al
• Demultiplexores. Analiza fuentes multimedia estado Configured y generará un evento ConfigureCompleteEvent.
(como WAV, MPEG, QuitckTime, etc.) y si la fuente está Una vez que el procesador se encuentre "configurado"
multiplexada, extrae separadamente cada una de las (estado Configured), puedes establecer cuál será su formato de
pistas. salida y acceder a cada una de sus pistas (objetos TrackControl)
• Efectos. Aplican determinados efectos o filtros de para configurar las opciones de procesamiento. Una vez establecidas
procesamiento sobre una pista. todas esas opciones, podrás llamar al método realize para que
• Códecs. Llevan a cabo la codificación y la decodificación. el procesador pase al estado Realizing y tras éste a Realized.
• Multiplexores. Combinan varias pistas de entrada en un
Una vez que el procesador se encuentre en estado Realized, lo
único flujo de salida.
normal es que no puedas modificar las opciones de procesamiento
• Presentadores o Renderers (o "renderizadores"). Procesa
y que si lo intentas obtengas una excepción del
el contenido almacenado en cada pista y lo envía al
tipo FormatChangedException.
destino apropiado para que pueda ser presentado al
usuario (pantallas, altavoces, etc.). Para seleccionar los plug-ins que quieres utilizar para procesar
cada pista debes seguir los siguientes pasos:
Utilización de un procesador.
Podemos enfocar el uso de un procesador desde dos puntos de vista: 1. Llamar al método estático getPlugInList de la
clase PlugInManager para consultar cuáles son los plug-
1. Un procesador puede ser utilizado como un reproductor ins disponibles. Obtendrás una lista de plug-ins que
programable que permite controlar el proceso coincidan con los formatos de entrada y de salida.
de descodificación y presentación de una fuente de datos. 2. Llamar al método getTrackControls del
2. En el sentido contrario, un procesador también podría ser objeto Processor para obtener un TrackControl para cada
utilizado como un elemento que permite controlar pista del contenido multimedia. Recuerda que
la codificación y multiplexación de un contenido el procesador debe estar en el estado Configured para
multimedia capturado a partir de una cámara, micrófono, poder llamar a ese método.
etc. 3. Utilizar los métodos setCodecChain o setRenderer de
cada objeto TrackControl para indicar los plug-ins que
Puedes controlar el tipo de procesamiento que se va a llevar a cabo
deseas utilizar en el procesamiento de cada pista.
de varias formas:
Cuando utilices el método setCodecChain para especificar los plug-
• Utilizando un ProcessorModel para construir
ins de códec y efectos en un procesador, debes tener en cuenta que
un procesador (Processor) que tenga unas determinadas
el orden en el que se irán aplicando los plug-ins en la cadena de
características de entrada y de salida.
procesamiento será determinado por los formatos de entrada y de
• Mediante el uso del método setFormat de los salida que soporta cada plug-in.
objetos TrackControl que representan a las pistas y así
especificar las conversiones de formato que quieres Para controlar la transcodificación que se va a llevar a cabo en una
realizar en cada pista. pista concreta por un determinado códec, puedes utilizar los

• Empleando el controles de códec asociados a la pista. Puedes obtener esos

método setOutputContentDescriptor del procesador para controles mediante el método getControls del objeto TrackControl.

especificar el formato de los datos que serán Ese método devuelve todos los controles (objetos Control) asociados

multiplexados a la salida. a una pista determinada, incluyendo controles de códec, como por
ejemplo H263Control, QualityControl y MPEGAudioControl.
• Usando el método setCodecChain de los
objetos TrackControl que representan a las pistas para
Para más información puedes revisar la lista de todos los controles 2. Crear un DataSink para escribir en un archivo mediante
de códec definidos en JMF en el subapartado "Controles" del el método estático createDataSink de la clase Manager.
apartado sobre arquitectura JMF. Habrá que pasarle el DataSource de salida y
un MediaLocator que indique la ubicación del archivo sobre
Conversión de formatos el que se va a escribir.
Puedes seleccionar el formato de una pista concreta mediante la 3. Usar el método open del DataSink para abrir el archivo.
manipulación del objeto TrackControl que representa a esa pista: 4. Iniciar la escritura de datos en el DataSink mediante el
método start.
1. Llamando al
método getTrackControls del procesador para obtener Aquí tienes un ejemplo de cómo podrías usar un DataSink para
el TrackControl de cada pista. Recuerda que volcar los datos de salida de un procesador:
el procesador debe estar aún en estado Configured.
2. Utilizando el método setFormat del procesador para
indicar el formato al cual quieres convertir esa pista.

Puedes utilizar el método setContentDescriptor de


un procesador para especificar el formato de los datos de salida.
Para obtener una lista de los formatos soportados dispones del
método getSupportedContentDescriptors.

Otra forma de establecer el formato de salida podría ser también


mediante el uso de un ProcessorModel a la hora de crear Si lo que deseas es interconectar la salida del procesador con la
el procesador. entrada de un reproductor u otro procesador, entonces lo que puedes
hacer es:
La especificación de un formato de datos de salida hace que se
seleccionen de manera automática las opciones de 1. Utilizar el método getDataOutput para obtener
procesamiento para ese formato pasando por encima de las el DataSource de salida del procesador.
opciones que se hayan podido previamente escoger en el 2. Utilizar ese DastaSource como entrada para construir un
tratamiento de las pistas. Si pones el formato de salida a null, nuevo procesador o un reproductor.
harías que el contenido fuera presentado (rendered) en lugar de
enviarlo a un objeto DataSource de salida. Transmisión multimedia en tiempo real
El envío y la recepción de contenidos multimedia en vivo y la gestión
Destino de los datos procesados.
de videoconferencias a través de Internet o de una Intranet
Puedes especificar el destino de un flujo de datos multimedia de
requieren la capacidad transmitir y recibir flujos de datos
varias formas:
multimedia en tiempo real.

• Mediante la selección de un Renderer específico para una Cuando un flujo de información es transmitido en tiempo real a un
pista a través del objeto TrackControl que representa a cliente, éste puede comenzar la reproducción de esa información sin
esa pista. necesidad de esperar a que la transferencia de información finalice.
• Mediante la utilización de la salida de un procesador como De hecho, es posible que la transferencia de información no tenga
entrada para un determinado DataSink. una duración o una longitud predeterminada, haciendo que la espera
• Mediante la redirección de la salida del procesador a la a que el flujo de datos finalice no tenga sentido. El término
entrada de un reproductor o de otro procesador. "streaming media", "streaming multimedia" o a veces simplemente
"streaming" (en español flujo de datos multimedia) suele utilizarse
Para seleccionar un Renderer determinado debes seguir los
tanto para las técnicas empleadas para el envío de información por
siguientes pasos:
la red en tiempo real como para los propios contenidos que son

1. Obtener los TrackControl de cada pista del contenido transferidos.

multimedia mediante el uso del


Hoy día puedes encontrar ejemplos de streaming multimedia en
método getTrackControls del procesador (recuerda que
cualquier parte de la web: radio y televisión en vivo, transmisiones
debe estar en estado Configured).
de conciertos, videoconferencias y muchos otros tipos de eventos
2. Utilizar el método setRenderer para establecer el plugin
celebrados en tiempo real.
de presentación elegido.
Protocolos de streaming.
Para volcar en un archivo el contenido multimedia generado por
Una de las características que diferencian el transporte de datos
un procesador, puedes utilizar un objeto DataSink que leerá los
multimedia en tiempo real del acceso a información estática es que
datos contenidos en el DataSource de salida del procesador y los
los protocolos de tiempo real no garantizan la llegada de todos los
preparará para ser almacenados. Para ello tendrías que seguir los
paquetes que hayan sido enviados. Cuando se trabaja con flujos de
siguientes pasos:
datos (streams) en tiempo real hay que reproducir la información
1. Obtener el DataSource de salida del procesador mediante independientemente de que hayan llegado todos los paquetes o se
el método getDataOutput. hayan perdido algunos por el camino. Sería más contraproducente
intentar recuperar algún paquete perdido que simplemente asumir
las posibles pérdidas y continuar con el flujo de información (tan
El protocolo RTP permite:
solo se notaría un pequeño corte en el audio o en el vídeo, a veces
imperceptible). Obviamente, la transmisión de contenidos
• Identificar el tipo de información que es transmitida.
multimedia a través de Internet (o cualquier otra red) requiere un
• Determinar el orden en el que los paquetes deberían ser
ancho de banda lo suficientemente amplio como para que el flujo de
presentados.
datos recibido pueda se reproducido de una manera continua y sin
• Sincronizar flujos que provengan de fuentes diferentes.
cortes.
No se garantiza que los paquetes de datos lleguen en el mismo
Por todo lo anterior, puede deducirse que los protocolos que se
orden en que fueron enviados, de hecho ni siquiera se garantiza que
utilizan habitualmente la transmisión de datos estáticos no son los
lleguen. Es responsabilidad del receptor la reconstrucción de los
más adecuados para el streaming. Protocolos
paquetes en el orden apropiado, así como la detección de paquetes
como HTTP y FTP están basados en el protocolo de transporte TCP,
perdidos. Esto podrías hacerlo gracias a la información
que fue diseñado para garantizar la fiabilidad de las comunicaciones
proporcionada por la cabecera del paquete.
en redes con anchos de banda reducidos y alta tasa de probabilidad
de errores. Con este diseño es posible comprobar cuando un paquete Dado que RTP no proporciona ningún mecanismo para garantizar la
se pierde o llega en mal estado y por tanto puede ser retransmitido. entrega u otras características relacionadas con la calidad del
De este modo se tiene la certeza de que las transferencias de servicio, dispones de un protocolo de control (RTCP) que te ofrece la
información son totalmente fiables. El precio que hay que pagar es posibilidad de monitorizar la calidad y efectividad con la que se están
una ralentización en este tipo de comunicaciones debido a la realizando las transferencias, así como un mecanismo de control e
complejidad y la sobrecarga exigida por este tipo de protocolos identificación de las transmisiones RTP. Por otro lado, si los
(mayor cantidad de información de control, envío de paquetes de requerimientos de calidad del servicio son muy altos para alguna
confirmación, control de paquetes perdidos, reenvíos, etc.). aplicación en particular, siempre tienes la opción de
utilizar RTP sobre un protocolo de transporte orientado a conexión,
Para streaming multmedia se ha decidido optar por otros
que será mucho más fiable en ese sentido (por ejemplo TCP).
protocolos que intenten aligerar al máximo la velocidad en las
comunicaciones aunque no proporcionen las garantías que
Arquitectura RTP en la API JMF.
ofrece TCP. El más utilizado para ello es el protocolo UDP, que no
garantiza que los paquetes lleguen a su destino (aunque lo habitual
es que así suceda), ni que lleguen en el mismo orden en el que fueron
enviados. Será el receptor, en la capa de aplicación quien deberá
controlar las pérdidas de datos, los paquetes duplicados y los que
llegan fuera del orden en el que fueron enviados.

Al igual que TCP, UDP es un protocolo de la capa de transporte sobre


el que se especifican los protocolos específicos de cada aplicación. El
protocolo estándar para la transmisión de datos en tiempo real en JMF permite la reproducción y transmisión de flujos RTP mediante
Internet es el RTP ("Real Time Protocol") o "Protocolo de Tiempo Real". la utilización de la API definida en los paquetes:
El protocolo RTP está descrito en el RFC 1889 de la IETF . Lo habitual
• javax.media.rtp.
es que este protocolo vaya encapsulado en la capa de transporte en
datagramas del protocolo UDP. • java.media.rtp.event.
• javax.media.rtp.rtcp.
Protocolo de transmisión en tiempo real RTP
Además de eso, también puedes extender JMF para que soporte
El protocolo RTP proporciona un servicio de transmisión de datos
otros formatos RTP adicionales a través del mecanismo de los plug-
punto a punto en tiempo real. Se trata de un protocolo de la capa
ins, que ya has visto anteriormente.
de aplicación y por tanto independiente de las capas de transporte
y red, aunque lo habitual es que a nivel de transporte vaya sobre Las aplicaciones RTP pueden clasificarse en:
datagramas UDP. Puede ser utilizado tanto en
servicios unicast como multicast. En la siguiente ilustración puedes • Clientes RTP, que reciben la información a través de la red.
observar un esquema de la arquitectura RTP: • Servidores RTP, que la envían.

Aunque en algunos casos, una aplicación puede ser cliente y


servidora a la vez, como por ejemplo en las aplicaciones de
videoconferencia.

Algunos ejemplos de aplicaciones que reciben flujos RTP podrían ser:

• Aplicaciones de videoconferencia (al recibir información).


• Aplicaciones que utilizadas como contestadores
telefónicos automáticos.
• Aplicaciones que registran conversaciones de voz. Cuando se inicia una nueva actividad, que se inserta en la pila de
nuevo y se lleva la atención al usuario. La pila de nuevo permanece
Algunos ejemplos de aplicaciones que envían información podrían en el "último en entrar, primero en salir" mecanismo de pila básica,
ser: por lo que, cuando el usuario se realiza con la actividad actual y
presiona el botón Atrás, que se extrae de la pila (y destruido) y
• Aplicaciones de videoconferencia (al enviar información).
reanuda la actividad anterior. (La pila de nuevo se discute más en
• Servidores de radio y televisión por Internet.
las tareas y documentos Volver Pila).
• Aplicaciones de seguridad y vigilancia que envían las
señales captadas por cámaras. Cuando se detiene una actividad debido a una nueva actividad se
inicia, se comunica el cambio en el estado del ciclo de vida a través
Extensión de la funcionalidad de JMF de métodos de devolución de llamada de la actividad.
Puedes extender las posibilidades de la API JMF mediante:
Existen varios métodos de devolución de llamada que podrían recibir

• La implementación de interfaces de plug-in que te una actividad, debido a un cambio en su estado si el sistema es la

permitan llevar a cabo procesamientos personalizados o creación de ella, deteniéndola, reanudarla, o destruirlo, y cada uno de

a medida sobre una pista. devolución de llamada que ofrece la oportunidad de realizar un
trabajo específico que es apropiado que el cambio de estado.
• La implementación completa de nuevos tipos
de DataSources y MediaHandlers. Por ejemplo, cuando se detuvo, su actividad debe liberar todos los
objetos grandes, tales como bases de datos de red o conexiones.
Una vez que implementes un nuevo plug-in, tendrás que instalarlo
Cuando se reanuda la actividad, se puede volver a adquirir los
y registrarlo con el PluginManager o gestor de plug-ins para que
recursos necesarios y reanudar las acciones que fueron
esté disponible para los procesadores.
interrumpidos. Estas transiciones de estado son parte del ciclo de
Una API multimedia para dispositivos móviles vida de la actividad que veremos a continuación.

Con Java ME en total desuso, nos queda como plataformas potentes


Crear Actividades en Android (I).
Android e iOS, tanto en su versión móvil como Tablet en las cuales
Toda actividad ha de tener una vista asociada, que será utilizada
podemos usar las APIs por defecto que nos traen. Existen también
como interfaz de usuario. Esta vista suele ser de
numerosas librerías que facilitan el desarrollo para determinados
tipo Layout aunque no es imprescindible, como se verá en el
contenidos como MediaFacer Library.
siguiente ejemplo.

Android. Actividades en Android. Una aplicación estará formada por un conjunto de actividades
independientes, es decir se trata de clases independientes que no
comparten variables, aunque todas trabajan para un objetivo
común. Otro aspecto importante es que toda actividad ha de ser
una subclase de Activity.

Las aplicaciones creadas hasta ahora disponían de una única


actividad. Esta era creada automáticamente y se le asignaba la vista
definida en res/layout/activity_main.xml. Esta actividad era
arrancada al comenzar la aplicación. A medida que nuestra
aplicación crezca va a ser imprescindible crear nuevas actividades.
En este apartado describiremos como hacerlo. Este proceso se puede
Una actividad (activity) es un componente de aplicación resumir en varios pasos:
que proporciona una pantalla que permite al usuario interactuar
con el fin de hacer algo, tal como marcar el teléfono, tomar una foto, • Crea un nuevo Layout para la actividad.
enviar un correo electrónico, o ver un mapa • Crea una nueva clase descendiente de Activity. En esta
clase tendrás que indicar que el Layout a visualizar es el
Cada actividad se da una ventana en la que extraer su interfaz de
desarrollado en el punto anterior.
usuario. La ventana normalmente llena la pantalla, pero puede ser
• Para que nuestra aplicación sea visible será necesario
menor que la pantalla y flotar en la parte superior de otras
activarla desde otra actividad.
ventanas.
• De forma obligatoria tendremos que registrar toda nueva
Una aplicación por lo general se compone de varias actividades que actividad en AndroidManifest.xml.
están ligadas entre ellas gracias a. Por lo general, una actividad en
una aplicación se especifica como la actividad "principal", que se Crear Actividades en Android (II).
presenta al usuario al iniciar la aplicación por primera vez. Cada LAS ACTIVIDADES O ACTIVITIES ESTÁN CONFORMADAS PRINCIPALMENTE POR
actividad se puede iniciar otra actividad con el fin de realizar DOS PARTES: LA PARTE LÓGICA Y LA PARTE GRÁFICA.

diferentes acciones.
LA PARTE LÓGICA ES UNA ARCHIVO .JAVA QUE ES LA CLASE QUE SE CREA PARA
Cada vez que se inicia una nueva actividad, se detiene la actividad PODER MANIPULAR, INTERACTUAR Y COLOCAR EL CÓDIGO DE ESA ACTIVIDAD.

anterior, pero el sistema conserva la actividad en una pila (el "stack").


LA PARTE GRÁFICA ES UN XML QUE TIENE TODOS LOS ELEMENTOS QUE Del método "onCreate" lo más importante es la línea de
ESTAMOS VIENDO DE UNA PANTALLA DECLARADOS CON ETIQUETAS PARECIDAS código: SetContentView(R.Layout.acivity_main). Que es la que hace
A LAS DEL HTML, ES DECIR, QUE EL DISEÑO DE UNA APLICACIÓN EN ANDROID el trabajo de enlazar la parte lógica con la parte gráfica. El archivo
SE HACE SIMILAR A UNA PÁGINA WEB; XML ES UN PRIMO DE HTML. XML que va a mostrarse cuando se mande a llamar la
clase "MainActivity" es el archivo XML llamado "activity_main".
RESUMIENDO, UNA ACTIVIDAD ESTÁ CONFORMADA POR LA PARTE LÓGICA (UN
ARCHIVO JAVA) Y LA PARTE GRÁFICA (UN ARCHIVO XML). Para cerrar la explicación: si yo creo una actividad nueva y la llamo
"VentanaPrinicipal", debo hacer que herede de activity si quiero que
Adentrando más en el tema anterior, ya sabemos que si tenemos funcione como actividad y para decirle que el archivo XML que va a
un archivo .java, ésto quiere decir que tenemos una clase principal, al mostrar sea el "ventanaprincipal.xml" o "pepito.xml". La línea que dice
ser una actividad extiende de la clase Activity (por eso el nombre) "SetContentView" debe llevar dentro algo parecido a esto:
que nos proporciona Android para crear actividades con sus "setContentView"(R.layout.ventanaprincipal).
métodos asignados.
Ciclo de vida de una actividad en Android.
Veamos una actividad básica:
A diferencia de muchos sistemas o aplicaciones que se inician con
Por ejemplo, este sería nuestro archivo "ManActivity" en el del un método "main()", en Android la secuencia es diferente y es como
ejercicio del "HolaMundo" que relizamos en unidades anteriores. se muestra en la imagen:

En la imagen anterior, lo que está encerrado en óvalos son


los “estados” y para llegar a un estado tenemos que pasar por un
método o más, en la imagen se muestran los métodos y son los
Expliquemos por líneas: que tiene el prefijo “on”.

Entonces, por ejemplo:

Ejemplo 1: Para llegar al estado "Created" de una actividad, pasamos


por el método "onCreate()".

Ejemplo 2: Para pasar al estado "Started" desde el estado "Stopped"


Los dos imports, son la forma de decir que necesitamos esos
pasamos por los métodos "onRestart()" y "onStart()".
archivos para trabajar dentro de la clase, los que ya nos da Android
para no tener que escribir las cosas desde cero. Como ves en la imagen, tenemos una pirámide que se inicia por la
izquierda con el "onCreate()" y termina del lado derecho con el
"onDestroy", pasando del principio al final.

Tienes que tener presente que no siempre utilizamos los métodos


del ciclo vida, depende de lo que vamos a necesitar y cuándo lo vamos
a necesitar, existiendo aplicaciones donde se aplican todos, ninguno
o solo un par.

Los métodos más importantes son los siguientes:


En esta última sección de código lo que estamos haciendo es crear
una clase que se llama "MainActivity" y la estamos extendiendo de • onCreate()
acvitity, en español esto es el concepto de herencia de la famosa • onStart()
programación orientada a objetos, estamos diciendo
• onResume()
que "mainactivity" es una clase que hereda las cosas de la clase
• onPause()
Activity que ya tiene Android definida.
• onStop()
Todas las activities deben llevar por lo menos un método, el • onRestart()
método "oncreate", que es en donde se crea la actividad o podemos • onDestroy()
decir que es donde se le da vida o comienza la vida.
onCreate() Archivo Manifest en una aplicación Android.
Android Manifest es un archivo XML que contiene nodos
Es el que debemos ejecutar en un inicio para definir, por ejemplo, la
descriptivos sobre las características de una aplicación Android.
interfaz del usuario y también crear algunas variables de ámbito
Características como los building blocks existentes, la versión de
de la clase. Este método por lógica solo se debería ejecutar solo una
SDK usada, los permisos necesarios para ejecutar algunos servicios
vez, al momento de invocar la actividad. En este método casi siempre
y muchas más. En pocas palabras el Android Manifest es un
vamos a encontrar cómo se define un archivo XML, como la parte
panorama de toda nuestra aplicación.
gráfica de la actividad o la configuración de la interfaz.

Si abres el archivo en el editor verás un código similar a este:


Cuando el método "onCreate()" termina de ejecutarse llama al
método "onStart()" y "onResume()", esto sucede de manera muy
rápida.

Técnicamente, la actividad se vuelve visible para nuestro usuario


cuando llamamos en "onStart()", pero como sigue muy rápido al
"onResume()" y se mantiene en ese estado hasta que suceda otra
cosa. Por ejemplo, cuando se apaga la pantalla o cuando vamos a
otra actividad, también en caso de recibir una llamada telefónica.

onStart()

Es donde la actividad se muestra ya al usuario como comentamos


anteriormente.

onResume()

Es el estado en donde se encuentra en primer plano y el usuario


Todas las aplicaciones deben contener este archivo por convención.
interactúa con la actividad, podemos decir en español que es el
El nombre debe permanecer intacto, ya que se usa como referencia
estado “corriendo” o “ejecutando”.
para el parsing de nuestra aplicación. El nodo raíz de este
onPaused() documento se representa con la etiqueta <manifest> y por
obligación debe contener un hijo de tipo <application>.
Es cuando esta se encuentra parcialmente oscurecida por una
actividad que se encuentra en el primer plano, por ejemplo está En el código anterior podemos observar que manifest posee dos
medio transparente o no cubre toda la pantalla, en este estado no atributos, xmlsn:android y package. El primero no debemos
se reciben datos de entrada del usuario y no puede ejecutarse código. cambiarlo nunca, ya que es el namespace del archivo.

onStop() El atributo package indica el nombre del paquete Java que soporta
a nuestra aplicación. El nombre del paquete debe ser único y un
En este estado se encuentra completamente invisible u oculto para
diferenciador a largo plazo.
el usuario, podemos decir que se encuentra en el “fondo”, en este
estado podemos decir que todo se congela, por ejemplo las variables La etiqueta <application> representa como estará construida
e información se mantiene pero no podemos ejecutar el código. nuestra aplicación. Dentro de ella definiremos nodos referentes a
las actividades que contiene, las librerías incluidas,
onRestart()
los Intents, Providers, y demás componentes.
Este método se llama después del "onStop()" cuando la actividad
Algunos atributos de la etiqueta <application> son:
actual se está volviendo a mostrar al usuario, es decir, cuando se
regresa a la actividad. Después de este continua el "onStart()" y luego • allowBackup: Este atributo puede tomar los valores de
en "onResume()" y finalmente ya está de nuevo mostrándose la true o false. Indica si la aplicación será persistente al
actividad al usuario. cerrar nuestro Android Virtual Device (AVD).

onDestroy()
• icon: Indica donde está ubicado el icono que se usará en la
aplicación. Debemos indicar la ruta y el nombre del archivo
Cuando el sistema destruye su actividad se manda a llamar al que representa el icono. En este caso apuntamos a las
método "onDestroy()" para la actividad. Este método es la última carpetas drawable donde se encuentra ic_launcher.png.
oportunidad que tenemos de limpiar los recursos y que si no los Icono por defecto que nos proporciona Android Studio.
eliminamos podrían no tener un buen rendimiento para el usuario • label: Es el nombre de la aplicación que verá el usuario en
en caso de olvidarlo. Es buena práctica asegurarse de que los hilos su teléfono. Normalmente apunta a la cadena “app_name”
que creamos son destruidos y las acciones de larga duración que se encuentra en el recurso strings.xml (Más adelante
también estén ya detenidas. lo veremos).
• theme: Este atributo apunta al archivo de recursos
styles.xml, donde se define la personalización del estilo
visual de nuestra aplicación.
Dentro de <application> encontraremos expresada la actividad
principal que definimos al crear el proyecto Test. Usaremos
<activity> para representar un nodo tipo actividad.

<activity> tiene dos atributos: name, el cual se refiere a la clase


Java que hace referencia a esta actividad (Comienza con un punto
“.”). Y el atributo label que hace referencia al texto que se mostrará
en la cabecera de la actividad. En este caso es el mismo string
“app_name”. Carpeta Layouts en Android.
En la carpeta layout encontrarás los archivos de diseño de todas
Por el momento estudiaremos hasta este punto. En próximos
tus actividades. En este caso existe el archivo activiy_my.xml. Este
artículos veremos el proposito de los nodos <intent-filter>, <action>
archivo representa el diseño de la interfaz de mi actividad principal.
y <category>.
En el se establecerán todos los widgets (pequeña aplicación o
El archivo Strings.xml en Android. programa ejecutados por el motor de widgets) que vaya a agregar

Dentro de la carpeta”res” encontraremos todos aquellos recursos a la actividad.

tercerizados para nuestra aplicación. Esta práctica de excluir los


atributos de la aplicación a través de archivos externos, permite
reducir la complejidad de diseño en las interfaces.

Uno de los recursos más relevantes es el archivo strings.xml que se


encuentra dentro de la subcarpeta values. Este fichero almacena
todas las cadenas que se muestran en los widgets (controles,
formas, botones, vistas, etc.) de nuestras actividades.

Por ejemplo, si tuvieses un botón cuyo título es “Presiona aquí”, es


recomendable incluir dicha cadena en tu archivo strings.xml.

Para declarar nuestro recurso de strings usaremos el nodo raíz


Construir la interfaz a través de nodos XML es mucho más sencillo
<resources>. Para declarar las cadenas usaremos la etiqueta
que la creación a través de código Java. Adicionalmente Android
<string> y estableceremos el atributo name como identificador.
Studio nos ha dotado de un panel de diseño estilo Drag and Drop, lo
Dentro de esta etiqueta pondremos el texto que se visualizará en
cual es una bendición para los desarrolladores, ya que facilita
el componente de interfaz. Esta declaración es similar al uso de la
demasiado la creación de una interfaz de usuario.
etiqueta <h1>Texto</h1> en HTML.
Este archivo de diseño comienza con un nodo raíz llamado
Si abres el archivo, verás que se encuentran tres nodos del tipo
<RelativeLayout>. Un Layout es el contenedor principal que define el
<string> : “app_name”, “action_settings” y “hello_world”.
orden y secuencia en que se organizarán los widgets en nuestra
actividad. Existen varios tipos de Layouts, como por ejemplo el
LinearLayout, GridLayout, FrameLayout, etc.

Android Studio crea por defecto un RelativeLayout porque permite


crear un grupo de componentes con ubicaciones relativas. Quiere
decir que se ubicaran por referencias y no por valores absolutos.
Esto permite ajustar nuestras aplicaciones a cualquier tipo de
pantalla para dispositivos móviles.

Por ejemplo, si recordamos los atributos que ya vimos, algunas de


app_name contiene el nombre de la aplicación. En este caso las utilidades de los atributos para <RelativeLayout> son:
es “Test”, action_settings se refiere al título del menú acciones y
hello_world es el string para nuestro mensaje en pantalla la • layout_width: Es el ancho que tendrá el layout dentro de
actividad. la actividad. Aunque se puede especificar con unidades
personalizadas, es recomendable usar match_parent
El archivo strings.xml es muy útil para los desarrolladores. Una de
para ajustarlo al ancho del dispositivo.
sus grandes utilidades es facilitar el uso de múltiples idiomas en tu
• layout_height: Representa la dimensión vertical del
aplicación. Ésto se debe a que puedes externalizar las cadenas del
layout. Usa match_parent para ajustarla al dispositivo.
código java y seleccionar la versión del archivo strings.xml con
el lenguaje necesitado. Se recominda recomienda utilizar este
• paddingLeft, paddingRight: Es el espacio lateral existente
entre el contorno del Layout y los widgets. Su valor
archivo siempre, incluye todo tu texto y no dejes nada por fuera, tu
apunta al archivo de recursos dimens.xml ubicado en la
aplicación te lo agradecerá en el futuro.
carpeta “values”. Este archivo contiene nodos de tipo
<dimen> con valores density-indepent pixels (dp). Para
estos paddings se usa el nodo “activity-horizontal-margin”, Miremos una pequeña imagen ilustrativa:
cuyo valor estándar son 16dp.
• paddingTop, paddingBottom: Es el espacio vertical
existente entre el contorno del Layout y los widgets. Su
valor es igual a el nodo “activity-vertical-margin” de
dimens.xml.
• context: Define el nombre del archivo Java que contiene la
actividad donde el Layout será acogido.

Con este diseñador solo ubicamos los componentes donde nos


plazca y automáticamente son generados como nodos en nuestro
archivo.

Los lectores que han creado páginas web a través de frameworks


como DreamWeaver podrán entender rápidamente el concepto,
debido a que diseñar la interfaz de una aplicación Android es muy
similar a crear aplicaciones HTML.
La imagen muestra algunos ejemplos de dispositivos móviles cuya
Carpeta Drawable en Android Studio. densidad se encuentra dentro de los rangos establecidos. Al final
Podemos encontrar el icono de la aplicación en una serie de carpetas podemos ver una categoría extra llamada xxxhdpi. Esta
que comienzan por el cualificador drawable. Estas carpetas se denominación describe a la densidad de televisores de última
relacionan directamente con el tipo de densidad de pantalla donde generación que ejecutan Android.
se ejecutará nuestra aplicación.
La Clase R.java en Android.
El archivo R.java es una archivo que se autogenera dentro de la
carpeta build, para linkear o vincular todos los recursos que tenemos
en nuestro proyecto al código Java.

La mayoría de dispositivos móviles actuales tienen uno de estos 4


tipos de densidades:

• Mediun Dots per Inch(mdpi): Este tipo de pantallas tienen


una densidad de 160 puntos por pulgada.
• High Dots per Inch(hdpi): En esta clasificación
encontraremos teléfonos cuya resolución es de 240
puntos por pulgada.
• Extra high dots per inch(xhdpi): Resoluciones mayores
a 340 puntos por pulgada
• Extra Extra high dots per inch(xxhdpi): Rangos de
resoluciones mayores a 480 puntos por pulgada.
Como ves, la clase R contiene clases anidadas que representan • Option Menu: Estos tipos de menú se encuentra
todos los recursos de nuestro proyecto. Cada atributo tiene un diseñados para ser implementados en un Activity, dentro
dirección de memoria asociada referenciada a un recurso en se colocan acciones concretas que el usuario en algún
específico. Por ejemplo, la clase string posee el atributo hello_world, momento pueda solicitar, como Ajustes, About, Buscar
el cual representa nuestro TextView en la actividad principal. Por etc…
ejemplo, según la imagen de código anterior, este recurso está • Context Menu: Este tipo de menú es un menú flotante
ubicado en la posición0x7f050002. como editar, compartir, eliminar que solo aparece cuando
el usuario realizo un clic prolongado en algún elemento.
Es importante que recuerdes no modificar el archivo R.java, ya que
el se actualiza automáticamente al añadir un nuevo elemento al • Popup Menu: En este tipo de menú se muestra una lista

proyecto. de elementos en forma vertical con diferentes Opciones.

Para estos tipos de Menús Android proporciona un estándar (XML)


La carpeta java de un proyecto en Android Studio.
para definir los elementos del menú, en este Post nos centraremos
En la carpeta “java” se alojan todos los archivos relacionados con
en el menú de opciones de un Activity.
nuestras actividades y otros archivos fuente auxiliares.

Practica.

Para definir un menú es necesario crear un archivo XML dentro del


directorio res/menú (Puedes utilizar el archivo main.xml que ya se
encuentra dentro) y agregar las siguientes líneas:
Al abrir nuestro archivo MyActivity.java veremos toda la lógica
necesaria para que la actividad interactúe de manera correcta con
el usuario.

Con estas nuevas líneas obtenemos un pequeño menú con dos


opciones, por ejemplo: un hola y un adiós, asignamos un ID para su
posterior manejo a cada uno de los ítems como lo realizamos
normalmente con algún tipo de componente (Botón, Área de Texto
etc. ) y colocamos un texto en ellos apoyándonos del archivo
string.xml (el cual se localiza en el directorio res/values dentro de
nuestro proyecto).

Si necesitamos agregar un submenú a nuestro menú resulta ser


una tarea sencilla, basta con agregar un nuevo menú a nuestro
Item en el cual deseemos que tenga el sub-menú tal como se
muestra abajo.

Para toda actividad creada debemos extender una subclase de


la superclase Activity. Como ya hemos visto, en el punto 10.1. de
unidad PMDM04, las actividades tienen un ciclo de vida y lo único
que esta bajo nuestro control es la manipulación de cada estado.

Las actividades no tienen un punto de entrada main(). Ésto se debe


a que las aplicaciones Android parten de múltiples puntos de
ejecución.

Creación de un menú en Android.


Los menús son parte importante en nuestras aplicaciones en
Android e Integrar uno nuevo a nuestro proyecto es relativamente
fácil y solo es cuestión de modificar dos archivos.

En Android existen 3 tipos de Menús:


Una vez tengamos desarrollado nuestro menú, procedemos a
modificar nuestro método onCreateOptionsMenu:

Nota: En caso no hayas creado un nuevo archivo XML en la dirección Si deseamos agregar un icono para alguna opción de nuestro menú
res/menú para el menú, y hayas utilizado en main.xml que se es necesario colocar la siguiente línea en nuestro <tiem> del xml:
encontraba ya existente no es necesario modificar nuestro método
onCreateOptionsMenu y éste debe quedar como lo siguiente:

ActionBar y ToolBar en Android.


La ActionBar (Barra de acción) en el desarrollo de Android. La
ActionBar es un menú auxiliar de las aplicaciones Android, que se
ubica en la parte superior de cada actividad. Digo de cada actividad,
ya que es un elemento que por lo general es persistente y
Sin embargo hasta este punto nuestro menú es un menú inservible,
habitualmente suele mostrar el nombre de la pantalla visualizada,
debido a que solo tiene la capacidad de aparecer pero no tiene
el icono de la aplicación (o el del menú de navegación) y las acciones
ninguna funcionalidad, es por eso que debemos crear
disponibles.
nuestro método onOptionsItemSelected que recibe como
parámetro un objeto de tipo MenuItem. La ActionBar forma parte del profundo rediseño acometido por
Google para Android y su adopción fue bastante rápida. Para poder
Mediante un switch colocamos las diferentes opciones que pueden
utilizarla en Android 2 era necesario recurrir a implementaciones
presentarse en el menú así como un pequeño mensaje mediante
propias o de terceros como la popular ActionBarSherlock pero en
Tost para saber que las opciones funcionan.
2013 se incluyó en librería de compatibilidad.

En la ActionBar se proporcionan al usuario las acciones que puede


realizar desde cada pantalla concreta. Estas acciones se muestran
como un icono, un texto, un icono con un texto o dentro de un menú
desplegable.

Esta pequeña barra tiene enormes utilidades, como por ejemplo:


Proveernos acceso rápido a las acciones más comunes y solicitadas
por los usuarios, organizar la navegación entre actividades
(pestañas para swiping, expand and collapse, navigation drawers,
etc.), proporcionarnos un espacio donde diferenciar nuestra
aplicación de otras aplicaciones (a través del título, iconos
particulares y demás) y proyectar fácil acceso de las funcionalidades
En muchas ocasiones tendremos diferentes opciones para el de la aplicación.
usuario, en esos casos será necesario utilizar un menú o sub-menú
el cual pueda ser marcado. La barra de acción se divide en cuatro partes fundamentales que
debemos reconocer antes de empezar a programar sobre ella. Por
lo que veremos la siguiente ilustración sobre su estructura:

y con la linea android:checked="true" podemos establecer un item


seleccionado por default. Dentro del switch podemos establecer su
nueva marcación.

Observemos la definición de cada segmento del ActionBar:

• Icono de la aplicación: Como ya hemos visto, en esta


ubicación se proyecta el recurso drawable del icono de la
aplicación. Por defecto se ha usado un icono El widget Toolbar forma parte de Android 5 (API 21) pero ha sido
predeterminado por los recursos de Android, pero es ideal incluido en la librería de compatibilidad para que pueda ser utilizado
darle vida a tu aplicación diseñando tu propia imagen, logo en cualquier versión de Android. En este artículo se utilizará la
o distinción. Toolbar proporcionada por la librería de compatibilidad y el proyecto
• View de control: Este espacio esta diseñado para insertar de ejemplo requerirá como mínimo Android Gingerbread (API 10).
views que permitan acceder al contenido de la actividad
La Action Bar está siendo reemplazada por un nuevo componente
con el fin de mejorar la navegación. Por lo general se
Android llamado Toolbar.
usan Spinners, TextViews, SearchViews, etc., para
controlar el contenido. Normalmente vemos un texto Las nuevas guías de diseño de Google han introducido el concepto
estático que visualiza el nombre de la aplicación. de App Bar que es una barra que tiene más flexibilidad de
• Botones de acción: Representan las acciones más transformación y personalización que hace parte del checklist de
populares dentro de la aplicación, las cuales podemos diseño de Material Design. Así que si deseas crear aplicaciones para
ejecutar rápidamente al presionarlos. Android 5 en adelante es necesario que aprendas a usarla.
• Despliegue de acciones: Este segmento contiene una lista
Vistas y gráficos en Android.
de acciones que no son tan populares, pero pueden ser
necesitadas en algún momento por el usuario para tener La API de Android viene con la clase Canvas que representa una

acceso de forma sencilla. superficie donde podemos dibujar y contiene muchos métodos para
dibujar formas: representar líneas, círculos, texto, etc. Se puede crear
rectángulos, cuadrados, círculos, textos, bitmaps o formas libres.

Para dibujar en un Canvas necesitaremos un pincel (Paint) donde


definiremos el color, grosor de trazo, transparencia, etc.

También podremos definir una matriz de 3x3 (Matrix) que nos


permitirá transformar coordenadas aplicando una translación,
escala o rotación. Otra opción consiste en definir un área conocida
como Clip, de forma que los métodos de dibujo afecten solo a esta
área.

Para dibujar dos rectángulos de colores con una margen simétrica


sobre los bordes exteriores, creamos una clase que herede de View y
Para añadir el diseño a la Action Bar necesitamos usar un archivo la usamos al lugar del Layout tradicional. En esta clase se podrá
de diseño que contenga los nodos necesarios para generar las trabajar en toda la pantalla sin depender de la clase Activity, se
opciones. usará libremente todos los métodos con Paint.

Pero eso no es problema para nosotros, ya que Android Studio La clase Path ermite definir un trazado a partir de segmentos de
autogeneró un archivo de recursos en la dirección main/res/menu. línea y curvas. Una vez definido puede ser dibujado
con canvas.drawPath(Path, Paint). Un Path ambién puede ser
Si abres el archivo main.xml verás el diseño de nuestra Action Bar
utilizado para dibujar un texto sobre el trazado marcado.
que ha sido creada por defecto.
Introducción al diseño de interfaces
gráficas en Android

Índice
1 Vistas............................................................................................................................ 2
1.1 Crear interfaces de usuario con vistas......................................................................2
1.2 Las vistas de Android...............................................................................................3
2 Layouts......................................................................................................................... 4
2.1 Utilizando layouts.................................................................................................... 5
2.2 Optimizar layouts.....................................................................................................6
3 Uso básico de vistas y layouts...................................................................................... 7
3.1 TextView..................................................................................................................7
3.2 EditText....................................................................................................................7
3.3 Button.......................................................................................................................8
3.4 CheckBox.................................................................................................................9
3.5 RadioButton........................................................................................................... 10
3.6 Spinner................................................................................................................... 11
3.7 LinearLayout..........................................................................................................12
3.8 TableLayout........................................................................................................... 14
3.9 RelativeLayout.......................................................................................................16
4 Interfaces independientes de densidad y resolución...................................................17
4.1 Múltiples archivos de recurso................................................................................ 17
4.2 Indicar las configuraciones de pantalla soportadas por nuestra aplicación........... 18
4.3 Prácticas a seguir para conseguir interfaces independientes de la resolución....... 19

Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.


Introducción al diseño de interfaces gráficas en Android

En esta sesión comenzaremos a tratar el tema de la interfaz gráfica de las aplicaciones en


Android. Pero antes de empezar hemos de tener en cuenta que la terminología de Android
referida a interfaces gráficas puede resultar un poco extraña al principio. Examinemos
alguno de los conceptos que trataremos en mayor profundidad a lo largo de la sesión:
• Las Vistas son la base del desarrollo de interfaces gráficas en Android. Todos los
elementos gráficos, comúnmente llamados widgets o componentes en otros entornos,
son subclases de View. Otros elementos más complejos, como agrupaciones de vistas
o layouts también heredan de esta clase. Así pues, en Android, un botón será una
vista, así como también un campo de edición de texto.
• Los Grupos de Vistas son extensiones de la vista genérica. Subclases de ViewGroup,
pueden contener múltiples vistas hijas. Los layouts son extensiones de los grupos de
vistas.
• Los Layouts son medios por los que organizar vistas en la pantalla del dispositivo.
Existen diferentes tipos de layout que nos permitirán disponer los elementos de la
pantalla de diversas maneras.
• Cada una de las pantallas o ventanas de nuestra aplicación se corresponderá con una
Actividad. Las actividades ya han sido tratadas en detalle en sesiones anteriores. En
Android son el equivalente de los formularios. Para mostrar una interfaz de usuario la
operación que se debe realizar es asignar una vista (normalmente un layout) a una
actividad.

1. Vistas

Todos los componentes visuales en Android son una subclase de la clase View. No se les
llama widgets para no confundirlos con las aplicaciones de tipo widget que se pueden
mostrar en la ventana inicial de Android. En sesiones anteriores ya hemos conocido
algunas vistas: el botón (clase Button) y la etiqueta de texto (clase TextView).

1.1. Crear interfaces de usuario con vistas

Cuando se inicia una actividad, lo hace con una pantalla temporalmente vacía sobre la
que deberemos colocar todos los elementos gráficos de su interfaz. Para asignar el
interfaz de usuario se hace uso del método setContentView, pasando como parámetro
una instancia de la clase View o de alguna de sus subclases. Este método también acepta
como parámetro un identificador de recurso, correspondiente a un archivo XML con una
descripción de interfaz gráfica. Este segundo método suele ser el más habitual.
Usar recursos de tipo layout permite separar la capa de presentación de la capa lógica,
proporcionando la suficiente flexibilidad para cambiar la interfaz gráfica sin necesidad de
modificar ni una línea de código. Definir la interfaz gráfica por medio de recursos en
lugar de mediante código permite también especificar diferentes layouts en función de
diferentes configuraciones de hardware o incluso que se pueda modificar la interfaz en
tiempo de ejecución cuando se produzca algún evento, como por ejemplo cambiar la

2
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

orientación de la pantalla.
En el siguiente código vemos como inicializar la interfaz gráfica a partir de un layout
definido en los recursos de la aplicación. Normalmente el recurso consistirá en un archivo
XML almacenado en la carpeta /res/layout/. En este ejemplo se supone que se está
haciendo uso del archivo milayout.xml, el cual se encuentra precisamente en dicha
carpeta:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.milayout);
TextView miTexto = (TextView)findViewById(R.id.texto);
}

En el ejemplo anterior también se puede observar cómo acceder desde el código de la


actividad a cualquiera de las vistas definidas en el layout, por medio de una llamada al
método findViewById. En este caso se crea una instancia de la clase TextView, que se
corresponderá con el elemento TextView del layout milayout, con lo que podremos
acceder a la vista desde el código para modificar el texto que muestra, poder asociarle
manejadores de eventos, etc. El método findViewById recibe como parámetro el
identificador de la vista en el layout. El identificador de una vista se especifica mediante
su atributo android:id y tiene la siguiente sintaxis:
android:id="@+id/[IDENTIFICADOR]", donde [IDENTIFICADOR] es el identificador
que le queremos asignar al elemento.
Como se ha comentado anteriormente, la otra alternativa para crear la interfaz gráfica de
una actividad es construirla directamente desde el código fuente. En el siguiente ejemplo
vemos cómo asignar una interfaz gráfica compuesta únicamente por un TextView a una
actividad cualquiera:
@Override
public void onCreate(Bundle sagedInstaceState) {
super.onCreate(savedInstanceState);
TextView texto = new TextView(this);
setContentView(texto);
texto.setText("Hola Mundo!");
}

El mayor inconveniente es que el método setContentView tan solo acepta como


parámetro una única vista, así que si queremos una interfaz más compleja deberemos
pasar como parámetro algún layout que deberemos haber construido manualmente en el
código.

1.2. Las vistas de Android

Android proporciona una gran variedad de vistas básicas para poder diseñar interfaces de
usuario de manera sencilla. Usando estas vistas podremos desarrollar aplicaciones

3
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

consistentes visualmente con las del resto del sistema, aunque también es posible
modificarlas o ampliar sus funcionalidades. Algunas de las vistas más utilizadas son:
• TextView: una etiqueta de sólo lectura. Permite texto multilínea, formateado de
cadenas, etc.
• EditText: una caja de texto editable. Permite texto multilínea, texto flotante, etc.
• ListView: un grupo de vistas que crea y administra una lista vertical de vistas,
correspondiéndose cada una de ellas a una fila de la lista.
• Spinner: un cuadro de selección que permite seleccionar un elemento de una lista de
posibles opciones. Se muestra como un botón que al ser pulsado visualiza el listado de
posibles opciones.
• Button: un botón normal.
• CheckBox: un botón con dos posibles estados representado por un recuadro que
puede estar marcado o no.
• RadioButton: un grupo de botones con dos posibles estados. Uno de estos grupos
muestra al usuario un conjunto de opciones de las cuales sólo una puede estar activa
simultáneamente.
• SeekBar: una barra de desplazamiento, que permite escoger visualmente un valor
dentro de un rango entre un valor inicial y final.

Vistas básicas de Android


Estos son sólo unos ejemplos. Android pone a nuestra disposición vistas más avanzadas,
incluyendo selectores de fecha, cajas de texto con autocompletado, mapas, galerías y
pestañas. En http://developer.android.com/guide/tutorials/views/index.html se puede
consultar un listado de los widgets disponibles en Android.

2. Layouts

4
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

Los layouts son subclases de ViewGroup utilizadas para posicionar diferentes vistas en
nuestra interfaz gráfica. Los layouts se pueden anidar con el objetivo de conseguir crear
layouts más complejos. Algunos de los layouts disponibles en Android son:
• FrameLayout: es el layout más simple. Lo único que hace es añadir cada vista a la
esquina superior izquierda. Si se añaden varias vistas se van colocando una encima de
la otra, de tal forma que las vistas superiores ocultan a las inferiores.
• LinearLayout: alinea diferentes vistas ya sea en una línea horizontal o una línea
vertical. Un LinearLayout vertical consiste en una columna de vistas, mientras que
un LinearLayout horizontal no es más que una fila de vistas. Este layout permite
establecer un peso mediante la propiedad weight a cada elemento, lo que controlará
el tamaño relativo de cada elemento en la vista.
• RelativeLayout: es el layout nativo más flexible, permitiendo definir la posición de
cada una de sus vistas de manera relativa a la posición de los demás o a los bordes de
la pantalla.
• TableLayout: permite disponer un conjunto de vistas en una rejilla formada por
varias filas y columnas. Es posible permitir que las filas o columnas crezcan o
disminuyan de tamaño.
• Gallery: muestra una lista horizontal de vistas en la que se puede navegar mediante
un scroll.
La documentación de Android describe en detalle las características y propiedades de
cada layout. Esta documentación se puede consultar en
http://developer.android.com/guide/topics/ui/layout-objects.html.

2.1. Utilizando layouts

La manera habitual de hacer uso de layouts es mediante un fichero XML definido como
un recurso de la aplicación, en la carpeta /res/layout/. Este fichero debe contener un
elemento raíz, el cual podrá contener de manera anidada tantos layouts y vistas como sea
necesario. A continuación se muestra un ejemplo de layout en el que un TextView es
colocado sobre un EditText usando un LinearLayout vertical:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Introduce un texto"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Escribe el texto aquí"
/>
</LinearLayout>

5
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

Para cada elemento se debe suministrar un valor para los atributos layout_width y
layout_height. Se podría utilizar una altura o anchura exacta en píxeles, pero no es
aconsejable, ya que nuestra aplicación podría ejecutarse en terminales con diferentes
resoluciones o tamaños de pantalla. Los valores más habituales para estos atributos, y que
además permiten que haya independencia del hardware, son los usados en este ejemplo:
fill_parent y wrap_content.

El valor wrap_content limita el tamaño de una vista al mínimo requerido para poder
mostrar sus contenidos. Por su parte, el valor fill_parent expande la vista para que
ocupe todo el tamaño disponible en su vista padre (o en la pantalla, si se trata del
elemento raíz). En el ejemplo anterior, el layout situado como elemento raíz ocupará toda
la pantalla. Las dos vistas dentro del layout ocuparán toda la anchura disponible, mientras
que su altura será tan sólo la necesaria para mostrar sus respectivos textos.
En el caso en el que sea estrictamente necesario por algún motivo (ya que es algo que se
desaconseja en general) es posible implementar un layout en el propio código fuente.
Cuando asignamos vistas a layouts por medio de código, es importante hacer uso de
LayoutParameters por medio del método setLayoutParams, o pasándolos como
parámetro en la llamada a addView, tal como se muestra en el siguiente ejemplo:
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
TextView texto = new TextView(this);
EditText edicion = new EditText(this);
text.setText("Introduce un texto");
edicion.setText("Escribe el texto aquí");
int lHeight = LinearLayout.LayoutParams.FILL_PARENT;
int lWidth = LienarLayout.LayoutParams.WRAP_CONTENT;
ll.addView(texto, new LinearLayout.LayoutParams(lHeight, lWidth));
ll.addView(edicion, new LinearLayout.layoutParams(lHeight, lWidth));
setContentView(ll);

2.2. Optimizar layouts

El proceso mediante el cual se rellena la pantalla correspondiente a una actividad de


elementos gráficos es costoso computacionalmente. El añadir un nuevo layout o una
nueva vista puede tener un gran impacto en la latencia a la hora de navegar por las
diferentes actividades de nuestra aplicación.
Suele ser una buena práctica, en general, utilizar layouts tan simples como sea posible.
Para conseguir una interfaz más eficiente podemos seguir los siguientes consejos:
• Evitar anidamientos innecesarios: no introduzcas un layout dentro de otro a menos
que sea estrictamente necesario. Un LinearLayout dentro de un FrameLayout,
usando ambos el valor fill_parent para su altura y su anchura, no producirá ningún
cambio en el aspecto final de la interfaz y su único efecto será aumentar el coste

6
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

computacional de construir dicha interfaz. Buscar layouts redundantes, sobre todo si


has hecho varios cambios en la interfaz.
• Evitar usar demasiadas vistas: cada vista que se añade a la interfaz requiere recursos y
tiempo de ejecución.
• Evitar anidamientos profundos: debido a que los layouts pueden ser anidados de
cualquier manera y sin limitaciones es fácil caer en la tentación de construir
estructuras complejas, con muchos anidamientos. Aunque no exista un límite para el
nivel de anidamiento, deberemos intentar que sea lo más bajo posible.
Para ayudarnos en la tarea de optimizar nuestros layouts disponemos del comando
layoutopt, incluido en el SDK de Android y que puede ser ejecutado en la línea de
comandos. Para analizar un layout o conjunto de layouts ejecutamos el comando
pasándole como parámetro el nombre de un recurso de tipo layout o una carpeta de
recursos de este tipo. El comando nos mostrará diferentes recomendaciones para mejorar
nuestros layouts.

3. Uso básico de vistas y layouts

En esta sección veremos algunos detalles sobre cómo utilizar algunas de las vistas y
layouts proporcionados por Android. Se debe tener en cuenta que esto no es más que una
introducción, y que es posible encontrar una descripción más detallada de todos estos
elementos en la documentación de Android.

3.1. TextView

Se corresponde con una etiqueta de texto simple, que sirve evidentemente para mostrar un
texto al usuario. Su atributo más importante es android:text, cuyo valor indica el texto
a mostrar por pantalla. También pueden ser de interés los atributos android:textColor
y android:textSize. Una curiosidad es que es posible hacer el TextView editable por
medio del atributo booleano android:editable; sin embargo, esta vista no está
preparada para este tipo de acción. Sólo se podrá editar texto a través de su subclase
EditText.

Dentro del código los métodos más utilizados son setText y appendText, que permiten
modificar el texto del TextView o añadir texto adicional durante la ejecución del
programa. En ambos casos el parámetro recibido será una cadena. Para acceder a su texto
usaremos el método getText.

3.2. EditText

EditText no es más que una subclase de TextView que está preparada para la edición de
texto. Al pulsar sobre la vista la interfaz de Android mostrará un teclado para poder
introducir nuestros datos. En el código se maneja igual que un TextView (por medio de

7
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

los métodos getText o setText, por ejemplo).

3.3. Button

Esta vista representa un botón normal y corriente, con un texto asociado. Para cambiar el
texto del botón usaremos su atributo android:text.
La forma más habitual de interactuar con un botón es pulsarlo para desencadenar un
evento. Para que nuestra aplicación realice una determinada acción cuando el botón sea
pulsado, deberemos implementar un manejador para el evento OnClick. Veamos un
ejemplo. Supongamos una actividad cuyo layout viene definido por el siguiente fichero
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/texto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Texto"
/>
<Button
android:id="@+id/boton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Púlsame"
/>
</LinearLayout>

En el método onCreate de la actividad podríamos incluir el siguiente código para que


cuando se pulsara el botón se modificara el texto del TextView:
public class MiActividad extends Activity {
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.miLayout);
TextView texto = (TextView)findViewById(R.id.texto);
Button boton = (Button)findViewById(R.id.boton);
boton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
texto.setText("Botón pulsado");
}
});
}
}

También es posible definir el manejador para el click del ratón en el propio fichero XML,
a través del atributo android:onClick. El valor de este atributo será el nombre del
método que se encargará de manejar el evento. Según la especificación del botón definido
en el siguiente ejemplo:
<Button

8
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/textoBoton"
android:onClick="manejador" />

cada vez que se pulse el botón se lanzará el método manejador, cuya cabecera deberá ser
la siguiente:
public void manejador(View vista) {
// Hacer algo
}

3.4. CheckBox

Se trata de un botón que puede encontrarse en dos posibles estados: seleccionado o no


seleccionado. Este tipo de botones, al contrario que en el caso de los pertenecientes a la
vista RadioButton, son totalmente independientes unos de otros, por lo que varios de
ellos pueden encontrarse seleccionados al mismo tiempo. Dentro del código se manejan
individualmente. Los métodos más importantes para manejar vistas de tipo CheckBox son
isChecked, que devuelve un booleano indicando si el botón está seleccionado, y
setChecked, que recibe un valor booleano como parámetro y sirve para indicar el estado
del botón.
Veamos otro ejemplo. Supongamos que el layout de nuestra actividad se define de la
siguiente forma:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/texto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Texto"
/>
<CheckBox
android:id="@+id/micheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Púlsame"
android:checked="false"
/>
</LinearLayout>

Obsérvese como se hace uso del atributo android:text del CheckBox para establecer el
texto que acompañará al botón. También se ha hecho uso del atributo android:checked
para establecer el estado inicial del botón. Para hacer que nuestra actividad reaccione a la
pulsación del CheckBox implementamos el manejador del evento OnClick para el mismo:
public class MiActividad extends Activity {
TextView texto;
CheckBox miCheckbox;

9
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

protected void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.main);
texto = (TextView)findViewById(R.id.texto);
miCheckbox = (CheckBox)findViewById(R.id.micheckbox);
miCheckbox.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Realizamos una acción u otra según
// si el botón está pulsado o no
if (miCheckbox.isChecked()) {
texto.setText("activado");
} else {
texto.setText("desactivado");
}
}
});
}
}

3.5. RadioButton

Un RadioButton, al igual que un CheckButton, tiene dos estados (seleccionado y no


seleccionado). La diferencia con el anterior es que una vez que el botón está en el estado
de seleccionado no puede cambiar de estado por intervención directa del usuario. Los
elementos de tipo RadioButton suelen agruparse normalmente en un elemento de tipo
RadioGroup, de tal forma que la activación de uno de los botones del grupo supondrá la
desactivación de los demás.
En el siguiente ejemplo vemos como definir un grupo formado por dos botones de radio
en el archivo XML de layout de una actividad:
<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton android:id="@+id/radio_si"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sí" />
<RadioButton android:id="@+id/radio_no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No" />
</RadioGroup>

Para que se realice una determinada acción al pulsar uno de los botones de radio
deberemos implementar el manejador del evento OnClick:
public class MiActividad extends Activity {
boolean valor = true;
RadioButton botonSi, botonNo;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
botonSi = (RadioButton)findViewById(R.id.radio_si);
botonNo = (RadioButton)findViewById(R.id.radio_no);

10
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

botonSi.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
valor = true;
}
});
botonNo.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
valor = false;
}
});
}
}

3.6. Spinner

Un Spinner es una vista que permite escoger uno de entre una lista de elementos. Es el
equivalente a un cuadro de selección de un formulario normal. Construir un Spinner en
Android requiere varios pasos, que vamos a ver a continuación.
• En primer lugar añadimos el Spinner a nuestro layout. El atributo android:prompt
indica la cadena de texto a mostrar en el título de la ventana del selector. Obsérvese
como en este caso su valor se ha definido a partir de una cadena del archivo
strings.xml de los recursos de la aplicación. Esto es así porque no es posible asignarle
una cadena como valor a este atributo.
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/eligeopcion"
/>
• Añadimos la cadena para el prompt en /res/values/strings.xml:
<string name="eligeopcion">Elige!</string>
• A continuación debemos indicar cuáles serán las opciones disponibles en el Spinner.
Esto se hará también mediante los recursos de la aplicación. En concreto, crearemos
un fichero en /res/values/ al que llamaremos arrays.xml y que contendrá lo siguiente:
<resources>
<string-array name="opciones">
<item>Mensual</item>
<item>Trimestral</item>
<item>Semestral</item>
<item>Anual</item>
</string-array>
</resources>
• Finalmente añadiremos el código necesario en el método onCreate de la actividad
para que se asocien las opciones al Spinner. Para ello se utiliza un objeto de la clase
ArrayAdapter, que asocia cada cadena de nuestro string-array a una vista que será
mostrada en el Spinner. Al método createFromResource le pasamos el contexto de
la aplicación, el identificador del string-array y un identificador para indicar el tipo

11
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

de vista que queremos asociar a cada elemento seleccionable. El método


setDropDownViewResource se utiliza para definir el tipo de layout que se utilizará
para mostrar la lista completa de opciones. Finalmente se asocia el adaptador al
Spinner:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Spinner s = (Spinner) findViewById(R.id.spinner);
ArrayAdapter adaptador = ArrayAdapter.createFromResource(
this, R.array.opciones, android.R.layout.simple_spinner_item);
adaptador.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
s.setAdapter(adaptador);
}

También podríamos haber asignado el array de valores de forma gráfica, usando la


propiedad Entries del Spinner.
Con respecto a los eventos relacionados con el Spinner, hemos de tener en cuenta que
Android no soporta el manejo de eventos para sus elementos individuales. Si intentamos
crear un manejador para el evento OnClick para cada opción por separado, por ejemplo,
se producirá un error en tiempo de ejecución. En lugar de ello deberemos definir un
manejador de evento global para el Spinner, que tendrá el siguiente aspecto:
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// Hacer algo
}
public void onNothingSelected(AdapterView<?> arg0) {
// Hacer algo
}
});

3.7. LinearLayout

Como se ha comentado anteriormente se trata de un layout que organiza sus componentes


en una única fila o una única columna, según éste sea horizontal o vertical,
respectivamente. Para establecer la orientación podemos utilizar o bien el atributo
android:orientation en la definición del layout en el archivo XML (tomando como
valor horizontal o vertical, siendo el primero el valor por defecto) o mediante el
método setOrientation desde el código.
Un ejemplo de layout de este tipo sería:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

12
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="red"
android:gravity="center_horizontal"
android:background="#aa0000"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"/>
<TextView
android:text="green"
android:gravity="center_horizontal"
android:background="#00aa00"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="2"/>
<TextView
android:text="blue"
android:gravity="center_horizontal"
android:background="#0000aa"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="3"/>
<TextView
android:text="yellow"
android:gravity="center_horizontal"
android:background="#aaaa00"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="4"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="Primera fila"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="Segunda fila"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="Tercera fila"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

13
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

Ejemplo de LinearLayout
En este ejemplo podemos ver el uso de un par de atributos interesantes. En primer lugar
tenemos android:layout-weight, que va a permitir que las diferentes vistas dentro del
LinearLayout ocupen una porción del espacio disponible proporcional a su peso. Por
otra parte tenemos android:gravity, que permite alinear el texto de una vista. En este
caso se ha centro el texto horizontalmente.

3.8. TableLayout

Se trata de un layout que organiza sus elementos como una rejilla dividida en filas y
columnas. Se compone de un conjunto de elementos de tipo TableRow que representan a
las diferentes filas de la tabla. Cada fila a su vez se compone de un conjunto de vistas.
Existe la posibilidad de tener filas con diferente número de vistas; en este caso la tabla
tendrá tantas columnas como celdas tenga la fila que contenga más vistas.
La anchura de una columna vendrá definida por la fila que contenta la vista de máxima
anchura en dicha columna. Esto quiere decir que no tenemos libertad para asignar valor al
atributo android:layout_width, que debería valer match_parent. Sí que podemos
definir la altura, aunque lo más habitual será utilizar el valor wrap_content para el
atributo android:layout_height. Si no se indica lo contrario esos serán los valores por
defecto.
Aunque no podamos controlar la anchura de una columna, si que podemos tener
columnas de anchura dinámica, usando los métodos setColumnShrinkable() y
setColumnStretchable(). El primero permitirá disminuir la anchura de una columna
con tal de que su correspondiente tabla pueda caber correctamente en su elemento padre.
Por su parte, el segundo permitirá aumentar la anchura de una columna para hacer que la
tabla pueda ocupar todo el espacio que tenga disponible. Hay que tener en cuenta que se

14
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

pueden utilizar ambos métodos para una misma columna; en este caso la anchura de la
columna siempre se incrementará hasta que se utilice todo el espacio disponible, pero
nunca más. Otra opción disponible es ocultar una columna por medio del método
setColumnCollapsed.

El siguiente ejemplo muestra un TableLayout definido como recurso de la aplicación por


medio de un fichero XML. Como se puede observar en el código se ha introducido un
elemento View cuya única función es de servir de separador visual entre la segunda y la
tercera fila:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow>
<Button
android:text="Uno"
android:padding="3dip" />
<Button
android:text="Dos"
android:padding="3dip" />
</TableRow>
<TableRow>
<Button
android:text="Tres"
android:padding="3dip" />
<Button
android:text="Cuatro"
android:padding="3dip" />
<Button
android:text="Cinco"
android:padding="3dip" />
</TableRow>
<View
android:layout_height="2dip"
android:background="#FF909090" />
<TableRow>
<Button
android:text="Seis"
android:padding="3dip" />
<Button
android:text="Siete"
android:padding="3dip" />
</TableRow>
</TableLayout>

15
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

Ejemplo de TableLayout

3.9. RelativeLayout

Este layout es muy flexible; permite indicar la posición de un elemento en función de


otros y de los bordes de la pantalla. Enumerar todos sus posibles atributos alargaría
innecesariamente esta sección, así que mostraremos un ejemplo que deje claro su
funcionamiento, dejando al lector la tarea de acudir a la documentación de Android en el
caso de que necesite profundizar más:
<?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="fill_parent">
<TextView
android:id="@+id/etiqueta"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Escribe"/>
<EditText
android:id="@+id/entrada"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_below="@id/etiqueta"/>
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/entrada"
android:layout_alignParentRight="true"
android:layout_marginLeft="10dip"
android:text="OK" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok"

16
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

android:layout_alignTop="@id/ok"
android:text="Cancelar" />
</RelativeLayout>

Ejemplo de RelativeLayout

4. Interfaces independientes de densidad y resolución

Durante el primer año de vida del sistema Android tan solo existía un único terminal que
hiciera uso de este sistema, por lo que a la hora de diseñar una interfaz gráfica no era
necesario tener en mente diferentes configuraciones de hardware. A principios del 2010
se produjo una avalancha en el mercado de nuevos dispositivos basados en Android. Una
consecuencia de esta variedad fue un aumento también en las posibles configuraciones de
resolución o densidad.
A la hora de diseñar una interfaz gráfica para nuestra aplicación Android es importante
tener en cuenta que ésta podría llegar a ejecutarse en una gran variedad de
configuraciones hardware distintas, con diferentes resoluciones (HVGA, QVGA o
WVGA) y densidades de pantalla (3.2 pulgadas, 3.7, 4, etc.). Los tablets cada vez están
más de moda, y es posible que otro tipo de dispositivos también incluyan un sistema
Android en el futuro.
En esta sección trataremos algunas cuestiones relativas a cómo diseñar nuestras interfaces
gráficas teniendo todo esto en cuenta.

4.1. Múltiples archivos de recurso

En la primera sesión del módulo de Android tratamos el tema de los recursos de la

17
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

aplicación. Vimos que para determinados tipos de recurso era posible crear una estructura
paralela de directorios que permitiera almacenar recursos a utilizar con diferentes
configuraciones de hardware. En este apartado vemos diferentes sufijos que podemos
añadir al nombre de la carpeta layout o drawable dentro de los recursos de la aplicación
para definir diferentes interfaces según la configuración de nuestra pantalla.
• Tamaño de la pantalla: tamaño de la pantalla relativa a un terminal de tamaño
"estándar":
• small: una pantalla con un tamaño menor de 3.2 pulgadas.
• medium: para dispositivos con un tamaño de pantalla típico.
• large: para pantallas de un tamaño significativamente mayor que la de un
terminal típico, como la pantalla de una tablet o un netbook.
• Densidad: se refiere a la densidad en píxeles de la pantalla. Normalmente se mide en
puntos por pulgada (dots per inch o dpi). Se calcula en función de la resolución y el
tamaño físico de la pantalla:
• ldpi: usado para almacenar recursos para baja densidad pensados para pantallas
con densidades entre 100 y 140dpi.
• mdpi: usado para pantallas de densidad media entre 140 y 180dpi.
• hdpi: usado para pantalla de alta densidad, entre 190 y 250dpi.
• nodpi: usado para recursos que no deberían ser escalados, sea cual sea la densidad
de la pantalla donde van a ser mostrados.
• Relación de aspecto (aspect ratio): se trata de la relación de la altura con respecto a la
anchura de la pantalla:
• long: usado para pantallas que son mucho más anchas que las de los dispositivos
estándar.
• notlong: usado para terminales con una relación de aspecto estándar.

Cada uno de estos sufijos puede ser utilizado de manera independiente o en combinación
con otros. A continuación tenemos un par de ejemplos de carpetas de recursos para
layouts que hacen uso de alguno de los listados anteriormente:
/res/layout-small-long/
/res/layout-large/
/res/drawable-hdpi/

4.2. Indicar las configuraciones de pantalla soportadas por nuestra aplicación

En el caso de alguna aplicación puede que no sea posible optimizar la interfaz para todas
y cada una de las configuraciones de pantalla existentes. En estos casos puede ser útil
utilizar el elemento supports-screens en el Manifest de la aplicación para especificar
en qué pantallas puede ésta ser ejecutada. En el siguiente código se muestra un ejemplo:
<supports-screens
android:smallScreens="false"
android:normalScreens="true"
android:largeScreens="true"

18
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

android:anyDensity="true"
/>

Un valor de false en alguno de estos atributos forzará a Android a mostrar la interfaz por
medio de un modo de compatibilidad que consistirá básicamente en realizar un escalado.
Esto no suele funcionar siempre de manera correcta, haciendo aparecer elementos
extraños o artefactos en la interfaz.

4.3. Prácticas a seguir para conseguir interfaces independientes de la


resolución

En esta sección resumimos algunas técnicas que podremos utilizar en nuestras


aplicaciones para que puedan ser visualizadas de manera correcta en la inmensa mayoría
de las configuraciones hardware. Pero antes de empezar hemos de tener en cuenta que la
consideración más importante a tener en cuenta es que nunca debemos presuponer cuál
será el tamaño de la pantalla del terminal en el que se ejecutará nuestra aplicación. La
regla de oro es crear layouts para diferentes clases de pantallas (pequeña, normal y
grande) y para diferentes resoluciones (baja, media y alta).
La primera regla es no utilizar nunca tamaños absolutos basados en número de
píxeles. Esto se aplica tanto a layouts, como a vistas y fuentes. En particular se debería
evitar usar el AbsoluteLayout, que se basa en la especificación de la disposición de sus
elementos a partir de coordenadas en píxeles. Haciendo uso del RelativeLayout se
pueden generar interfaces suficientemente complejas, evitando el uso de coordenadas
absolutas, por lo que es una mejor solución.
A la hora de determinar el tamaño de una vista o un layout utilizaremos valores como
fill_parent o wrap_content. También podría ser posible asignar valores a los atributos
layout-width y layout-height medidos en píxeles independientes de la densidad (dp)
o en píxeles independientes de la escala (sp). Esto mismo podría ser aplicado a las
fuentes. Las medidas independientes de la densidad o de la escala son un medio por el
que se puede especificar el tamaño de los componentes de nuestra interfaz de tal forma
que al mostrarse en diferentes configuraciones hardware se escalen para seguir mostrando
el mismo aspecto. Por ejemplo, un dp equivale a un píxel en una pantalla de 160dpi. Una
línea de 2dp de anchura aparecerá como una línea de 3 píxeles de anchura en una pantalla
de 240dpi.
Por supuesto no debemos olvidar utilizar sufijos para crear una estructura paralela de
directorios de recursos, tal como se explicó en la sección anterior. Como mínimo se
deberían definir las siguientes carpetas con sus recursos correspondientes:
/res/drawable-ldpi/
/res/drawable-mdpi/
/res/drawable-hdpi/
/res/layout-small/
/res/layout-normal/
/res/layout-large/

19
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

Por último, no debemos olvidar probar nuestra aplicación en la mayor variedad de


dispositivos posibles. En este sentido podemos hacer uso de la emulación y de Android
SDK, ya que es poco práctico hacer estas pruebas en dispositivos reales. Hemos de
recordar que a la hora de crear un dispositivo virtual para probar nuestras aplicaciones
Android podemos especificar diferentes configuraciones de pantalla, tanto en cuanto a
resolución como en cuanto a densidad. La forma más sencilla de probar diferentes
configuraciones es hacer uso de los skins que incorpora el AVD Manager del SDK. Crea
un dispositivo virtual para cada opción de skin disponible y prueba tu aplicación en todos
ellos.

Selección de skins en el AVD del SDK de Android

20
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Introducción al diseño de interfaces gráficas en Android

21
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
APLICACIONES MULTIMEDIA INTERACTIVAS:
CLASIFICACIÓN
Consuelo Belloch Ortí
Unidad de Tecnología Educativa. Universidad de Valencia

INTRODUCCIÓN

¿Existen aplicaciones que por sus características pueden resultar interesantes para los
pedagogos, logopedas y educadores? Con el desarrollo y evolución de las tecnologías se
han desarrollado ampliamente un conjunto de aplicaciones denominadas APLICACIONES
MULTIMEDIA INTERACTIVAS, que nos permiten interactuar con el ordenador utilizando
diferentes códigos en la presentación de la información (texto, imagen, sonido,... ). Estas
aplicaciones son las más utilizadas en la educación y en los procesos de intervención en
logopedia.

Para tener una visión general de los diferentes tipos de programas, podemos
clasificarlos en función de diferentes criterios:
• Sistema de navegación
• Finalidad y base teórica
• Nivel de control que tiene el profesional.

APLICACIONES MULTIMEDIA INTERACTIVAS

Más interesantes para el desarrollo de procedimientos, habilidades y conocimientos,


son las aplicaciones multimedia interactivas. “Los sistemas Multimedia, en el sentido que
hoy se da al término, son básicamente sistemas interactivos con múltiples códigos”
(Bartolomé, A. 1994).

Según Fred Hoffstetter: Multimedia es el uso del ordenador para presentar y combinar:
texto, gráficos, audio y vídeo con enlaces que permitan al usuario navegar, interactuar,
crear y comunicarse.

Las aplicaciones multimedia pueden estar almacenados en CD-ROMs (uso off-line) o


residir en páginas de Web (uso on-line).

La evolución producida en los sistemas de comunicación ha dado lugar a este tipo


heterogéneo de aplicaciones o programas que tienen dos características básicas:

ƒ Multimedia: Uso de múltiples tipos de información (textos,


gráficos, sonidos, animaciones, videos, etc.) integrados
coherentemente.
ƒ Hipertextual: Interactividad basada en los sistemas de
hipertexto, que permiten decidir y seleccionar la tarea que
deseamos realizar, rompiendo la estructura lineal de la
información.

AplicacionesMultimedia Interactivas: clasificación - 1


Multimedia
Uso de múltiples tipos de información (textos, gráficos, sonidos, animaciones,
videos, etc.) integrados coherentemente.

El uso de los diferentes códigos o medios en la que se presenta la información viene


determinado por la utilidad y funcionalidad de los mismos dentro del programa. Y, la inclusión
de diferentes medios de comunicación -auditivo, visual- facilita el aprendizaje, adaptándose
en mayor medida a los sujetos, a sus características y capacidades (pueden potenciar:
memoria visual, comprensión visual, memoria auditiva, comprensión oral, etc.).

A continuación presentamos brevemente la función que pueden realizar cada uno de


estos códigos de información.

ƒ Texto. Para Daniel Insa y Rosario Morata "El texto refuerza el contenido de la
información y se usa básicamente para afianzar la recepción del mensaje
icónico, para asegurar una mejor comprensión aportando más datos y para
inducir a la reflexión" (1998: 5). La inclusión de texto en las aplicaciones
multimedia permite desarrollar la comprensión lectora, discriminación visual,
fluidez verbal, vocabulario, etc. El texto tiene como función principal favorecer
la reflexión y profundización en los temas, potenciando el pensamiento de
más alto nivel. En las aplicaciones multimedia, además permite aclarar la
información gráfica o icónica. Atendiendo al objetivo y usuarios a los que va
destinada la aplicación multimedia podemos reforzar el componente visual
del texto mediante modificaciones en su formato, resaltando la información
más relevante y añadiendo claridad al mensaje escrito.
ƒ Sonidos. Los sonidos se incorporan en las aplicaciones multimedia
principalmente para facilitar la comprensión de la información clarificándola.
Los sonidos que se incorporar pueden ser locuciones orientadas a
completar el significado de las imágenes, música y efectos sonoros para
conseguir un efecto motivador captando la atención del usuario. Son
especialmente relevantes para algunas temáticas (aprendizaje de idiomas,
música, ...) y sin lugar a duda, para las aplicaciones multimedia cuya finalidad
es la intervención en problemas de comunicación y/o lenguaje. Asimismo, la
inclusión de locuciones y sonidos favorece el refuerzo de la discriminación y
memoria auditiva.

ƒ Gráficos e iconos. Un elemento habitual en las aplicaciones multimedia son


los elementos iconográficos que permiten la representación de palabras,
conceptos, ideas mediante dibujos o imágenes, tendiendo a la representación
de lo esencial del concepto o idea a transmitir. Como indica Martínez Rodrigo
“El lenguaje visual gráfico o iconográfico implica habitualmente abstracción
aun cuando se plantee en términos de hiperrealismo. Siempre un lenguaje
icónico tiende a la abstracción por ser un modo de expresión que busca la
realidad en los códigos universales. ... La abstracción supone el arribo de una
imagen visual a la condición de código” (1997). Su carácter visual le da un
carácter universal, no sólo particular, son por ello adecuadas para la
comunicación de ideas o conceptos en aplicaciones que pueden ser

AplicacionesMultimedia Interactivas: clasificación - 2


utilizadas por personas que hablan diferentes idiomas o con distintos niveles
en el desarrollo del lenguaje.
ƒ Imágenes estáticas. Las imágenes estáticas tienen gran importancia en las
aplicaciones multimedia, su finalidad es ilustrar y facilitar la comprensión de
la información que se desea transmitir. Rodríguez Diéguez (1996) indica que
la imagen puede realizar seis funciones distintas: representación, alusión,
enunciativa, atribución, catalización de experiencias y operación. Podemos
distinguir diferentes tipos de imágenes: fotografías, representaciones
gráficas, fotogramas, ilustraciones, etc.
ƒ Imágenes dinámicas. Las imágenes en movimiento son un recurso de gran
importancia, puesto que transmiten de forma visual secuencias completas de
contenido, ilustrando un apartado de contenido con sentido propio. Mediante
ellas, en ocasiones pueden simularse eventos difíciles de conocer u observar
de forma real. Pueden ser videos o animaciones. La animación permite a
menudo un control mayor de las situaciones mediante esquemas y
figuraciones que la imagen real reflejada en los videos no posibilita.

Hipertextual
Interactividad basada en los sistemas de hipertexto, que permiten decidir y
seleccionar la tarea que deseamos realizar, rompiendo la estructura lineal de la
información .

El término hipertexto fue utilizado en 1967 por Theodor Nelson, haciendo referencia su
estructura interactiva que permite la lectura no secuencial atendiendo a las decisiones del
usuario. El hipertexto es una red de información formada a partir de un conjunto de unidades
de texto que se conectan por múltiples enlaces. En las aplicaciones multimedia interactivas se
pueden establecer diferentes tipos de interrelación entre el usuario y el programa, dando
mayor o menor libertad al usuario para poder establecer su propio recorrido dentro de la
aplicación. El sistema de navegación que utiliza el usuario por el programa está determinada
por la estructura de la aplicación que debe atender a la finalidad y características de la
aplicación multimedia interactiva.

CLASIFICACIÓN SEGÚN SU SISTEMA DE NAVEGACIÓN

La estructura seguida en una aplicación multimedia es de gran relevancia pues


determina el grado de interactividad de la aplicación, por tanto, la selección de un
determinado tipo de estructura para la aplicación condicionará el sistema de navegación
seguido por el usuario y la posibilidad de una mayor o menor interacción con la aplicación.
No existe una estructura mejor que otra, sino que esta estará subordinada a la finalidad de
la aplicación multimedia.

Los sistemas de navegación más usuales en relación a la estructura de las aplicaciones


son:

ƒ LINEAL. El usuario sigue un sistema de navegación lineal o secuencial para


acceder a los diferentes módulos de la aplicación, de tal modo que
únicamente puede seguir un determinado camino o recorrido. Esta estructura
es utilizada en gran parte de las aplicaciones multimedia de ejercitación y

AplicacionesMultimedia Interactivas: clasificación - 3


práctica o en libros multimedia.

ƒ RETICULAR. Se utiliza el hipertexto para permitir que el usuario tenga total


libertad para seguir diferentes caminos cuando navega por el programa,
atendiendo a sus necesidades, deseos, conocimientos, etc. Sería la más
adecuada para las aplicaciones orientadas a la consulta de información, por
ejemplo para la realización de una enciclopedia electrónica.

ƒ JERARQUIZADO. Combina las dos modalidades anteriores. Este sistema es


muy utilizado pues combina las ventajas de los dos sistemas anteriores
(libertad de selección por parte del usuario y organización de la información
atendiendo a su contenido, dificultad, etc.).

Orihuela y Santos (1999) distinguen además otros cuatro tipos de estructuras en las
aplicaciones multimedia interactivas: Paralela, Ramificada, Concéntrica y Mixta.

CLASIFICACIÓN SEGÚN SU FINALIDAD Y BASE TEÓRICA

Se han desarrollado multitud de aplicaciones multimedia, con diferentes objetivos y


funciones pedagógicas. Así, tenemos: enciclopedias multimedia, cuentos interactivos,
juegos educativos, aplicaciones multimedia tutoriales, etc. La finalidad de las aplicaciones
multimedia puede ser predominantemente informativa o formativa, así Bartolomé (1999)
diferencia dos grandes grupos de multimedias:

• Multimedias informativos:

o Libros o cuentos multimedia. Se parecen a los libros convencionales en


formato papel en cuanto a que mantienen una estructura lineal para el
acceso a la información, pero en sus contenidos tiene un mayor peso o
importancia el uso de diferentes códigos en la presentación de esta
información (sonidos, animaciones,...).

AplicacionesMultimedia Interactivas: clasificación - 4


o Enciclopedias y diccionarios multimedia. Al igual que las enciclopedias
y diccionarios en papel son recursos de consulta de información, por lo que
su estructura es principalmente reticular para favorecer el rápido acceso a
la información. Las enciclopedias y diccionarios multimedia utilizan bases
de datos para almacenar la información de consulta de forma estructurada,
de modo que el acceso a la misma sea lo más rápido y sencillo.

o Hipermedias. Son documentos hipertextuales, esto es con información


relacionada a través de enlaces, que presentan información multimedia. Su
estructura es en mayor o menor grado jerarquizada, utilizando diferentes
niveles de información. No obstante, los usuarios tienen gran libertad para
moverse dentro de la aplicación atendiendo a sus intereses.

• Multimedias formativos:

o Programas de ejercitación y práctica. Presentan un conjunto de


ejercicios que deben realizarse siguiente la secuencia predeterminada del
programa. Se basan en la teoría conductista y utilizan un feedback externo
para el refuerzo de las actividades. Han sido muy cuestionados desde la
perspectiva pedagógica, aunque tienen un importante desarrollo y uso en
actividades que exigen el desarrollo y ejercitación de destrezas concretas.

o Tutoriales. Son semejantes a los programas de ejercitación pero


presentan información que debe conocerse o asimilarse previamente a la
realización de los ejercicios. En muchos tutoriales se presenta la figura del
tutor (imagen animada o video) que va guiando el proceso de aprendizaje.
Siguen los postulados del aprendizaje programado.

o Simulaciones. Tienen por objeto la experimentación del usuario con gran


variedad de situaciones reales. Básicamente el programa muestra un
escenario o modelo sobre el que el estudiante puede experimentar, bien
indicando determinados valores para las variables del modelo, o bien
realizando determinadas acciones sobre el mismo, comprobando a
continuación los efectos que sus decisiones han tenido sobre el modelo
propuesto. De este modo, el usuario toma un papel activo en su proceso
de aprendizaje, decidiendo que hacer y analizando las consecuencias de
sus decisiones. Se basan en el aprendizaje por descubrimiento.

o Talleres creativos. Promueven la construcción y/o realización de nuevos


entornos creativos a través del uso de elementos simples. Por ejemplo,
juegos de construcción, taller de dibujo,...

o Resolución de problemas. Estas aplicaciones multimedia tienen por


objeto desarrollar habilidades y destrezas de nivel superior, basándose en
la teoría constructivista. Para ello, se plantean problemas contextualizados
en situaciones reales, que requieren el desarrollo de destrezas tales como
comprensión, análisis, síntesis, etc. Para ello se proporcionan materiales y
recursos para su solución, junto a materiales adicionales para profundizar
en el tema planteado.

o Caza del tesoro. Una caza del tesoro es un documento hipermedia


(página web) en la que se presentan una serie de preguntas sobre un
determinado tema, junto a una lista de direcciones web en las que se

AplicacionesMultimedia Interactivas: clasificación - 5


pueden buscar las respuestas. Como punto final se incluye una pregunta
"la gran pregunta”, que los alumnos deben responder a partir de la
comprensión e integración de lo aprendido durante la búsqueda y
resolución de las preguntas, pues no es posible encontrar la respuesta de
forma directa. Como indica Adell (2003) "Las cazas del tesoro son
estrategias útiles para adquirir información sobre un tema determinado y
practicar habilidades y procedimientos relacionados con las tecnologías de
la información y la comunicación en general y con el acceso a la
información a través de la Internet en particular". En Internet podemos
encontrar ejemplos de Cazas de Tesoro y aplicaciones que permiten su
creación (Aula 21).

o WebQuest. La metodología WebQuest desarrollada por Bernie Dodge y


Tom March, es una actividad orientada a la investigación, en la que parte o
toda la información con la que interaccionan los alumnos, proviene de
Internet. WebQuest usa el mundo real, y tareas auténticas para motivar a
los alumno. Están compuestas por seis partes esenciales: Introducción,
Tarea, Proceso, Recursos, Evaluación y Conclusión. Su estructura es
constructivista y por tanto fuerza a los alumnos a transformar la
información y entenderla; sus estrategias de aprendizaje cooperativo
ayudan a los estudiantes a desarrollar habilidades y a contribuir al producto
final del grupo [March,1999]. Existen múltiples páginas en Internet que
ofrecen ejemplos de WebQuest, como por ejemplo Eduteka que nos
presenta diversos ejemplos de WebQuest en español.

o Wiki. Es una aplicación orientada al aprendizaje colaborativo. Básicamente


consiste en la elaboración de documentos multimedia de forma
colaborativa. Los documentos (páginas wiki) se alojan en un servidor y
puede ser escritos por un conjunto de personas a través de un navegador,
utilizando una notación sencilla para dar formato, crear enlaces, etc.
Cuando alguien edita una página wiki, sus cambios aparecen
inmediatamente en la web, sin pasar por ningún tipo de revisión previa.

CLASIFICACIÓN SEGÚN LA NIVEL DE CONTROL QUE TIENE EL PROFESIONAL

Una de las características más deseables en una aplicación multimedia es su


capacidad para poder ser configurado y/o adaptado por el profesional para poder atender
las necesidades concretas de los usuarios. Los tipos de software según el menor o mayor
nivel de control por parte del profesional son:
o Programas cerrados. Lo componen los programas informáticos, que
trabajan sobre un determinado contenido, y el profesional, no tiene
posibilidad de modificarlo y/o adaptarlo a las características de las
personas con las que trabaja. Tienen una estructura secuencial que no
puede ser modificada por el usuario.

o Programas semiabiertos.- Estas aplicaciones permiten que el profesional


modifique algunos de las características del programa o tome decisiones
sobre el itinerario a seguir. Algunos programas semiabiertos permiten
seleccionar diferentes niveles de dificultad en las actividades a realizar, así

AplicacionesMultimedia Interactivas: clasificación - 6


como adaptar el interface del usuario a las características del mismo
(tamaño de las letras, tipografía, etc.), y la gran mayoría de los mismos son
aplicaciones hipermedia que permiten que el usuario o profesional
seleccione el itinerario. El programa Exler de la Escuela de Patología del
Lenguaje, es un ejemplo de este tipo de programa, puesto que permite:
seleccionar el tipo de actividades que deseamos realizar, el nivel de
dificultad de las actividades y también ajustar la tipografía a las
características de los usuarios.

o Programas abiertos.- Son programas informáticos, que partiendo de un


conjunto de posibilidades de actuación, permiten que el profesional fije el
contenido concreto a desarrollar, pudiendo adaptarlo a las necesidades de
las personas concretas que lo van a utilizar. Un ejemplo de programa
abierto es el programa Clic que puede ser utilizado por los logopedas para
crear ejercicios y actividades orientadas a la intervención de un caso o
problema concreto.

PARA SABER MÁS

Adell. J. Internet en el aula: a la caza del tesoro. Edutec, Revista Electrónica de Tecnología
Educativa, núm. 16. http://edutec.rediris.es/Revelec2/revelec16/adell.htm
Bartolomé, A. (1994) "Multimedia interactivo y sus posibilidades en educación superior",
Pixel-Bit. Revista de medios y educación, 1, 5-14. .
http://www.us.es/pixelbit/articulos/n1/art11.htm
Bartolomé, A. (1999) Hipertextos, hipermedia y multimedia: configuración técnica, principios
para su diseño y aplicaciones didácticas. En Cabero, J. (coord.). Medios
audiovisuales y nuevas tecnologías para la formación del siglo XXI. Murcia: DM.
Orihuela, J.L. y Santos M.L. (1999) Introducción al diseño digital. Madrid: Anaya Multimedia.
Marquès, P. (1999) Diseño, selección, uso y evaluación del multimedia didáctico.
Informática. Videojuegos. http://dewey.uab.es/pmarques/disdesa.htm
Marquès, P. (1999) Los espacios web multimedia: tipología, funciones, criterios de calidad.
http://dewey.uab.es/pmarques/tipoweb.htm
Prendes, Mª P. y Solano, I. Mª (2001) Taller de Multimedia. Presentado en el Congreso de
Oviedo del 2001. http://tecnologiaedu.us.es/bibliovir/pdf/paz11.pdf

AplicacionesMultimedia Interactivas: clasificación - 7


Tema 5. Análisis de motores de juegos
Introducción a los videojuegos llamado Spacewar! que funcionaba en ordenadores PDP-11.
Posteriormente el juego fue mejorado por otros compañeros
Todos tenemos una idea intuitiva más o menos clara de lo que es
de Stephen, distribuyéndose como software de dominio público a
un videojuego. Como puedes deducir, hoy en día un videojuego no deja
través de la ARPAnet, la antecesora de Internet.
de ser más que un tipo de aplicación más que puede ejecutarse en
un ordenador, dispositivo móvil o consola. Pero no fue hasta comienzos de los años 70 cuando se extendió el
uso de los videojuegos en las casas y en los salones de máquinas
Podemos definir un videojuego como un juego electrónico que
recreativas. Los responsables fueron Ralph Baer y Nolan Bushnell,
interactúa con uno o varios jugadores con el fin de que se consigan
el primero mediante el diseño y la comercialización de la primera
ciertos objetivos y que muestra visualmente una respuesta a sus
consola de videojuegos: la Magnavox Odyssey, y el segundo por la
acciones.
fundación de una de las empresas de videojuegos más influyentes
Un videojuego tiene que informar mediante algún mecanismo al de la historia: Atari. Esta compañía creó innumerables máquinas
jugador o jugadores del resultado de sus acciones, ya sea recreativas y la primera consola que fue un éxito de ventas: la Atari
visualmente, acústicamente, mediante vibraciones, etc. 2600.
Normalmente, esa realimentación se realiza mediante la proyección
A partir de ahí, todo fue historia.
de una imagen de vídeo en una pantalla, de ahí el nombre:
videojuegos.
Los videojuegos en la actualidad
Puede llamarte la atención que no hayamos incluido el requisito de Mucho ha cambiado en el campo desde los tiempos de esos primeros
que sea entretenido o divertido. Eso es correcto, aunque es deseable, videojuegos. Los de hoy en día pueden llegar a ser auténticas
un videojuego no tiene por qué ser divertido de jugar al igual que producciones multimillonarias que superan en presupuesto a las
ocurre, por ejemplo, al ver una película. Al cine se puede ir a pasar grandes películas de Hollywood e involucran a cientos de personas.
miedo (películas de terror), a ver historias tristes (dramas) o para
Hoy en día, la industria de los videojuegos está centrada en 3
estar en tensión constante (acción). ¿Quiere decir que esas películas
grandes plataformas: los ordenadores personales, las consolas y los
no son buenas? Nada más lejos: lo importante es que sea una
dispositivos móviles. Hace unos años tendríamos que haber
experiencia agradable, que te deje con ganas de repetir. Lo mismo
mencionado las máquinas recreativas, pero la democratización de
ocurre con los videojuegos, como veremos más adelante cuando
los ordenadores y las consolas han terminado por relegarlas
analicemos los géneros.
prácticamente al olvido.

Orígenes de los videojuegos. Por lo general, los saltos tecnológicos aparecen primero en los
Es difícil poner una fecha exacta al origen de los videojuegos, pero ordenadores ya que están en permanente evolución y sus
podemos comenzar en 1947. Fue en ese año cuando se registró una posibilidades de expansión permiten la inclusión de nuevos tipos de
patente en Estados Unidos acerca de un dispositivo de dispositivos. Eso sucedió es su momento con la introducción de las
entretenimiento que consistía en disparar a aviones en una pantalla tarjetas de sonido, los gráficos 3D o las tarjetas de aceleración de
de tubo de rayos catódicos (CRT). Thomas T. Goldsmith, Jr. y Estle cálculos físicos. Las novedades más demandadas pasan poco
Ray Mann aparecen como sus autores. Bien pudieron ser los padres después al mundo de las consolas. A su vez, los dispositivos móviles
del concepto de videojuego. son cada vez más potentes y van adquiriendo poco a poco las
características técnicas de los otros dispositivos antes mencionados.
El Nimrod, construido en 1951, se considera primer ordenador
diseñado exclusivamente para jugar al Nim, un juego donde jugador Programar para una de estas plataformas implica una serie de
y máquina se alternaban para retirar palitos de unos montones. ventajas y de inconvenientes respecto a las demás. Veamos algunos
aspectos interesantes:
Poco después, en 1952, Alexander S. Douglas programó una versión
de las tres en raya (Noughts and Crosses) que se ejecutaba en el
• Consolas: Son dispositivos diseñados exclusivamente para
ordenador EDSAC de la Universidad de Cambridge en 1952. No tuvo
ejecutar videojuegos. Su hardware evoluciona con cada
mucha repercusión dado que solamente podía ejecutarse en el
iteración (que en ellas se llama "generación"), lo que suele
ordenador de dicha universidad.
suceder cada varios años. Generalmente, las capacidades

William Higinbotham podría considerarse como el creador del de una consola en el momento del lanzamiento son

videojuego multijugador. Su "tenis para dos" permitía a dos punteras aunque permanecen constantes durante la vida

jugadores competir en el mismo juego. Se exhibió públicamente del producto. Eso implica que con el paso del tiempo se

durante unas jornadas de puertas abiertas celebradas en su quedarán obsoletas, dando paso a una nueva generación.

empresa. Aunque fue disfrutado por cientos de personas, no llegó al La gran ventaja desde el punto de vista de la

público adecuado y por tanto no condicionó la aparición de otros programación de videojuegos es que el hardware no varía

videojuegos. durante este tiempo lo que permite concentrar los


esfuerzos en una única plataforma. De cara al usuario
En 1961, un estudiante del MIT de nombre Stephen final, su uso es mucho más simple que el de un ordenador
Russell desarrolló un videojuego para dos jugadores personal. La generación actual de consolas a la fecha de
escribir este texto está formada por la Sony PlayStation de dos ordenadores personales pueden ser muy distintas
5, la Microsoft Xbox Series S/X y la Nintendo Switch. Sin y eso necesita una inversión extra de tiempo y dinero
olvidar la base instalada de Playstation 4 y Xbox One. durante el desarrollo.
• Ordenadores personales: Dado que son dispositivos de • Dispositivos móviles: Los últimos en llegar han sido los
propósito general, los ordenadores personales han sido dispositivos móviles. Si bien hace años que existen las
uno de las primeras plataformas en permitir la ejecución videoconsolas portátiles (Nintendo Gameboy, Atari
de juegos. Dado que continuamente están saliendo al Lynx, Sega GameGear, Nintendo DS, Sony PSP, etc.) el
mercado nuevos procesadores, tarjetas gráficas, concepto ha evolucionado para dar soporte a teléfonos
memorias, etc., se produce un crecimiento lento pero firme móviles y a otros dispositivos similares (por ejemplo,
en las capacidades de los ordenadores. Por ello, los equipos reproductores de música). Concretamente, la aparición de
de última generación casi siempre van a superar a las los smartphones con grandes capacidades gráficas y con
consolas en potencia y memoria ya que adaptan las un sistema operativo de grandes prestaciones
nuevas tecnologías con mayor velocidad. La cruz de la (iOS, Android, etc.) ha disparado el consumo de videojuegos
moneda es que los videojuegos tienen que soportar un en este tipo de dispositivos.
abanico muy amplio de hardware ya que la configuración
Características de las plataformas de desarrollo de videojuegos

Característica Evolución de la tecnolo- Diversidad del hardware para el Potencia de cálculo Memoria y capacidad de
gía mismo software almacenamiento

Ordenadores Continua. Alta. Alta. Alta.


personales.

Consolas. Se mantiene constante Baja. Se mantiene constante Media.


durante años. durante años.

Dispositivos Continua. Depende de la plataforma, suele Media, depende mucho Media.


móviles. ser alta. de la gama.

Clasificación de los videojuegos ejemplos podríamos nombrar: Double Dragon, Street

No existe una clasificación unificada de los géneros de videojuegos, Fighter, Mortal Kombat y Virtua Fighter.

ni siquiera los expertos se ponen de acuerdo. De hecho, lo normal es • Videojuegos de disparo en primera persona (FPS):
que un juego moderno sea una mezcla de más de uno. Muchos de Los FPS son juegos donde la pantalla representa lo que
estos géneros han surgido como respuesta a un juego innovador vería el personaje a través de sus ojos. En ellos, el
que en su momento sentó escuela y cuya idea fue repetida y/o personaje suele ir fuertemente armado y dispara a los
mejorada en desarrollos posteriores. A continuación, vamos a enemigos mientras recoge ítems desperdigados por el
describir algunos de los más importantes, resaltando algunos de los nivel. Los más conocidos fueron: Wolfenstein
juegos que dieron paso al género o bien supusieron su 3D, Doom, Duke Nukem 3D y Quake.
popularización: • Plataformas: Este tipo de juegos suele consistir en un
personaje, controlado por el jugador, que avanza por un
• Acción: Los videojuegos de acción son aquellos donde se nivel mediante movimientos rápidos y saltos. Los niveles
realizan movimientos y/o combates rápidos. Aquí suelen recorrerse en horizontal o vertical. Se podría
podemos incluir algunas de los géneros que veremos más considerar Super Mario Bros. como el iniciador del género
adelante como los juegos de lucha, simulaciones de al que siguieron otros éxitos como Bubble
combate, juegos de disparo en primera persona, etc. Como Bobble o Rainbow Island y sin olvidar a Sonic.
representantes de los primeros juegos de acción
• Estrategia: Son juegos donde la planificación de las
podríamos nombrar Pong, Asteroids y Space Invaders.
acciones suele tener bastante peso a la hora de obtener
• Aventura: Son aquellos que sumergen al jugador en una un victoria. Podemos clasificar los juegos en dos
historia que se va desarrollando conforme avanza en el subgéneros: estrategia en tiempo real y estrategia
juego. Suelen incluir puzzles y otros desafíos que se basada en turnos. La diferencia principal entre ambas es
superan con astucia e intuición. Las más antiguas no el tiempo de reacción ante los sucesos, ya que el juego no
tenían gráficos y funcionaban mediante descripciones del se detiene en el primer caso, obligando a realizar acciones
entorno en forma de texto. Entre ellas destacan Zork (en de forma rápida. Uno de los juegos que sentó las bases de
modo texto) y las series: King's Quest y Monkey Island. la estrategia en tiempo real fue The Anciert Art of War,
• Lucha: En ellos, el jugador pelea contra otros jugadores o aunque el género fue popularizado por Dune
bien contra personajes controlados por el videojuego. Eso 2, Warcraft y Command & Conquer. En el caso de la
incluye los juegos de lucha uno contra uno o los juegos estrategia por turnos Civilization fue el gran iniciador, al
cooperativos donde el jugador o jugadores se enfrentan a que siguieron otros títulos célebres como Master of
hordas de enemigos mientras avanzan por un nivel. Como Orion o X-Com.
• Puzzle: Estos juegos permiten al jugador aplicar Clasificación de los videojuegos (III)
estrategias y deducciones lógicas con el fin de conseguir • Minijuegos: Son muchos juegos en uno, generalmente de
ciertos objetivos. Suelen trabajar mucho con formas muy corta duración y con unas reglas simples. Pueden ser
geométricas y combinaciones de elementos y colores que parte de otro tipo de juego, ofreciéndose los minijuegos
pueden ser movidos, intercambiados o rotados. También como una recompensa o como un requisito para seguir
pueden incluir un tiempo limitado o bien introducir una avanzando. En otros casos, el juego en sí consiste en la
competición con otro jugador para darle más emoción. El superación de un conjunto de minijuegos (por
caso más claro de este tipo de juegos es Tetris. ejemplo, Mario Party).
• Simuladores: Como su nombre indica, intentan recrear un • Tradicionales: Los equivalentes electrónicos de los juegos
sistema del mundo real. Desde el funcionamiento de una de toda la vida. El juego Noughts and Crosses (OXO) puede
ciudad (SimCity), de un avión (Microsoft Flight Simulator), considerarse uno de los primeros juegos de este género,
de un negocio de transportes (Transport Tycoon) o de una pero aquí también se incluyen los juegos de cartas, de
nave espacial (X-Wing). mesa (ajedrez, damas), etc.

Clasificación de los videojuegos (II) • Educativos: Su misión es que el jugador aprenda mientras
se divierte. El aprendizaje puede producirse mediante la
• Juegos de rol (RPG): Es la versión electrónica de los juegos
necesidad de memorización de ciertos datos para
de rol tradicionales que se juegan con papel y dados. Los
conseguir cierto objetivo o bien mediante la observación y
jugadores tienen unas estadísticas (fuerza, sabiduría,
repetición de ciertas tareas o comportamientos. Algunas
dinero, etc.) que se van incrementando conforme aumenta
de las series más conocidas son la de Carmen Sandiego y
su experiencia. Además, pueden adquirir y usar objetos y
la saga The Incredible Machine.
armas que van recopilando durante el juego. Al contrario
• Serios: Los juegos serios son un género muy reciente.
que sus equivalentes en papel, aquí no es necesario echar
Están diseñados no solo para entretener sino para que al
a volar la imaginación para situarse en el juego; además,
jugar repetidamente se adquieran ideas que entran
las reglas están implícitas, lo que evitar tener que
dentro del ámbito de la ética, la realidad social o el trabajo
memorizarlas. Las series Ultima, Baldur's Gate o Final
en equipo.
Fantasy son algunos ejemplos de este tipo de videojuegos.
• Juegos de rol multijugador masivos (MMORPG): Son La industria del videojuego.
juegos de rol cuyo principal interés es que el desarrollo de El hecho de que un usuario final disfrute de un videojuego es el
la partida se produce en un escenario donde coinciden resultado del esfuerzo de muchos elementos que han de trabajar
cientos o miles de otros jugadores. En sus orígenes eran coordinados. Lo primero que podemos preguntarnos es ¿de dónde
juegos de texto y se llamaban MUD (Mazmorras sale la inversión inicial para pagar el desarrollo? Normalmente
multiusuario). Suelen ser juegos en los que se paga una procede de un editor o productor que adelanta parte del dinero que
suscripción mensual para acceder. Algunos ejemplos espera a recibir por las ventas del videojuego. Dado que es la entidad
representativos son: Ultima Online y World of Warcraft. que paga, el editor suele tener gran poder de decisión en el
• Carreras: El objetivo de estos juegos es llegar el primero desarrollo.
a la meta. En muchos de ellos se intenta recrear lo mejor
posible las sensaciones de conducir un vehículo en una El desarrollador (puede ser una empresa o un particular) es el

competición. El primero en convertirse en éxito de ventas encargado de diseñar e implementar el videojuego. Suele estar

fue Pole Position. especializado en unos determinados géneros y/o plataformas (por
ejemplo, juegos de rol multijugador en ordenadores personales).
• Deportivos: Estos videojuegos tratan de transmitir la
Respecto al desarrollador podemos distinguir tres casos según los
emoción de practicar un deporte real. Esto incluye desde
proyectos que desarrollen:
deportes individuales, a dobles o en equipo. El
juego Summer Games o las series PC Fútbol, Pro
• Un desarrollador interno es el que trabaja siempre para
Evolution Soccer y FIFA.
el mismo editor o fabricante de hardware.
• Musicales: El jugador tiene que seguir o acompañar el
• Un desarrollador de terceros firma contratos con los
ritmo dado por el juego ya sea mediante la pulsación de
editores para llevar a cabo un proyecto concreto en
botones, movimientos o entonación al cantar. El juego
particular. De hecho, la misma empresa desarrolladora
clásico es el Dance Dance Revolution, donde el jugador
puede tener a la vez proyectos con distintos editores.
tiene que saltar sobre unas flechas que hay en el suelo
• Un desarrollador independiente es un equipo pequeño,
siguiendo las indicaciones de la pantalla. Otros juegos que
puede que incluso unipersonal. Suele autopublicar sus
popularizaron el género son las series Singstar, Guitar
propios juegos y, al no estar atado a un editor, tienen
Hero y Rock Band.
plena libertad creativa. Eso permite que surjan ideas
innovadoras que nunca recibirían dinero de un editor que
las percibiría como una inversión arriesgada. La falta de
recursos económicos suelen suplirla con soluciones y
productos ingeniosos. A la hora de escribir estas líneas
hay un repunte de este tipo de equipos debido a que los
costes de distribución que existen en los dispositivos experimentado. Si el tamaño del equipo es grande, puede aparecer
móviles son muy bajos. el rol de director técnico ("Technical Director"), que tiene un papel
menos directo en la programación porque dedica tiempo a gestionar
La etapa de distribución es la encargada de hacer llegar el videojuego
el equipo; si existe, es él quien se comunica directamente con el
a las estanterías de las tiendas o a las tiendas de descargas
director de proyecto.
digitales. Es aquí donde se genera el beneficio para el desarrollador
y/o el editor. Hay que tener en cuenta que el distribuidor se lleva Motores de juegos
parte de esas ganancias.
Hoy en día en la gran mayoría de los videojuegos presentan tres
grandes componentes a grandes rasgos: el código específico del
juego, el motor de juegos y los recursos.

El código específico es donde se implementa la lógica de ese juego


es particular, es decir, el sitio donde se definen las reglas, el
comportamiento de los elementos del juego (por ejemplo, la pelota,
los enemigos, etc.), cómo se reacciona ante las posibles acciones del
jugador, las condiciones para ganar, etc.

El equipo de desarrollo
Si bien en los años 70 y 80 los videojuegos eran diseñados e EL MOTOR DE JUEGOS CONTIENE LA FUNCIONALIDAD SOBRE LA QUE SE APOYA
implementados por una persona o un grupo reducido, hoy en día lo EL CÓDIGO ESPECÍFICO PARA REALIZAR ESAS ACTIVIDADES. COMO VEREMOS
normal es que un proyecto se lleve a cabo por decenas de personas. MÁS ADELANTE, UN BUEN MOTOR DE JUEGOS ES INDEPENDIENTE DEL TIPO DE

Como se puede deducir, en ellos no solamente hay programadores JUEGO QUE SE ESTÁ IMPLEMENTANDO Y SÓLO INCLUYE CONCEPTOS GENÉRICOS

y artistas sino que también intervienen editores, productores, COMO LA CAPTURA DE EVENTOS DEL TECLADO/RATÓN, LA GESTIÓN DE

distribuidores, escritores, probadores, publicistas, gestores RECURSOS, LA VISUALIZACIÓN DE LOS OBJETOS , LAS COMUNICACIONES POR RED,

económicos, soporte técnico, etc. Tan solo tienes que fijarte en los LA REPRODUCCIÓN DE SONIDOS, ETC.

títulos de créditos de un videojuego moderno, ¡aparece tanta gente


El motor de juegos nos permite abstraernos del hardware del
como en una película!
equipo y el sistema operativo, aunque usan sus servicios para llevar
Cada persona que participa posee un área de especialización, aunque a cabo sus funciones. Esto es muy interesante, veamos la razón con
no siempre la aplica trabajando en lo más obvio. Por ejemplo, un un ejemplo: si nuestro motor de juegos soportara 3 clases de
programador puede estar echando una mano en gestión de calidad consola y 3 sistemas operativos distintos de ordenadores
o en soporte técnico ya que sus conocimientos benefician el personales, podemos tener un juego funcionando en 6 plataformas
funcionamiento de esa parte del proyecto. con muy poco esfuerzo adicional.

En el caso de la producción. podemos dividir al personal en otros El tercer componente, los recursos (o assets, en inglés), es el
grupos según la función que desempeñan: diseño general, compendio de los contenidos del juego. Entre esos contenidos
programación, diseño artístico, audio y gestión. En este módulo podemos encontrar música, efectos de sonido, modelos 3D, fondos
profesional nos centraremos en la parte de producción, y dentro de de pantalla, tipos de letra, niveles, etc. El motor de juegos cargará
ella en los equipos de diseño y programación, especialmente en esta los recursos según le indique el código específico. De la creación de
última. los contenidos suelen encargarse los diseñadores artísticos,
ingenieros de sonido, escritores, diseñadores de niveles, etc. aunque
La parte del equipo dedicada al diseño son las personas encargadas
su desarrollo puede estar supervisado por programadores.
de determinar la idea, la mecánica del juego, los diseñadores de
niveles o de misiones y los escritores de la historia y los diálogos. Entonces, si ya tengo la idea y quiero programar un juego, ¿por
Suelen incluir un diseñador principal (en inglés, "Lead Designer"), que dónde empiezo? ¿Qué componente es el primero sobre el que debo
es quien coordina a los demás. trabajar? Pues dado que la elección del motor de juegos determinará
cómo será el código específico y el formato de los recursos lo lógico
Por otro lado, y dado que el producto final es software, debe haber
es comenzar seleccionando un motor de juegos. Es una decisión que
programadores que se encarguen de la creación del código específico,
no debe tomarse a la ligera ya que cambiar el motor de juego a
del motor de juegos y de todas las demás herramientas
mitad de un proyecto puede tomar mucho tiempo.
relacionadas (editores de niveles, instaladores, etc.). Los equipos de
programadores suelen construirse alrededor del programador
principal (en inglés: "Lead Programmer"), que suele ser el más
En los siguientes apartados haremos una revisión de los distintos basarse en este tipo de motores de juego. A la hora de
componentes que tiene un motor de juego típico y veremos cuáles escribir estas líneas, los más reconocidos son id Tech 5, C4
son sus características más importantes. Engine, Gamestudio, Source
Engine, RAGE, SCUMM (específico para aventuras
Clasificación de motores de juegos. gráficas), Unreal Engine, IW y Torque Game Engine.
Un motor de juegos expone una interfaz de programación de
aplicaciones (API) que permite al código específico utilizar su Programación de un motor. APIs básicas
funcionalidad. Sin embargo, no todos los motores ofrecen las Cada motor soporta solamente determinados lenguajes de
mismas características. programación para el código específico del juego, así que debemos
elegirlo con cuidado. Es importante destacar que algunos motores
Según el nivel de abstracción cubierto por el motor de juegos,
usan un lenguaje propio para especificar el comportamiento del
podemos distinguir 3 tipos de complejidad creciente:
juego. El cómo se integra el código específico del juego con el motor
puede variar considerablemente de un motor a otro, ya que no todos
• Motores diseñados únicamente para facilitar la
los motores comparten la misma estructura interna ni filosofía de
representación en pantalla y el acceso al hardware en
programación. La mejor forma de empezar es estudiar los
general (audio, dispositivos de entrada, red, etc). De hecho,
tutoriales que suelen incluirse con el motor.
muchos de ellos son considerados como librerías más que
como motores de juegos. Algunos Un motor de juegos está especializado solamente en determinados
ejemplos: SDL, LWJGL o Allegro. aspectos, no todos incluyen las mismas características. De hecho, es
• Motores que, además de lo anterior, añaden un soporte posible combinar distintos motores de manera que cada uno aporte
total o parcial a la lógica del juego. Podemos parte de la funcionalidad necesaria para completar los requisitos del
destacar: JGame y Ogre3D. juego. En esos casos, el código específico se comunicará con ambos
• Motores que, además de todo lo anterior, incluyen motores para integrarlos.
plataformas para la creación, modificación o integración
Muchos motores de juegos se apoyan en librerías ya existentes que
de contenidos. Algunos de ellos: Blender Game
les proporcionan parte de su funcionalidad básica. Por
Engine, jMonkeyEngine 3 o XNA Game Studio, Unreal
ejemplo, OpenGL o DirectGraphics suelen utilizarse para mostrar
Engine, CryEngine o Unity.
objetos en 2 y 3 dimensiones, siendo estas librerías las que acceden
Otro aspecto a considerar es el del coste económico y de la licencia directamente a los controladores de la tarjeta gráfica. Esto permite
de uso. Para proyectos personales o no comerciales es posible al motor no estar atado a un determinado hardware. Más aún,
encontrar motores que no nos supongan ninguna inversión, lo que algunas de esas librerías están disponibles para múltiples
puede ser determinante. La licencia de uso de otros motores pueden plataformas (por ejemplo OpenGL) lo que facilita la portabilidad del
impedirnos vender el resultado final o limitar el número de unidades motor a múltiples sistemas.
vendidas antes de tener que pagar.
Las librerías no tienen por qué ser exclusivamente para acceder al
Teniendo todo esto en cuenta, podemos clasificar los motores en dos apartado gráfico: existen muchas otras librerías para otros usos.
grandes grupos según el tipo de licencia de uso: Por ejemplo, OpenAL o FMOD hace lo mismo con el audio
y OpenCL con las operaciones de computación intensiva.
• De código abierto: No es necesario pagar por su uso e
incluyen el código fuente del motor. Hay que prestar Ventajas de la utilización de motores.
atención al tipo de licencia concreto, pues algunas ¿Qué pasaría si no usáramos un motor de juegos? De hecho, los
restringen el uso del motor a la creación de videojuegos primeros videojuegos no los utilizaban. Cuando esto ocurre, el código
que también sea software libre (por ejemplo, la específico es el encargado de todas las tareas: comunicarse
licencia GPL). La documentación y el soporte suele ser directamente con el sistema operativo y el hardware, dibujar en la
llevado a cabo por la comunidad de usuarios. Algunos de pantalla, interceptar los dispositivos de entrada, conectarse con
los más conocidos son Ogre3D, Irrlicht, id Tech otras instancias a través de la red en el caso multijugador,
4 (publicado como código abierto en noviembre de implementar la "inteligencia" de los enemigos, simular la gravedad,
2011), jMonkeyEngine 3 y Crystal Space 3D. detectar cuando dos objetos colisionan, etc.
• Propietario: Son desarrollados por empresas que cobran
A priori, puede parecer una idea buena porque sólo se implementa
por la comercialización de juegos basados en sus motores.
aquello que se va a necesitar, con lo que el resultado será más
En algunos casos pueden ofrecerse de forma gratuita si
óptimo. Además, el código estará más integrado.
se cumplen determinadas condiciones. El coste final puede
depender de muchos factores: número de empleados, de Imaginemos que realizamos el proyecto así. ¿Qué problemas
copias vendidas o del dinero entregado por los editores podríamos encontrarnos? Analicemos algunos escenarios posibles:
para su realización. Suelen estar excelentemente
documentados y, por lo general, incorporan herramientas
adicionales que facilitan la gestión de los contenidos. Las
empresas ofrecen normalmente asistencia técnica de sus
productos. Por todo ello, las grandes producciones suelen
Escenarios Sin un motor de juegos Con un motor de juegos
El juego tiene tanto éxito que Tendremos que revisar todo el código del proyecto anterior y El código del proyecto ya está separado en código
nos encargan una continua- diferenciando lo que nos sigue sirviendo de lo que no. El motivo específico del juego y el motor (código genérico).
ción. es que no hay una separación clara entre lo genérico y lo es-
pecífico del juego.
Hay que migrar el juego a No nos queda más remedio que revisar todo el código del pro- En el momento que el motor de juegos soporte
una nueva plataforma. yecto anterior y analizar qué parte es dependiente de la pla- la nueva plataforma bastará con recompilar el
taforma antigua para sustituirlo por código nuevo. Por su- proyecto.
puesto, tendríamos que tener cuidado de no estropear nada.
Es posible que los recursos ya no sean válidos para la nueva
plataforma y haya que adaptarlos.
Además, si detectamos un error en el juego o mejoramos al-
guna herramienta habrá que cambiar el código por separado
en ambos proyectos.

Llega un nuevo programador Habrá que explicarle la estructura del código, que será distinta Bastará con enseñarle a usar el motor de juego,
a la empresa y queremos para cada proyecto en el que vaya a trabajar y plataforma que será el mismo en muchos proyectos. Luego
formarlo para trabajar en soportada. se le señalarán las particularidades de cada pro-
nuestros proyectos. yecto concreto en el que vaya a trabajar.
Queremos incorporar a Será necesario investigar cómo funciona la característica X, Seguramente haya un motor de juego que im-
nuestro juego el efecto grá- implementarla en todas las plataformas del proyecto y hacer plemente la característica X de serie en todas las
fico X de un juego de la com- las pruebas correspondientes antes de usarla. plataformas, con lo únicamente tendremos que
petencia. usarla.

Componentes de un motor de juegos geométricas (líneas, rectángulos, círculos, etc.) con lo que se evita

Como hemos visto antes, no todos los motores de juegos tienen las tener que implementar los algoritmos en el código específico.

mismas funcionalidades, aunque sí existen algunos aspectos que


Normalmente, un juego 2D consiste en una o varias capas de
son comunes entre muchos de ellos. Vamos a ver cuáles son los
imágenes en el fondo sobre las que se dibujan objetos 2D como
componentes más habituales que componen un motor de juegos:
pueden ser el jugador, los enemigos, monedas, obstáculos, disparos,
etc. A esos objetos que son representativos en el juego se les
• Motor gráfico 2D.
denomina sprites. El motor controla todos los que estén activos en
• Motor gráfico o de renderizado 3D.
un momento dado, almacenando la posición en la que están, si hay
• Detector de colisiones.
que dibujarlos rotados, si están o no animados, etc.
• Motor de físicas.
• Motor de inteligencia artificial (IA). Las animaciones en los juegos 2D se producen cambiando el dibujo
del sprite rápidamente de manera que haya pequeñas diferencias
• Motor de sonidos.
entre uno y otro, lo que produce en el cerebro una sensación de
• Gestor de comunicaciones en red.
continuidad. Observa las siguientes imágenes y verás como cada
En los siguientes apartados desarrollaremos en qué consiste cada dibujo está un poco más girado que el anterior: si se dibujara uno
uno de estos componentes y lo que aporta a un videojuego. tras de otro (de izquierda a derecha y de arriba a abajo) en la misma
posición el efecto que captarías es que estaría girando en el sentido
Motor gráfico 2D. de las agujas del reloj.
La palabra "vídeo" proviene del latín y significa "yo veo". Es por ello
Los motores gráficos 2D dan soporte a los sprites, ofreciendo
que los videojuegos tienen una componente visual tan importante y
herramientas para escalar, rotar y mover objetos por la pantalla
que la sucesión de imágenes es la forma principal de comunicación
con comodidad (antaño las transformaciones había que hacerlas
con el jugador. El número de imágenes por segundo que se crean es
por software y eran costosas). En la unidad de trabajo siguiente se
un número llamado framerate y se mide en cuadros por segundo
verán estos conceptos con más detenimiento. También la
(FPS). Para que el ojo no perciba parpadeo, el framerate debe ser
información que se superpone en la pantalla (como el número de
superior a 46 FPS.
vidas o la puntuación).
Antes de detallar las características de los motores gráficos
Cuando dos sprites se superponen el resultado final dependerá del
debemos introducir el concepto de 2D y 3D.
orden en que se dibujen. Dicho orden normalmente se
Los juegos 2D son aquellos en los que los dibujos tienen dos denomina plano o capa. Si el motor lo soporta, hay que elegir el plano
dimensiones, por ejemplo ancho y alto, pero no profundidad. Para de cada sprites cuidadosamente para que se produzca el efecto que
entendernos, son formas planas que se mueven por la pantalla. deseamos.
Podemos dibujarlas más grandes para parecer que están más cerca
En algunos motores gráficos podemos representar mundos que
y más pequeños para parecer que se alejan, pero siguen siendo
sean varias veces más grandes que el tamaño de la pantalla. El
planos. Un motor 2D permite dibujar de forma sencilla figuras
motor se encargará de mostrar solamente aquella parte que le
indique el código específico. Esto permite, por ejemplo, seguir al Motor gráfico o de renderizado 3D
jugador a lo largo y ancho de un nivel. Por otro lado, en los juegos 3D los objetos tienen tres dimensiones
y, por tanto, pueden moverse en el espacio con total libertad. Esto
Otro elemento gráfico que poseen muchos motores 2D es el
intenta imitar la visión del ser humano, que también es
de tilemap (celosía), que es una composición de imágenes pequeñas
tridimensional. El problema es que la gran mayoría de los monitores
del mismo tamaño. Este recurso es muy útil para dibujar fondos,
sólo son capaces de representar imágenes 2D. Para solucionarlo
mapas y niveles. Su origen viene en la necesidad de ahorrar
podemos realizar una operación llamada proyección, que
memoria y formas primitivas de acelerar determinadas rutinas por
transforma esa imagen 3D en una 2D. Ese proceso de generar las
hardware.
imágenes recibe el nombre de renderizado y es tarea del motor
Motor para videojuegos en 2D en Android gráfico.
Aunque Android incluye muchas características para poder
Normalmente, los objetos 3D están constituidos por vértices que
programar videojuegos sin necesidad de utilizar otras herramientas,
juntos forman polígonos, generalmente de tres o cuatro lados.
cierto es que para programar un juego con sólo estas herramientas
puede llevar muchísmo tiempo.

Para realizar videojuegos más avanzados y complejos en menos


tiempo existen los motores de videojuegos. Estos motores te
facilitan la generación de animaciones e interacciones complejas,
detectar colisiones entre las distintas animaciones, te evitan tener
que programar la física de un complejo movimiento, o el cálculo de
cómo las luces y las sombras afectan a los objetos de una escena.
En la figura se observa una esfera 3D. Los vértices son los puntos
Te ayudan con la inteligencia artificial que necesite un videojuego y
donde confluyen las líneas. La flecha roja indica el eje X, la verde el
te facilitan tareas como la de incluir sonidos o trabajar con redes.
eje Y y la azul el eje Z.
Hay todo tipo de motores diseñados para todo tipo de videojuegos,
fundamentalmente agrupádose en dos tipos, en dos dimensiones
(típico videojuego tradicional con pantalla plana), y tres dimensiones
donde se trata de dar una imagen mucho más real de la escena que
el videojuego plantea.

ANDROID 2D. COCOS2D-X


Cocos2d es un motor para videojuegos en 2d open source, comenzó
Los polígonos formados por los vértices se observan mejor en esta
siendo un motor para videojuegos en dos dimensiones para el
otra figura. Fíjate que algunos se ven en un color más claro que
sistema operativo IOS de los Iphone. Posteriorment apareció
otros. Todo ello es debido a la iluminación de la escena.
Cocos2d-X, evolucionada en paralelo a Cocos2d, que también permite
hacer videojuegos para otras plataformas.

Desde aquí surgieron numerosas versiones de Cocos2d, Cocos2d-X


incluye un lenguaje de programación en C++ desde donde se puede
dotar de acción a los objetos y animaciones creadas con Cocos2d.
Actualmente, incluye también un proyecto llamado Cocos2d-android
para crear juegos específicamente para Android en Java.

Con Cocos2d se pueden programar los mecanismos típicos de los Sobre los polígonos se pueden aplicar texturas para darle un
juegos 2d con muchísima facilidad, por ejemplo: aspecto más realista. Simplificando mucho, podemos considerarlas
como unas pegatinas que se pegan a su superficie. Las veremos con
• Reproducir efectos de sonidos.
más detalle en la unidad de trabajo de motores 3D.
• Incorporar fuentes de texto y realizar efectos.
• Tratamiento y animación de Sprites o pequeñas imágenes Un motor gráfico 3D simplificará el trabajo con los objetos
con fondo transparente para incluir un videojuego. tridimensionales permitiendo al código específico realizar

• Incorporar miles de efectos: rotar, reflejar, paralaje, operaciones con ellos independientemente del hardware sobre el

escalamiento, tintado, deslizamiento, saltar. que se esté ejecutando el juego. Entre estas operaciones está la de
crear objetos, moverlos, escalarlos, rotarlos, deformarlos, animarlos,
Pero sin duda, la gran ventaja de Cocos2d es que es open source y cambiarles las texturas, etc. Además, deberá soportar la creación de
el soporte de la comunidad es muy alto. En los enlaces siguientes te luces que iluminen dichos objetos y cámaras que permitan observar
mostramos distintos tutoriales y guías de programación, además el mundo renderizado desde la perspectiva que necesitemos en cada
de la descarga de Cocos2d. momento.

El conjunto de todos los objetos de este mundo virtual junto con las
cámaras, las luces y demás elementos forman la escena.
Internamente, la escena se suele almacenar mediante el Algunos detectores de colisiones clasifican los objetos en tipos de
denominado grafo de escena que se estudiará más adelante. manera que sólo comprueban ciertas combinaciones. Por ejemplo:
las colisiones de los objetos tipo "bala" con los tipo "enemigo" sí se
Motor para videojuegos en 3D en Android. Unity. comprueban, pero las colisiones de los objetos tipo "enemigo" con
Aunque existen numerosos motores para videojuegos en 3D en otros tipo "enemigo" no. Esto permite ahorrar mucho tiempo de
Android, el motor de videojuegos que más éxito tiene en el mercado cálculo.
es Unity que es un motor cross-platform (multiplataforma) para la
creación no solo de videojuegos en 3d sino de cualquier tipo de Si el caso de los objetos 2D no es sencillo de solucionar, con los

aplicación que contenga gráficos avanzados, incluidos gráficos en 2d. objetos 3D será más difícil aún porque los objetos tienen volumen

Unity, al contrario de Cocos2d, es propietario, no es Open Source. y no todo está en el mismo plano.

Unity es un motor de videojuego multiplataforma creado por Unity


Technologies. Unity está disponible como plataforma de desarrollo
para Microsoft Windows, OS X.

La empresa Unity Technologies fue fundada en 2004 por David


Helgason (CEO), Nicholas Francis (CCO), y Joachim Ante (CTO) en
Copenhague, Dinamarca, después de su primer juego, GooBall, que no
obtuvo éxito.

Unity puede usarse junto con 3ds Max, Maya, Softimage, Blender,
Modo, ZBrush, Cinema 4D, Cheetah3D, Adobe Photoshop, Adobe
Fireworks y Allegorithmic Substance.

Desde el 2016, la corporación cambió a un modelo de suscripción.


Unity tiene opciones de licencias gratuitas y de pago. La licencia
gratuita es para uso personal o para empresas más pequeñas que
generan menos de 100.000 dólares al año, y las suscripciones se
Motor de físicas.
basan en los ingresos generados por los juegos que utilizan Unity.
Muchos de los videojuegos intentan dotar a los objetos que aparecen
Detector de colisiones de un comportamiento creíble. Esto es, si empuja un objeto hacia el
borde una mesa, el jugador espera que el objeto caiga al suelo. Si se
Tanto en el motor gráfico 2D como en el 3D tenemos objetos que
golpea un cristal, esperamos que se haga añicos. O si lanzo golpeo
se mueven por la pantalla. Una de las acciones que realizaremos
una pelota, espero que siga una trayectoria natural y al llegar a la
más frecuentemente es comprobar si dos objetos colisionan entre
pared rebote de una forma natural.
sí. Por ejemplo: nos interesará saber si una bala ha chocado contra
un enemigo o si nuestro coche ha llegado a la meta. Ahí es donde Todos estos comportamientos son calculados por el motor de
entran los detectores de colisiones. físicas y pueden ir desde dotar de un efecto de gravedad al escenario
donde estamos jugando hasta el cálculo de la dirección que tomarán
¿Es realmente necesario que se encargue un motor? ¿No podemos
dos objetos al chocar, calcular por donde discurre un líquido
comprobarlo nosotros mismo desde el código específico de una
derramado, por donde se romperá un objeto al golpearlo o que al
forma sencilla? Resulta que detectar colisiones no es nada fácil,
tirar de una cuerda se mueva un objeto que está atado al otro
especialmente en ciertas circunstancias. En el caso 2D, dos objetos
extremo. De hecho, en entornos 3D suele ser el motor de físicas el
no han colisionado hasta que tienen un punto (píxel) en común. Eso
encargado de detectar las colisiones entre objetos. Para poder
implica tener que comprobar punto a punto de las dos imágenes
realizar todas estas actividades el motor asignará una serie de
buscando que ambas coincidan en algún sitio. Si tenemos en cuenta
propiedades físicas a cada objeto (velocidad, aceleración, masa, etc.)
que puede haber decenas de objetos en pantalla de forma
y aplicará las ecuaciones de la física newtoniana.
simultánea y que el objeto puede ser de gran tamaño, las cosas se
complican. Podríamos simplificar el proceso de detección de una Los cálculos de este tipo de motores requieren un uso intensivo del
colisión suponiendo que hay un círculo o una caja que envuelve cada procesador. Es por ello que hace unos años surgieron tarjetas
objeto y comprobar si dichas figuras geométricas se superponen: aceleradoras de física, que permitían dejar libre la CPU para otras
matemáticamente es sencillo de comprobar. El problema es que tareas. Su funcionamiento era similar a las GPU de las tarjetas
pueden darse falsos positivos de colisión, veamos un ejemplo. gráficas 3D pero su procesador (PPU) estaba dedicado solamente a
realizar cálculos de simulaciones físicas. Poco después se trasladó
En la parte izquierda de la imagen puedes comprobar como hay un
esa funcionalidad a las propias GPU de las tarjetas gráficas, ya que
falso positivo: las cajas de la izquierda están superpuestas pero los
éstas son capaces de realizar millones de cálculos por segundo en
objetos en realidad no se tocan. En la parte derecha sí lo hacen. Si
paralelo.
el motor hace bien su trabajo, cuando las figuras geométricas
colisionen realizará un estudio más cuidadoso en la zona Por tanto, es importante conocer que algunos motores de físicas
superpuesta para saber si efectivamente han chocado; de esta son capaces de descargar la CPU y realizar los cálculos en la GPU de
forma podemos solucionar los falsos positivos. la tarjeta gráfica o en la PPU de la tarjeta aceleradora de física.
Motor de inteligencia artificial (IA). Los motores de sonido pueden ser muy complejos: algunos tienen

Los jugadores esperan que los enemigos que aparezcan en el conexiones con el motor de físicas para cambiar las características

videojuego le supongan un desafío. De lo contrario, rápidamente se del sonido según a situación. Por ejemplo, podría producir

perdería el interés por jugar. Es ahí donde entra el motor de un efecto doppler (el que hace que sepamos si una ambulancia está

inteligencia artificial (IA) cuya misión es la de proveer a algunos de acercándose o alejándose simplemente por el sonido que emite).

los elementos del juego de un comportamiento que parezca ser También, otros pueden distorsionar el sonido para simular la

inteligente. Dada la complejidad de algunos de los problemas que acústica de una sala o de un corredor, dotando de más realismo al

soluciona el motor de IA y teniendo en cuenta que la toma de juego.

decisiones tiene que hacerse en muy poco tiempo, la mayoría de las


Por último, algunos motores permiten generar sonido posicional
veces se opta por usar técnicas simples y rápidas.
en 3D. Eso quiere decir que pueden localizar espacialmente el sonido

Por ejemplo, para encontrar de forma eficiente el trayecto que en aquellos sistemas que soporten audio multicanal (por ejemplo,

tendría que seguir un guardia de seguridad para llegar a un punto 5.1) o bien simularlo en la salida de auriculares. Si el motor de

en un mapa se usan algoritmos de búsqueda de caminos. El más sonidos está integrado con el motor gráfico 3D, una forma de

conocido, es el A*. conseguirlo es asociando el sonido a uno de los objetos de la escena;


el resultado se obtiene automáticamente.
En la figura, el algoritmo A* ha encontrado el camino óptimo (en
rojo) para llegar al cuadro azul desde el cuadro verde, teniendo en Gestor de conexiones en red.
cuenta que los cuadrados grises son obstáculos. El último componente de los motores que vamos a estudiar es el
gestor de conexiones en red. Este gestor nos permite que nuestro
Un recurso muy utilizado para simular comportamientos sería el
juego no esté aislado y pueda comunicarse con otros sistemas. El
uso de máquinas de estados finitos. Su aplicación permite que un
uso más obvio es la creación de juegos multijugador, pero no es el
objeto se comporte de forma distinta según la situación. Por
único.
ejemplo: un guardia puede estar vigilando una zona y, en caso de
detectar al jugador, dejaría inmediatamente esa tarea para proceder Otros escenarios que pueden resolverse con este componente son:
a perseguirlo. Si lo perdiera de vista, volvería a su tarea original. Al
personaje del juego que no está bajo control directo del ordenador • Subida de la puntuación de la partida a un servidor Web o

se le denomina NPC (personaje no jugador) y suele ser controlados a una red social.

por la IA. • Descarga de actualizaciones del juego.


• Implementación de salas para poder seleccionar
En algunos juegos el comportamiento inteligente se produce
contrincantes.
mediante la ejecución de código específico condicionado a ciertos
• Comunicaciones de voz y/o de texto entre varios jugadores.
eventos. No es extraño que ese código esté en lenguaje de
guiones (scripting) formando parte de los recursos; así no será Todo ello, como siempre, sin que el código específico tenga que saber
necesario recompilar todo el proyecto para hacer cambios en el nada de la infraestructura de red, el sistema operativo o la
comportamiento del juego. plataforma en general.

Cualquier técnica en el campo de la inteligencia artificial podría Muchos de los gestores de conexiones en red facilitan la creación de
aplicarse aquí: lógica difusa, redes neuronales, redes bayesianas, juegos multijugador permitiendo que el estado y las propiedades de
algoritmos genéticos, etc. Eso sí, hay que considerar que no deben los objetos (por ejemplo, su posición, su nivel de salud, etc.) se
ser muy costosas computacionalmente hablando para que el juego sincronice con todos las demás instancias del juego a través de un
no se ralentice. servidor. En estos casos es posible que el código específico no
necesite apenas cambios para adaptarse al entorno multijugador.
Motor de sonidos
Es el encargado de controlar la reproducción de música y efectos de La forma de implementar el multijugador varía de un motor a otro.
sonido de manera sincronizada con el resto del juego. El código Algunos usan la arquitectura cliente/servidor, de manera que todos
específico puede disponer de esta funcionalidad de forma las instancias de juego se conectan a un mismo servidor, que recibe
independiente al hardware de la plataforma. Esto quiere decir que los datos de cada cliente y los reenvía a los demás. Otros usan
si, por ejemplo, el hardware no soportara la reproducción de más de una arquitectura distribuida o P2P donde todos se comunican
un sonido a la vez, el motor podría realizar la mezcla él mismo para directamente con todos, aunque sólo suele usarse en redes de área
producir el mismo efecto sin tener que cambiar el código específico. local donde eso no supone una penalización de rendimiento.

Tanto la música como los efectos de sonido son parte de los La separación entre los roles de cliente y servidor es la más
recursos del juego. Ambos pueden incluirse como partitura (usando utilizada. De hecho, algunos juegos como Quake 2 o Quake 3, siguen
archivos MIDI) o bien de forma digitalizada, ya sea grabada este modelo incluso para el modo de un jugador, simplemente la
directamente del mundo real o modificada con algún editor de audio máquina se conecta consigo misma. El servidor guarda en memoria
como el de la figura. En los juegos modernos también pueden el estado de la partida (objetos activos, vida restante de cada
incluirse voces que se reproducirán de forma sincronizada con el jugador, ítems, munición restante, etc.) y los clientes sirven
motor de gráficos con el fin de dar la sensación de que el modelo solamente para enviar los movimientos o acciones (saltar, disparar,
está moviendo los labios. etc) y para mostrar por pantalla la proyección de lo indicado por el
servidor. El haberlo diseñado así permite extender soportar sin avanzados que pueden ser acelerados por el hardware si
apenas cambios los modos multijugador. éste lo soporta. La versión inicial fue programada por Loki
Software para facilitarse el trabajo de adaptar juegos
Librerías que dan soporte a los motores. de Windows a Linux.
Internamente los motores también se apoyan en otros
componentes. Por ejemplo: el sistema operativo, librerías de Otras librerías interesantes:

representación gráfica o librerías de reproducción de sonidos.


• OpenCL: Es una librería estándar de programación
Algunas de esas librerías son multiplataforma mientras que otras
paralela. Puede usarse para motores de físicas o para
sólo funcionan en ciertos sistemas operativos o un hardware en
motores de IA. Es gestionada por la misma gente
concreto.
de OpenGL y soporta aceleración hardware allí donde esté
Veamos algunas de las librerías más utilizadas respecto al aspecto disponible.
gráfico:
Y por último, librerías que ofrecen funcionalidad combinada:

• DirectGraphics (DirectDraw y Direct3D): Son librerías


• SDL (Simple DirectMedia Layer): Ofrece una interfaz de
de Microsoft que es encuentran disponibles en sus
programación multiplataforma muy sencilla para acceder
productos, como el sistema operativo Microsoft
a los dispositivos gráficos (2D) y de sonido. Además,
Windows y en su consola Xbox360. DirectDraw soporta
permite gestionar los dispositivos de entrada,
funciones para dibujar en 2D, mientras que Direct3D se
como joysticks, gamepads, teclados, pantallas táctiles y
usa para dibujar escenas 3D a partir de primitivas
ratones. Suele combinarse con OpenGL si se desea
sencillas. En otros sistemas operativos puede ser
funcionalidad 3D. Si no se necesitan otros motores
emuladas usando Wine, un entorno que permite ejecutar
específicos, SDL puede ser la base sobre la que construir
programas Windows. Las dos librerías forman parte de
un videojuego.
la colección de APIs DirectX. La versión 11 de DirectX está
• Allegro: Similar a SDL, aunque incorpora soporta 3D (sin
incorporada de serie en Windows 7 y Windows Server
aceleración, de forma limitada).
2008R2, aunque puede también instalarse en Windows
Vista y Windows Server 2008.
• OpenGL: Es la librería gráfica 2D y 3D más utilizada. Se
soporta en múltiples sistemas operativos y plataformas.
Está gestionada por el consorcio sin ánimo de
lucro Khronos Group y está en constante revisión. La
última versión a la hora de escribir estas líneas es la 4.2.
Una de las grandes ventajas de OpenGL es el soporte de
extensiones que permite ampliar la funcionalidad de la
librería o bien implementar funciones opcionales.
Estudio de juegos existentes
• OpenGL ES: Es una versión reducida de OpenGL dirigida a
Para acabar la unidad vamos a estudiar un juego existente.
dispositivos empotrados o con poca potencia. Suele ser la
Concretamente un juego GPL llamado SuperTuxKart.
librería utilizada en las plataformas móviles modernas
como los smartphones. Una aplicación que haga uso Tras jugar un rato a él y navegar por la página web (en inglés)
de OpenGL ES puede funcionar casi sin cambios en su hemos extraído información sobre los siguientes aspectos. Intenta
versión correspondiente de OpenGL estándar pero lo localizarlos por tu cuenta, te vendrá bien para realizar la tarea de la
contrario no suele ser cierto. unidad de trabajo.
• Vulkan: es una API multiplataforma para el desarrollo de
aplicaciones con gráficos 3D. Su principal característica es • Género:

que puede aprovechar la cantidad de núcleos presentes en o Carreras: El juego consiste en una competición

el procesador principal y reducir el uso de este para las de karts.

mismas tereas que librerías anteriores.


o Acción: Hay que tomar decisiones rápidas, como
frenar con anticipación, usar armamento que
Respecto al audio: cogemos en cajas, etc.
o Lucha: Podemos usar las armas para atacar al
• DirectSound y DirectSound 3D: También forman parte
resto de jugadores y enlentecer su avance.
de DirectX. La primera de ellas, especializada en
• Plataformas:
audio 2D fue dividida posteriormente en dos librerías
o Ordenadores: Windows, Linux, FreeBSD, NetBS
(XAudio2 y XACT3). Presentan el mismo problema
D, Solaris y MacOS X.
que DirectGraphics ya que sólo funcionan
o Consolas: Ninguna.
en software o hardware de Microsoft.
o Dispositivos móviles: Ninguno.
• OpenAL: Es la equivalente de OpenGL en audio, es decir,
• Librerías/motores del juego:
tiene soporte multiplataforma, integrándose muy bien
o Motor de gráficos: Irrlicht.
con la librería gráfica. Soporta efectos especiales
o Librería 3D: OpenGL.
o Librería de audio: OpenAL. Estudiar algunos casos concretos en detalle puede darnos mucha
• Soporte multijugador información. Busca juegos que sean software libre y que usen el
o Sí, hasta 4 jugadores en el mismo ordenador. No motor de juegos que vayas a utilizar para analizar su código fuente.
soporta juego en red.
NOSOTROS, TE ADELANTO, UTILIZAREMOS JGAME PARA LA
• Inteligencia artificial
PARTE 2D Y JMONKEYENGINE 3.0 PARA LA PARTE 3D, AUNQUE EXISTEN
o Sí, para los participantes no humanos que
OTROS POSIBLES PARA LA CREACIÓN DE VIDEOJUEGOS EN ANDROID QUE
compiten con el jugador.
PROPORCIONAN MECANISMOS PARA REALIZAR ANIMACIÓN Y TRATAR GRÁFICOS

TANTO EN 2D COMO EN 3D Y ENTRE LOS MÁS IMPORTANTES


DESTACAN: CANVAS EN ANDROID, ANIMADORES (ANIMATORS), DRAWABLE
La mejor forma de iniciarse en el mundo del desarrollo de ANIMATION, LOS DIBUJABLES (DRAWABLES), OPEN GL COMO LIBRERÍA DE
videojuegos es jugando e investigando. FUNCIONES DE ALTO RENDIMIENTO PARA EL TRATAMIENTO DE GRÁFICOS 3D.
Tema 6. Desarrollo de juegos en 2D
Instalando el motor de juegos en 2D (en la última versión, a septiembre de 2016, es la versión 2.9.2
de LWJGL son Windows, MacOS X, Solaris y Linux). También nos
Existe una gran cantidad de motores 2D, algunos de los cuáles son
ofrece la posibilidad de crear un applet Java, que puede ser
de código abierto, otros simplemente gratuitos y, cómo no, también
incrustado en una página web.
de licencia comercial. Es posible encontrar motores que incorporan
sus propios lenguajes de programación y editores de recursos La estructura de un videojuego genérico que utiliza Slick es la
integrados en un entorno de desarrollo. Otros son simplemente siguiente:
librerías que se pueden usar en los entornos de desarrollo que
nosotros queramos. No menos importante es tener en cuenta
la cantidad y fecha de sus últimas actualizaciones, pues muchos
motores llevan meses o años abandonados a su suerte.

Como imaginarás, la elección de un motor depende aún de muchos


otros factores:

• Lenguaje de programación a usar.


• Plataformas a las que queremos destinar el producto
A pesar de ser un motor 2D, Slick hace un uso intensivo de
final.
la GPU de la tarjeta gráfica para realizar todas las operaciones
• Características técnicas que nos ofrece, etc. relacionadas con el dibujo de formas. De igual forma, soporta
aceleración hardware para la reproducción de audio si el sistema
En nuestro caso vamos a continuar en la línea de usar Java como
operativo y el hardware lo soporta.
lenguaje de programación y utilizaremos como entorno de
desarrollo el ya conocido NetBeans, aunque complementado con Una vez finalizado el proyecto, podremos generar dos tipos de
otras herramientas externas que nos pueden facilitar el trabajo distribuciones: una basada en ficheros (una carpeta que contiene
(editores de mapas, etc.) todo el código/assets y que puede ejecutarse manualmente) y otra
basa en Web Start, que permite colocar un enlace en una
Es conveniente recordar que los conceptos básicos son similares en
página web que, al hacer clic sobre él, hará una copia local y
la gran mayoría de motores, así que lo que aprendamos a lo largo
ejecutará el proyecto automáticamente.
de esta unidad será aplicable a otros, independientemente del
lenguaje de programación o de las herramientas que tenga
Preparando Slick.
disponibles.
La versión de LWJGL, que viene incluida por defecto en la
Con el fin de aprender todos estos conceptos usaremos Slick, un distribución de Slick, suele ser una versión obsoleta. Para evitar
motor de juegos 2D en forma de librería Java, que es de código problemas de compatibilidad con hardware y sistemas operativos
abierto y que está, a fechas de escribir este texto, mantenido recientes es altamente recomendable descargar aparte el último
activamente. Para poder ofrecer un buen rendimiento paquete disponible de LWJGL.
independientemente de la plataforma utilizada, Slick se basa en los
Descomprime Slick y LWJGL, cada uno en una carpeta. Deberías ver
servicios ofrecidos por LWJGL, LightWeight Java Game Library,
una estructura similar a esta:
una solución que permite la utilización nativa
de OpenGL (gráficos), OpenAL (audio) y OpenCL (computación
intensiva) así como la gestión de dispositivos de entrada
(joysticks, gamepads, etc).

No lo cubrimos en esta unidad, pero es conveniente que sepas que


Slick también soporta desarrollos para dispositivos móviles que
usan la plataforma Android. Además, existe una librería de motor
de físicas muy sencilla de utilizar y que se integra perfectamente
con Slick.

¿Listo para comenzar la aventura de construir tu propio videojuego?

Slick.
El motor de juegos 2D que utilizaremos para aprender los conceptos
es Slick. Se apoya fundamentalmente en la librería LWJGL, la cual
De momento, la carpeta que nos interesa de Slick es la carpeta lib.
incluye código nativo que se ejecuta fuera de la máquina virtual
En ella están muchos de los ficheros que hay que adjuntar al
de Java. El uso de este componente dota al motor de gran rapidez.
proyecto para usar el motor.
Los proyectos que desarrollemos con Slick podrán ser ejecutados en
aquellas plataformas para las que exista el soporte de código nativo
"Add Library...". Para terminar, elige Slick en la lista y haz clic en "Add
Library". Acepta los cambios con "OK".

Pronto aprenderemos para qué sirven la mayoría de ellos. Ahora, lo


que vamos a hacer es actualizar los paquetes de LWJGL obsoletos:

1. Borra de la carpeta lib los


ficheros jinput.jar, lwjgl.jar y natives-*.jar
2. Ahora copia en lib los siguiente ficheros que están en la
carpeta jar de LWJGL: jinput.jar y lwjgl.jar
3. Por último, copia el fichero lwjgl_util_applet.jar en la Ya casi hemos acabado: Abre un explorador de archivos y ve a la
carpeta applet de Slick, reemplazando el que existe allí. carpeta del proyecto recién creado. Haz una nueva carpeta
llamada data; será aquí donde incluiremos los assets (gráficos,
¡Hecho! La actualización debería funcionar sin problemas con
música, mapas, sonidos, etc.) del proyecto. Para terminar, copia en la
cualquier versión 2.x.x de LWJGL. Si vas a instalar una versión
carpeta del proyecto la carpeta native de LWJGL junto con su
posterior ten en cuenta que podrías encontrar incompatibilidades
contenido. Esto será necesario para que, cuando ejecutemos el
con Slick.
proyecto, se encuentren las librerías nativas correspondientes a tu
No pierdas de vista la carpeta native de LWJGL, pues si un proyecto sistema operativo.
hace uso de Slick, necesitaremos colocar una copia suya dentro.
El contenido de nuestro proyecto debe quedar más o menos así:
Creación de un proyecto Slick en NetBeans.
En cada proyecto en el que vayamos a usar Slick hay que incluir
ciertas librerías y rutas a carpetas para que el IDE pueda compilar
las llamadas correctamente, indicar dónde se producen los errores
y mostrar la documentación Javadoc.

Dado que es posible que tengamos que crear más de un proyecto a


lo largo del tiempo, vamos a aprovechar una de las posibilidades
de NetBeans: la configuración de librerías. Una vez hecho esto, para
usar Slick en nuestro proyecto bastará con añadir la librería al
proyecto. Esqueleto de una aplicación Slick (I). La interfaz Game.
Las aplicaciones Slick son objetos que implementan la
Esto solamente tendrás que hacerlo una vez ya que los datos de la
interfaz org.newdawn.slick.Game. Como veremos más adelante,
librería se almacenan en la configuración de NetBeans.
normalmente no trabajaremos directamente con esta interfaz; en
Con la librería creada en NetBeans es muy sencillo usar Slick. Crea su lugar, usaremos algunas clases predefinidas que realizan
un nuevo proyecto "Java Application". Elige la carpeta donde deseas algunas funciones adicionales por nosotros.
guardarlo y, como nombre, escribe ProyectoSlick. Lógicamente, en
La interfaz presenta tan sólo cinco métodos:
tus propios proyectos podrás poner el nombre que consideres
conveniente, pero para seguir los ejemplos es mejor que uses éste, Método Descripción
por si quieres sustituir parte del código fuente más adelante. Deja boolean closeRequested() Devuelve true si se solicita la finaliza-
el resto de las opciones en sus valores por defecto y haz clic en ción de la ejecución del programa.
"Finish". java.lang.String getTitle() Devuelve el título del juego.
void init(GameContainer con- Inicializa el juego.
Vamos a incorporar la librería que hemos configurado. Haz clic con tainer)
el botón secundario sobre el proyecto y elige "Properties" para void update(GameContainer Implementa la lógica del juego.
modificar sus propiedades. En la nueva ventana, elige "Libraries" en container, int delta)
el panel de la izquierda, luego la pestaña "Compile" y pulsa el botón void render(GameContainer Dibuja los elementos del juego en la
container, Graphics g) pantalla.
Los dos primeros métodos son sencillos de Existen dos subclases de GameContainer que tenemos que
entender: closeRequested() es el mecanismo que tenemos para considerar, según el lugar donde queremos que nuestro juego se
indicar al motor de juegos que queremos terminar la ejecución del ejecute: AppGameContainer y AppletGameContainer. El primero
juego. Será llamado periódicamente. getTile(), por otra parte, nos permite crear aplicaciones independientes, mientras que el segundo
permite definir el título del juego que, generalmente, aparecerá en permite incrustar el juego en una página web como un applet Java.
el título de la ventana o en la barra de tareas del sistema operativo.
AppGameContainer tiene dos constructores, uno al que
simplemente tenemos que pasar como parámetro el objeto que
implementa la interfaz Game y un segundo más completo que
permite establecer la altura y anchura de la ventana y activar (o
no) el modo de pantalla completa. AppletGameContainer sólo tiene
el primero ya que las dimensiones de la ventana vienen dadas por
el código HTML de la página web.

Dado que el juego se puede mostrar en una ventana, a pantalla


completa o en una sección de una web, a partir de ahora, cuando
queramos referirnos a la región rectangular donde el usuario
visualiza la ejecución, hablaremos simplemente de contenedor.

Esqueleto de una aplicación Slick (II). Código inicial.


Pega este código en el proyecto vacío que creamos antes. Todos los
cambios respectos al código fuente del proyecto original están
marcados:

Ejecútalo y, si has seguido la instrucciones correctamente, deberías


ver una ventana de fondo negro con un contador de fotogramas por
segundo en la esquina superior izquierda.

El método init() será llamado por el motor al arrancar el juego,


antes de empezar con el game loop, o bucle de juego. Es aquí donde
se cargan los assets iniciales (gráficos, música, mapas, sonidos, etc.)
y se establece el valor inicial de las variables que necesitemos para
mantener el estado de nuestro juego.

A partir de este punto, nuestro juego entra en el game loop que


hemos mencionado antes. El game loop es un bucle que ejecuta
cíclicamente una secuencia de acciones. La ejecución continuada de
esa secuencia hace que el mundo que simulamos en el juego tome
vida, reaccione a las acciones del jugador y se muestre en pantalla.
En Slick el game loop es muy simple pues se llama continuamente
a los dos métodos que nos quedan por explicar.

El primero que es llamado es update(). Es aquí donde se lee el


teclado y los controladores, se actualiza la posición de los personajes,
se detectan colisiones, se toman decisiones acerca de la inteligencia
artificial, etc. Al conjunto de estos procesos se les denomina game
logic o lógica de juego. No se debe dibujar nada en pantalla en esta
fase del game loop.

Por último, el método render() será el encargado de dibujar lo que


debe ver el jugador en la pantalla. Como veremos a lo largo de la
unidad, es muy sencillo colocar imágenes, mapas y otras formas
geométricas.
Observa el código con atención y comprobarás que hemos extendido
Todo este flujo de llamadas está supervisado por una instancia de de la clase BasicGame en lugar de implementar la interfaz Game.
la clase GameContainer que se crea nada más arrancar el juego. La Como ya comentamos antes, lo normal es usar alguna clase
instancia controlará el game loop, la salida por pantalla, la gestión predefinida que implemente internamente la interfaz porque
de la temporización (fotogramas por segundo, tiempo transcurrido incluyen funcionalidad incorporada que puede hacernos la vida más
entre llamadas, etc.) y los dispositivos de entrada como son el sencilla. En este caso, BasicGame proporciona el código necesario
teclado, el ratón, los gamepads, etc. para añadir un título cuando se crea el objeto, acabar pacíficamente
con la aplicación cuando se cierra la ventana y también algunos
métodos para controlar los dispositivos de entrada.

Analizando el esqueleto de la aplicación (I). Inicialización del


contenedor.
Echemos un vistazo al código que acabamos de incluir en el proyecto:
Ese trozo de código, que envuelve varias llamadas, captura la
excepción SlickException, que es la única que puede ser lanzada por
el motor de juegos.

La clase BasicGame requiere le informemos a través del


constructor del título del juego para que pueda implementar el
método getTitle() de la interfaz Game. Por tanto, se lo comunicamos
Esta línea crea el contenedor, en este caso un AppGameContainer,
mediante una llamada super.
que gestionará la ejecución del juego. Como único parámetro le
pasaremos la referencia a un objeto que implemente la
interfaz Game. En nuestro proyecto, esa referencia se
corresponderá con la clase TutoriaSlick que extiende
a BasicGame (recuerda que BasicGame implementa la interfaz, por
tanto la clase principal también lo hará).
La clase BasicGame requiere le informemos a través del
constructor del título del juego para que pueda implementar el Analizando el esqueleto de la aplicación (II). Inicialización
método getTitle() de la interfaz Game. Por tanto, se lo comunicamos gráfica.
mediante una llamada super.

Con esta orden estamos indicando, a la librería LWJGL, en qué lugar


debe buscar las librerías nativas para el sistema operativo actual.
Aquí hemos solicitado al contenedor la creación de una ventana de
¿Recuerdas que copiamos la carpeta native dentro del proyecto?
800x600 píxeles. Si a setDisplayMode hubiéramos
Pues bien, fíjate que estamos almacenando en la
pasado true como último parámetro, la aplicación intentaría correr
propiedad org.lwjgl.librarypath la cadena
a pantalla completa en lugar de en una ventana.
"native/%nombre_de_la_plataforma%", donde el nombre de la
plataforma será linux, macosx, solaris o windows. Si no tuviéramos
este recurso tendríamos que añadir un parámetro en tiempo de
ejecución -Djava.library.path que apuntara a todas las carpetas
nativas. Esta solución es mucho más elegante.

Estas dos llamadas establecen otros parámetros gráficos del juego.


La primera indica que nuestra intención es que el motor genere
60 frames por segundo (FPS), esto es, que se dibuje el contenedor
60 veces en un segundo llamando al método render(). Si el juego es
complejo, no siempre será posible alcanzarlos.

La segunda línea pide al motor que sólo realice el volcado del


renderizado en la memoria de la tarjeta gráfica durante el
sincronismo vertical (V-Sync). El no activar el sincronismo puede
provocar que la imagen cambie mientras se está mostrando por el
monitor, lo que provoca a veces defectos momentáneos como cortes
o discontinuidades (el término en inglés es tearing). La restricción
hace que el rendimiento del juego baje: es decisión del programador
si activarlo o no. Observa las discontinuidades producidas por
Aquí hacemos lo mismo con la librería JInput, que está incluida el tearing en un fotograma congelado:
en LWJGL pero está basada en un código fuente distinto. Esta
librería es la que proporciona acceso directo y nativo a los
dispositivos de entrada tales como joysticks, gamepads, etc.
Aprovecharemos el hecho de que las librerías nativas de JInput se
encuentran en las mismas carpetas que las de LWJGL y
asignaremos directamente la ruta anterior.
el personaje reaccione con algunos elementos, como el borde del
mapa u obstáculos que encuentre por el camino.

Todo ello se verá de una forma muy práctica, así que deberías seguir
todas las explicaciones en un ordenador que tenga instalado
el IDE NetBeans y el motor Slick tal y como se explicó en el apartado
anterior.

¿Preparado? ¿Listo? ¡Ya!

Tilemaps (mapas de baldosas)


El truco es que no se almacenan las pantallas como un gráfico
normal sino como una composición de trozos predefinidos que se
colocan cuidadosamente unos junto a otros para dar sensación de
que forman un todo continuo. En realidad, lo que se almacena en
La última llamada indica al contenedor que queremos comenzar a memoria son las referencias de cada trozo y esas referencias
ejecutar el game loop. pueden ocupar cientos o miles de veces menos que el trozo en sí
mismo. Vaya ahorro, ¿no?
Los métodos init(), update() y render() y sus parámetros se
explicarán más adelante cuando empecemos a incluir funcionalidad Observa la siguiente imagen que contiene trozos de una vía del tren
a nuestro juego. y otros elementos decorativos:

Sistema de coordenadas.

Está compuesta de trozos de 32x32 píxeles, cada uno de los cuáles


se denomina baldosa, aunque el término más utilizado cuando
hablamos de videojuegos es su traducción al inglés: tile. Puedes ver
los tiles separados aquí:

Para que podamos informar al motor de la posición de los objetos es


necesario que tanto el motor como el código específico use el mismo
criterio. En este caso, el criterio se denomina sistema de
coordenadas y es, ni más ni menos, la forma de expresar un lugar Como ves, los tiles son dibujos normalmente cuadrados que suelen
en un plano 2D. Como tiene dos dimensiones, cualquier punto puede ir almacenados todos juntos en una única imagen. Esas imágenes
indicarse con un par de números que denominaremos coordenadas. se llaman tilesets, o conjunto de baldosas.

Slick usa un sistema de coordenadas clásico (X,Y) donde la esquina


superior izquierda del contenedor es la posición (0,0). El primer
número expresa la componente horizontal, mientras que el segundo
es la componente vertical. Los valores negativos serán a la izquierda
y hacia arriba respecto a dicha esquina, mientras que los positivos
serán hacia la derecha y hacia abajo.

Elementos de un proyecto 2D
A lo largo de este apartado echaremos un vistazo a los elementos
típicos de un juego 2D y cómo se pueden utilizar con Slick.
Partiremos de un proyecto vacío al que iremos incorporando código
y recursos.

Inicialmente nuestro juego será un simple mapa al que


posteriormente añadiremos un personaje que se puede mover por Normalmente, los tilesets son procesados directamente por el

él. Para darle más realismo, haremos que dicho personaje realice motor 2D de manera que se "trocean" internamente para poder

una animación mientras se desplaza. Por último, conseguiremos que dibujar los tiles independientemente. Parte del dibujo de
un tile puede ser transparente, lo cual será útil cuando vayamos a
superponerlo encima de otro tile para hacer efectos visuales.

Para crear mapas o pantallas complejas se suele usar un editor de


mapas de baldosas (editor de S) que permite usar este tipo de
imágenes y colocar unos con otros los elementos de forma sencilla,
incluso superponiendo unos encima de otros mediante capas. Un
Dado que es en init() donde deben cargarse los assets del videojuego,
ejemplo de este tipo de programas es Tiled, aunque algunos motores
será ahí donde indicaremos a Slick que asocie el tilemap a dicho
incorporan un editor propio. Aprenderemos a usarlo en el próximo
objeto:
apartado.

Creando tilemaps con Tiled


Tiled es un editor de tilemaps, es software libre y muy versátil. Es
multiplataforma, existiendo versiones
para Windows, Linux y MacOS X .
El constructor que hemos usado tiene dos parámetros: una ruta
Crear un tilemap con Tiled es similar a componer un collage. que indica dónde está el tilemap y otra carpeta que indica en qué
Podemos crear tantas capas de tiles como necesitemos. Las capas lugar está el tileset asociado. Lógicamente, tanto el tilemap como
se dibujarán en orden, pudiendo mezclar unos dibujos con otros el tileset deben copiarse a la carpeta data del proyecto. Aprovecha
hasta conseguir el efecto deseado. para copiar tanto el mapa como el tileset a la carpeta data de tu
proyecto.
Por ejemplo: podemos dibujar los tiles que representan el suelo en
la capa inferior, los árboles, paredes y demás objetos que podamos Reflexiona
encontrar en otra capa y los tejados en la última. Dado que luego,
¿Recuerdas que antes te pedimos que grabaras el mapa en la
desde el motor, podemos ocultar y mostrar las capas mediante
misma carpeta que el tileset? Eso evita problemas ya que la ruta
código, podríamos ocultar el tejado cuando nuestro personaje se
hasta el tileset se almacena en el fichero del mapa como una ruta
introduzca en una casa y volverlo a mostrar cuando salga.
relativa a él: poniendo todo en la misma carpeta siempre lo
Tiled nos facilita la importación de tilesets y la edición de los mapas encontrará.
mediante múltiples herramientas: relleno, copiado y pegado de
El siguiente paso es mostrar el mapa, o usando el término técnico:
grupos de tiles, etc. Otra de sus funciones destacables es que
renderizarlo. Esto se hace en el método.:
permite añadir atributos a los distintos elementos del mapa. Por
ejemplo, si cierto tipo de tile impide el paso del jugador, podemos
marcarlo con un atributo. Dichos atributos pueden ser leídos luego
desde el motor de una forma sencilla.

El método render() toma dos parámetros: el contenedor donde está


ejecutándose el juego y un objeto de clase Graphics. El último objeto
permite realizar operaciones gráficas aceleradas por LWJGL, tales
como dibujado, traslaciones, rotaciones, rellenado de formas,
recortes, gradientes etc. Consulta la documentación para más
información.

Pues bien, una única línea es lo que necesita Slick para dibujar el
mapa en la esquina superior izquierda del contenedor del juego.
Ejecuta el proyecto y observa el resultado:
Dibujando tilemaps en Slick.
Si no lo tienes listo aún, crea un nuevo proyecto en NetBeans y
prepáralo para usar Slick tal y como vimos algunas secciones antes.

Como recordarás, derivaremos la clase principal de una


clase BasicGame. Esta clase tiene 3 métodos que son llamados en
distintos momentos de la ejecución del
juego: init(), update() y render().

Empezaremos por declarar una variable que luego hará referencia


al tilemap que acabamos de crear. La declararemos dentro de la
clase Tutorial:
¡Esto va tomando forma! comprobamos que una bala ha chocado contra un enemigo, etc. En
nuestro caso aprovecharemos para cambiar los valores de
Pero hay un problema: el mapa no se mueve. En el siguiente
desplazamiento X e Y de g.translate() según lo que nos indique el
apartado vamos a añadir el código que haría falta para poder
jugador por el teclado.
desplazarnos por el mapa.
Otro método relacionado que te puede resultar de utilidad alguna
Desplazando el tilemap (I). Traslaciones. vez es .rotate(x, y, angulo), también de la clase Graphics. Los dos
Vamos a darle un poco de movimiento al mapa. Lo haremos usando primeros parámetros especifican las coordenadas del centro de
el método g.translate() cuyo efecto es desplazar según las rotación y el tercero es el ángulo en grados. A partir de la llamada,
coordenadas que le aportemos todo lo que dibujemos tras ejecutarlo. todo lo que se dibuje se hará rotado según esos datos.

El método g.translate(X,Y)tiene dos parámetros que se Desplazando el tilemap (II). Respondiendo al teclado.
corresponden con el desplazamiento horizontal (X) y vertical (Y) que
Ya tenemos un mecanismo para desplazar el mapa, pero para que
queramos aplicar. Dado que la ausencia de desplazamiento se
sea útil tendremos que crear un par de variables que almacenen el
corresponde (0,0):
valor actual de dicho desplazamiento. Dichos valores variarán según
las teclas que se pulsen.
• Cualquier valor negativo en el desplazamiento X hará que
los dibujos se dibujen más a la izquierda.
• Cualquier valor positivo en el desplazamiento X hará que
los dibujos se dibujen más a la derecha.
• Cualquier valor negativo en el desplazamiento Y hará que
los dibujos se dibujen más hacia arriba.
• Cualquier valor positivo en el desplazamiento Y hará que
los dibujos se dibujen más hacia abajo.

Los dibujos se han desplazado 200 píxeles a la izquierda y 300


píxeles hacia arriba por lo que la sensación es que nos hemos
movido hacia la derecha y hacia abajo... ¿lo ves? (compara con el
dibujo de la sección anterior y fíjate en algún punto de referencia,
como los lingotes de oro).

¡Ya podemos darnos una vuelta por el mapa! Dentro del


método update(), el que gestiona el bucle de juego, lo primero que
hacemos es crear un objeto Input que nos permite tener acceso al
teclado, al ratón y a los controladores de juego que tengamos
conectados (gamepads, joysticks, etc.). De momento, nos
conformamos con usar el método entrada.isKeyDown() para saber
si cierta tecla está pulsada o no y actuar en consecuencia. El
parámetro delta es el número de milisegundos que han transcurrido
desde la última llamada al método.
Es más simple de lo que parece, veámoslo con un ejemplo: añadimos
una línea al método render() En caso de querer usar otros dispositivos de entrada podemos usar
la funcionalidad que nos aporta Slick mediante la
Pero lo que queremos es movernos libremente por el mapa. Para
librería jinput.jar. Se soportan múltiples controladores simultáneos.
ello tendremos que utilizar el último método que aún no hemos
Llamando a métodos del
tocado: update()
tipo getControllerCount(), isButtonPressed(int boton, int
Dentro de update() es donde se realizan los cambios internos del controlador), isControllerLeft(int controlador) o getAxisValue(int
juego: leemos el teclado, cambiamos la posición del jugador, controlador, int eje) se pueden tener un control total sobre ellos.
Si nuestro juego es muy complejo y el método render() tarda mucho
tiempo en dibujarlo todo, la llamada a update() se retrasará. ¿Cómo
podemos compensarlo? Pues cuanto más tiempo pase, más grande
será delta y mayor será la magnitud del desplazamiento. Así
nuestro movimiento será independiente de la frecuencia con que se
llame al método. Un buen truco, ¿no crees?

Sprites.

Lo que hemos hecho es crear dos variables nuevas para almacenar


la posición del guerrero dentro del mapa y otra adicional para
almacenar la imagen del sprite. El sprite se crea como un nuevo
objeto de la clase Image yque se asigna a la referencia jugador.
Haremos que la posición inicial sea el centro del contenedor:

Ya tenemos un fondo en nuestro contenedor que somos capaces de


desplazar a nuestra voluntad, pero sin elementos que pueblen el
mapa, se muevan por él e interactúen entre sí no podemos hacer
gran cosa. Es ahí donde entra el concepto de sprite, palabra
anglosajona que podría traducirse por duende o hada, y que no es
más que un dibujo 2D que puede colocarse en la pantalla y ser
manipulado como una entidad independiente.

Los sprites suelen describirse de forma similar a un tilemap en el


sentido que todas las imágenes que pueden representar su estado
(corriendo, saltando, parado, agachado, etc.) están juntas en una
imagen más grande. El motor de juegos se encargará de alternar
entre una u otra imagen en función de las instrucciones del código
específico. A este concepto se le llama animación y permite dar una
sensación más natural al sprite.

Fíjate en la diferencia entre tilemap y sprite: ambos son gráficos


que se pueden desplazar, pero su concepción y utilización son muy
distintas. Mientras un tilemap se considera una especie de fondo de
pantalla compuesto de imágenes pequeñas que se colocan unas
junto a otras los sprites son elementos que se mueven por él y cuya
imagen puede cambiar.
Descarga el fichero siguiente, cópialo con el
Los motores 2D suelen estar optimizados para mostrar un gran nombre mosquetero1.png a la carpeta data de tu proyecto y prueba
número de sprites simultáneamente en el contenedor. Piensa que los cambios.
cada jugador, cada enemigo e incluso cada bala es un sprite que se
También hemos cambiado el comportamiento de las flechas del
puede mover y animar de forma independiente a los demás.
cursor, que ahora cambian el valor de la posición del jugador y no el
¡Aprendamos a usarlos a nuestros proyectos! desplazamiento global de los dibujos (esta vez multiplicamos el
tiempo transcurrido desde la última actualización por 0.1 para que
Creando un sprite estático.
el jugador no vaya demasiado rápido). Por último, dentro del
Vamos a incorporar un personaje a nuestro mapa: un mosquetero. método render(), hemos llamado al método draw() del dibujo del
Nuestro sprite no estará animado por ahora, ya lo resolveremos mosquetero para dibujarlo en la posición que le corresponde.
más adelante.
Creando un sprite animado (I). Constructores. Animation(SpriteSheet Similar al anterior, pero en vez de usar to-
Nuestro héroe se mueve, pero parece un trozo de cartón más que hoja, int[] cuadros, int[] dos los frames que tiene el sprites-
duraciones) heet permite elegir sólo algunos y en el or-
una personaje. La solución para darle un toque natural es usar
den que queramos mediante un array
animaciones. Ya vimos antes que una animación no es más que una
de pares de coordenadas para
secuencia de imágenes que cuando se muestran secuencialmente
cada frame (el primero del sprites-
producen un efecto de dinamismo y movimiento. Fíjate en la
heet tiene índice 0,0). Se especifican las du-
siguiente imagen:
raciones de cada cuadro con otro array al
final.
Animation(SpriteSheet Éste es el constructor más complejo. Per-
hoja, int x1, int y1, int x2, int mite seleccionar una región rectangular
y2, bool horizontal, int du- de frames dentro de un spritesheet (en el
racion, bool autoActuali- ejemplo se explica como referenciarlos).
zar) Todos los frames tendrán la misma dura-
ción. También se puede activar o desacti-
var la autoactualización.

Si queremos usar directamente un spritesheet del comienzo, no nos


quedará más remedio que usar uno de los dos últimos
constructores.
En ella se pueden apreciar 4 filas, cada una de las cuáles es la
Creando un sprite animado (II). Cargando los frames.
animación que tendrá el sprite al moverse en la dirección arriba,
derecha, abajo e izquierda respectivamente. Si extraemos una de Vamos a crear cuatro animaciones, una por cada dirección en la que

esas imágenes independientes el trozo se denomina cuadro puede mirar nuestro personaje. Estos son los cambios que vamos a

o frame de la animación. El conjunto de la imagen global que hacer a nuestro código fuente:

contiene los cuadros se denomina hoja de sprites o spritesheet.

Descarga el spritesheet siguiente, cópialo con el nombre


mosquetero.png a la carpeta data de tu proyecto.

Tomando el caso concreto de Slick, hay que indicar cuánto tiempo


queremos que dure un determinado frame. Para cambiar de
un frame a otro podemos elegir entre encargar al motor la
temporización automática o bien especificar los tiempos
manualmente a través de código específico: es el parámetro de
autoactualización. La clase que se utiliza para realizar animaciones
en Slick es Animation. Tiene muchos constructores para adaptarse
a casi cualquier fuente de frames. A continuación destacamos
algunas de las más interesantes, las demás formas pueden
consultarse en la documentación de Slick sobre la clase Animation:

Constructores de la clase animation

Constructor Descripción
Animation(Image[] cua- Crea una animación con todas las imáge-
dros, int duracion) nes pasadas en el array cuadros. Cada una
de ellas se mostrará durante dura-
cion milisegundos.
Animation(Image[] cua- Crea una animación con todas las imáge-
dros, int[] duraciones) nes pasadas en el array cuadros. El
array duraciones indica durante cuantos
milisegundos se mostrará
cada frame respectivamente.
Hemos creado una variable cuadros de tipo SpriteSheet para
Animation(Image[] cua- Igual que el anterior, pero nos permite ac-
almacenar la imagen (spritesheet) que contendrá todos
dros, int[] duraciones, bool tivar o desactivar desde el constructor la
los frames de tamaño 24x32 píxeles y cinco variables de
autoActualizar) temporización automática.
Animation(SpriteSheet Crea una animación partiendo de todos los clase Animation. La variable llamada jugador contendrá ahora una
hoja, int duracion) cuadros del spritesheet hoja pasado como referencia a la animación actual mientras que las demás
parámetro. En el ejemplo veremos cómo almacenarán los datos de la animación correspondiente a la
se carga un spritesheet en Slick. dirección que indica su nombre. Por defecto, nuestro sprite estará
mirando hacia abajo.
Observa que para cargar la animación hemos usado el constructor
más complejo: el que selecciona un rectángulo de frames. Por
ejemplo, en el caso de la animación cuando el jugador mira hacia la
derecha tendríamos que seleccionar la segunda fila entera. Fíjate en
la imagen de arriba: cada frame tiene una coordenada (X,Y) en
el spritesheet, con lo que la fila que buscamos serán los frames (0,1)
(1,1) y (2,1). Si tuviéramos que expresarlo como un rectángulo, sería
uno que comienza en (0,1) y termina en (2,1).

El primer parámetro booleano del constructor debe valer true, si


queremos que la animación recorra el rectángulo fila por fila,
o false si queremos que lo haga columna por columna. Esto sólo
cambia la animación si el rectángulo tiene más de una fila y más
de una columnas, que no es nuestro caso.
Al detectar una tecla pulsada hacemos tres operaciones:
El siguiente parámetro especifica que cada frame tendrá una
duración de 150 milisegundos. • Actualizar la posición del jugador.
• Asignar al jugador la animación que corresponda con la
Copia el fichero mosquetero.png en la carpeta data y ejecuta el
dirección del movimiento.
proyecto. Aún no debería verse ninguna animación porque, si te fijas
en los constructores, hemos desactivado la animación automática • Indicar a la animación manualmente que han
transcurrido delta milisegundos para que seleccione
con el último parámetro. En el siguiente punto haremos los últimos
el frame que corresponde. Esto es necesario porque
cambios al respecto.
hemos desactivado las actualizaciones de las animaciones.
Reflexiona
Guarda los cambios y ejecuta el proyecto. Tu jugador debería
La animación hacia abajo también podría haberse creado así: moverse de manera más natural. Sin embargo, no todo es perfecto:
nuestro personaje puede salirse de la pantalla, con lo que dejaríamos
de verlo. Además, puede pasar por encima de todo, incluso de las
rocas o las paredes que hayamos marcado como obstáculos en
el tilemap. Resolveremos estos problemas un poco más adelante.

Un par de métodos que pueden resultarte interesantes y que están


Animando un sprite. muy relacionados con la animación:
Ya queda poco para dotar de animación a nuestro personaje. ¿Te has
fijado en que no hemos cambiado la línea que
• .setLooping(boolean bucle): si se llama al
método setLooping(false) en una animación, cuando ésta
dice jugador.draw(posX, posY) dentro del método render() y, aún así,
termine no volverá a empezar. Es muy útil, por ejemplo,
el compilador no se ha quejado de ningún error? Es más, nuestro
para animar explosiones.
personaje sigue dibujándose sin problemas. El motivo es que la
clase Animation también tienen un método .draw() con los mismos • .setPingPong(boolean pingpong): cuando se le pasa un
parámetros que la clase Image con lo que no ha sido necesario tocar valor true, la animación se realiza primero en un sentido
dicha línea. La imagen que realmente se dibujará dependerá del y luego en el contrario, para luego volver a empezar. Es
estado de la animación. como si rebotara al llegar el final de la animación.
• .isStopped(): devuelve true cuando la animación se ha
Para animar al sprite tendremos que hacer estos cambios en el
detenido.
método update():
Siguiendo al jugador (I). Centrando la vista Siguiendo al jugador (II). Poniendo límites.
Nuestro próximo reto es que el contenedor, la ventana por la que el El problema no es difícil de arreglar, pero para entender cómo
jugador se asoma al videojuego, procure mantener al personaje funciona vamos a tener que introducir algunos métodos nuevos que
dentro del alcance visual. todavía no hemos utilizado de la clase TiledMap:

Una posible solución es alterar la porción que vemos del mundo • .getWidth(), que nos devuelve el número de columnas
representado por el mapa. ¿Te suena? ¡Claro! En una de las fases del tilemap completo.
iniciales del proyecto conseguimos movernos libremente por • .getHeight(), que nos devuelve el número de filas
el tilemap. Aprovecharemos los conocimientos adquiridos. del tilemap completo.
• .getTileWidth(), que nos devuelve la anchura de un tile en
píxeles.
• .getTileHeight(), que nos devuelve la altura de un tile en
píxeles.

Fíjate en que si
multiplicamos mapa.getWidth() por mapa.getTileWidth() obtendre
mos la anchura total del mapa en píxeles. Lo mismo ocurrirá con la
altura total si hacemos lo propio sustituyendo Width por Height.
Añade esta línea con las demás variables:
Para no complicarlo mucho, vamos a intentar que el jugador
siempre esté en el centro de la pantalla. Con el fin de realizar los
cálculos necesitaremos usar dos métodos nuevos de la
variable gc de la clase GameContainer que se pasa como parámetro
Éstas al final del método init():
a update():

• .getWidth(), nos devuelve la anchura en píxeles del


contenedor del juego (la ventana).
• .getHeight(), nos devuelve la altura en píxeles del
contenedor del juego. Y el resto al final del método update():

El desplazamiento que tendremos que aplicar dependerá de la


posición del jugador: para que siempre esté en el centro habrá que
restarle a su posición la mitad de la anchura y la mitad de la altura
de la pantalla, es decir, del contenedor. En el ejemplo el jugador se
encuentra en la posición (500,300); si el contenedor mide 400x300
píxeles, tendremos que colocarlo en la posición (100,150) para que el
jugador esté en el centro.

Recuerda que para desplazar a la derecha y hacia abajo los valores


de despX y despY han de ser negativos (de ahí el signo menos
inicial). Coloca estas líneas tras los bloques if que procesan la
pulsación de las teclas:

Esa sección de código se asegura de que a pesar de desplazarnos


nunca nos saldremos de los límites del mapa. Intentemos entender
el por qué con un ejemplo: imagina que nuestro tilemap tiene unas
Guarda el proyecto y ejecútalo. Comprobarás como el contenedor dimensiones de 1000x700 píxeles y nuestro contenedor de 400x300
sigue ahora al jugador. Sin embargo ocurre un problema: cuando píxeles. Piensa que lo que desplazamos es el mapa:
movemos nuestro personaje cerca de las esquinas del tilemap se
ven unas antiestéticas franjas negras.
Un desplazamiento positivo en la variable X implica que • Considerar todos los tiles de una capa como obstáculos.
el tilemap se va a mostrar más a la derecha respecto al borde del Esto sólo lo podremos hacer si el diseñador los ha colocado
contenedor, lo cuál provocará que se visualice una franja negra a la en una capa independiente. Se buscarán y almacenarán
izquierda. Por eso si no dejamos que el desplazamiento en X sea en un array de forma similar al punto anterior.
positivo evitaremos la franja en ese lado.
Centrémonos en la segunda solución: ¿Cómo puedo marcar
Siguiendo el razonamiento, lo máximo que podremos desplazar a la un tile como obstáculo? En Tiled existe un concepto llamado
izquierda el mapa es su anchura en píxeles menos la anchura del "propiedades". Una propiedad es información adicional que puede
contenedor. Si nos desplazamos más allá, veremos una franja negra incluirse a nivel de mapa, a nivel de capa y a nivel de tile. Constan
a la derecha, por eso limitamos el valor a que, como mínimo, sea ese de un nombre y un valor, ambos siendo de tipo java.lang.String.
valor y nunca menos.
Las propiedades del mapa se cambian desde la opción "Mapa |
Lo mismo se aplicaría al desplazamiento de la coordenada Y. Propiedades del mapa...". Las de una capa, haciendo clic con el botón
secundario encima de su nombre y eligiendo la opción "Propiedades
Para que quede perfecto lo único que falta es evitar que el jugador
de la capa..." desde el menú contextual. En el caso de
se salga del mapa y eso lo resolveremos cuando implementemos
un tile del tileset se hace de forma similar a la capa.
las colisiones.

Índice de recursos gráficos.


Puedes encontrar
muchos tilesets y spritesheets en Internet usando un buscador,
aunque debes tener cuidado con la licencia de uso y los derechos de
autor: algunos prohíben el uso comercial, otros son de uso libre y en
otros has de informar al autor o incluirlo en los créditos de tu
proyecto. Consulta bien antes de usarlos.

Colisiones.
Por fin nuestro héroe puede moverse por todo lo ancho y alto del
mapa sin salirse de sus límites y sin que lo perdamos de vista.

Aún así, hay todavía un comportamiento extraño, y es que puede


pasar por encima de todos los elementos del mapa. Sería deseable
evitar que el sprite invadiera tiles que se consideran obstáculos.

Para ello, la única información fiables que tenemos es el tilemap ya Estas propiedades pueden consultarse mediante Slick, aunque los
que la colocación de obstáculos es tarea del diseñador de mapas. En desarrolladores nos advierten de que son funciones muy lentas y
los próximos apartados aprenderemos a extraer información del él no deben usarse en los métodos update() o render(). Por tanto, si
y ha reaccionar de forma correcta ante los obstáculos. vamos a utilizarlas deberemos hacerlo en el método init() y
almacenar los datos extraídos en alguna variable (por ejemplo el
La colisión entre sprites no está soportada por Slick aún. Eso
array que comentamos antes). Los métodos para leer las
significa que, si la necesitamos, tendremos que programarla
propiedades se aplican a objetos de la clase TiledMap:
nosotros. En la unidad anterior se explicaron algunas
aproximaciones a la detección de colisiones usando formas
Método Descripción
geométricas que serían muy sencillas de aplicar.

Marcando los obstáculos en el tilemap (I). Propiedades. java.lang.String


getMapPro- Devuelve el valor de la propiedad nombre-
Antes de impedir el paso al jugador tenemos que saber por donde
perty(java.lang.String nom- Propiedad del método. Si no existe, se de-
no puede pasar porque algún tile no se lo permite. Hay muchas
brePropiedad, volverá def en su lugar.
formas de indicar que un tile del mapa contiene un obstáculo:
java.lang.String def)

• Podríamos hacerlo de forma manual, incluyendo en el


java.lang.String
código específico una lista con las coordenadas de Devuelve el valor de la propiedad nombre-
getLayerProperty(int indice-
los tiles que hay que evitar. No es una buena idea porque Propiedad de la capa número indice-
Capa,java.lang.String nom-
cualquier cambio en el mapa implicaría modificar el código Capa (la primera empieza por 0). Si no
brePropiedad
del juego. Además, sería una tarea muy laboriosa. existe, se devolverá def en su lugar.
,java.lang.String def)
• Marcar desde Tiled ciertos tiles del tileset como
obstáculos. Cuando carguemos el mapa, buscaremos las Devuelve el valor de la propiedad nombre-
ocurrencias de los tiles marcados. Una vez encontrados, java.lang.String getTilePro-
Propiedad del tile número tileID. El identi-
perty(int tileID,
almacenaremos en un array bidimensional del tamaño ficador lo podemos extraer directamente
java.lang.String nombrePro-
del mapa que indique si el tile que está en esa posición es del mapa con el método .getTileId(x,y,capa).
piedad, java.lang.String def)
un obstáculo o no. Si no existe, se devolverá def en su lugar.
Marcando los obstáculos en el tilemap (II). Leyendo las Detectando la colisión con obstáculos (I). Algoritmo.
capas.
Aún tenemos que estudiar la tercera solución. De hecho, será esa la
que utilicemos para nuestro proyecto ya que nuestro tilemap tiene
todos los obstáculos colocados en una capa independiente. Si cuando
creaste tu mapa no lo tuviste en cuenta, modifícalo antes de seguir
o no funcionará la detección de obstáculos. Recuerda que la primera
capa es el suelo, la segunda la decoración y la tercera los obstáculos.

Nuestro personaje debe poder moverse libremente por el mapa


El método para detectar si hay un tile en cierta posición de una capa
hasta que un obstáculo se lo impida. ¿Cómo sabemos si la posición
determinada es .getTileId() y se introdujo en la sección anterior. Si
del sprite es válida o no? Observa la siguiente imagen y fíjate en
la llamada devuelve 0 es que en esa posición del mapa y capa no
el sprite de la parte de arriba: siempre que su base no esté tocando
hay ningún tile. Ya que tenemos que usarla en el método init() , lo
ningún tile obstáculo debería considerarse que su posición es buena.
que vamos a hacer es recorrer todo el mapa buscando los obstáculos
Dado que la anchura del sprite es más pequeña que la de
y almacenar en un array si el paso está bloqueado o no:
un tile será suficiente comprobar si alguno de los dos extremos de
la base tocan un obstáculo (mira los puntos azules de la parte de la
derecha).

Podrías preguntarte el motivo de considerar solamente la base en


lugar del rectángulo completo, ¡es una muy buena pregunta! Se debe
a la perspectiva de nuestros tiles. Observa ahora el sprite que hay
justo debajo y verás como su rectángulo está dentro de
un tile obstáculo, sin embargo esa posición es visualmente válida
porque el jugador no está tocando el tile realmente. Es por ello que
solo comprobaremos dónde pisa, es decir, la base del sprite.

Si estuviéramos usando un tileset y sprites con una perspectiva


cenital (donde la acción se desarrolla solamente en horizontal y lo
vemos justo desde arriba), entonces sí tendríamos que comprobar
el cuadro entero. Imagina por un momento que estuviéramos
Añadimos estas tres variables al comienzo de la clase: programando un juego de tanques en perspectiva cenital y en el que
hay tiles que representan un muro:

Las dos primeras nos servirán para guardar las dimensiones del
mapa en tiles. Así evitamos tener que llamar a los métodos
continuamente. El array almacenará un booleano por cada tile del
mapa indicando con true si es un obstáculo o con false si no lo es.
Por último, incorpora las siguientes líneas al final del método init(): Aquí no tendría sentido comprobar sólo la base porque podría
ocurrir que se solaparan, como pasa con el tanque de abajo, y no se
vería natural.

Volviendo al caso que nos ocupa, el problema se resume en


comprobar si alguno de los extremos de la base está tocando
un tile marcado como obstáculo. Para hacer los cálculos
necesitaremos algunos datos que almacenaremos en nuevas
variables. Colócalas detrás de las últimas que creamos.

Fíjate que recorremos el tilemap preguntando el ID del tile que está


en la posición (x,y) de la capa 2, la de los obstáculos (recuerda que
empieza a contar desde 0). Si devuelve un número distinto de
El primer par de variables almacenará las dimensiones en píxeles
cero, (mapa.getTileId(x, y, 2) != 0) valdrá true.
del sprite (en este caso serán 24 y 32 respectivamente). El segundo
Con estos cambios, ya podemos comprobar de una forma sencilla par contendrá las dimensiones en píxeles de un tile del mapa.
desde el resto del código si hay un obstáculo en determinado tile del Añadamos las asignaciones de sus valores al final del método init():
mapa.
Para saber a qué tile pertenece un punto determinado del jugador
primero necesitamos tener su posición en píxeles. Luego se
convierte a unidades de tiles dividiendo el componente por el
tamaño de un tile.

El extremo izquierdo de la base:

• En píxeles: [jugadorX, jugadorY+jugadorHeight]


Detectando la colisión con obstáculos (II). Implementación.
• En tiles: [jugadorX/tileWidth,
Una vez disponemos del algoritmo ya sólo tenemos que
(jugadorY+jugadorHeight)/tileHeight]
implementarlo. Básicamente lo que haremos es guardar una copia
El extremo derecho de la base: de la posición del jugador antes de que pueda ser cambiada con las
teclas del cursor. Después de procesar el teclado comprobaremos si
• En píxeles: [jugadorX+jugadorWidth, la posición nueva es válida; si no lo es devolveremos la posición
jugadorY+jugadorHeight] original.
• En tiles: [(jugadorX+jugadorWidth)/tileWidth,
(jugadorY+jugadorHeight)/tileHeight]

Ya que estamos detectando colisiones con obstáculos podemos


aprovechar para detectar la colisión con el borde del tilemap,
evitando así que el jugador pueda salir. No sería necesario realizarlo
si rodeáramos el mapa con obstáculos, pero no es nuestro caso así
que tendremos que considerar esta posibilidad vía código.

Es muy sencillo, solamente tenemos que preguntarnos en qué casos


la posición no será válida. Veámoslo primero con la coordenada X:
Añadiendo personajes no jugadores (NPC)
• Si jugadorX es menor que 0, estaremos saliéndonos por En un juego normalmente será necesario añadir otros sprites no
la parte izquierda. controlados directamente por el jugador, tales como enemigos, balas
en movimiento, personajes interactivos, etc. Con los conocimientos
• Si jugadorX es mayor que mapaWidth (la anchura del
que ya tienes no deberías tener problemas en programarlos, pero
mapa) quiere decir que la parte izquierda del sprite se
aún así vamos a hacer un ejemplo que demuestre el potencial
habrá salido del mapa. Pero el sprite hace ya unos
de Slick.
cuantos píxeles que empezó a salirse por su extremo
derecho. Por tanto, a la anchura del mapa límite habrá que El objetivo será crear NPC, personajes no controlados por el jugador.
restarle la anchura del sprite. Su misión consistirá en moverse en línea recta desde una posición
aleatoria del mapa a otra y pararse una vez llegar ahí. No
Modifiquemos la sentencia if que acabamos de añadir con estos
cambios y asunto arreglado:
entraremos en implementar colisiones con ellos, eso te lo dejamos
como ejercicio si quieres profundizar.

Cada NPC será un sprite animado. Reutilizaremos el spritesheet del


protagonista, con lo que todos serán copias idénticas suyas.
Implementaremos el código de la manera más sencilla, pero ten en
cuenta que lo óptimo sería crear una clase nueva que guarde el
estado del NPC (su posición, su destino, su animación, etc.) en lugar
de hacerlo con arrays.

Por lo pronto tenemos que determinar qué variables son distintas Y esto al final de render():
para cada NPC. En este caso será la posición actual, la posición
destino y la animación activa). No podemos reutilizar directamente
las animaciones del protagonista porque interferirían entre sí al no
ser animaciones automáticas, sino manuales.

Fíjate que hemos activado la animación automática ya que las


instancias de animación son compartidas entre todos los NPCs y
con la animación temporizada manualmente se interferían entre
ellos. Para elegir posiciones al azar usamos la
función Math.random() que devuelve un double entre 0 y 1. Si lo
multiplicamos por mapaWidth nos devolverá un número entre 0 y
mapaWidth (sin llegar a él). Dado que las coordenadas se
almacenan como float, hemos tenido que hacer un casting.

¡Ejecútalo y visita el mapa recién poblado! Puedes aumentar la


población cambiando el valor de numeroNPCs.

El hecho de implementar esas variables como arrays nos permite


Ajustes de profundidad.
acceder rápidamente a los datos del NPC que necesitemos en cada
Para terminar con la parte gráfica resolveremos otro problema que
momento y, además, usando bucles for podemos realizar las
aún tenemos pendiente. Recuerda que en el tilesheet aparecían
mismas operaciones a todos ellos de una forma sencilla.
columnas y otras estructuras de varios tiles de alto. Tal y como está
Añade esto al final de init(): ahora el código, un tile o bien bloquea el paso o el jugador pasa por
encima:

Lo ideal sería que pudiéramos hacer que el jugador aparezca detrás


de algunos tiles:
Ten en cuenta que este problema sólo lo vamos a sufrir cuando
nuestro juego 2D no use la perspectiva cenital: si los sprites son
planos y no tienen altura no se van a solapar si no han colisionado.

Veamos cómo conseguir este comportamiento de una manera


sencilla. Lo primero será modificar nuestro tilemap y añadir una
nueva capa que llamaremos "Altura". En ella colocaremos la parte
superior de los objetos que tengan más de un tile de altura. Fíjate
que los tiles que coloquemos en esa capa nunca podrán ser
obstáculos para el jugador porque en realidad están a diferente
nivel.

Música y efectos de sonido.


PARA ACABAR CON ESTA INTRODUCCIÓN A LOS MOTORES 2D VEAMOS CÓMO
AGREGAR MÚSICA Y EFECTOS DE SONIDO A NUESTRO PROYECTO. LAS
CAPACIDADES EN ESTE ÁMBITO VARÍAN ENTRE UN MOTOR U OTRO , AUNQUE LA

GRAN MAYORÍA PERMITEN REPRODUCIR UNA CANCIÓN DE FONDO MIENTRAS LOS

DEMÁS SONIDOS SON LANZADOS A DISCRECIÓN DEL CÓDIGO ESPECÍFICO.

Algo que debemos investigar, antes de encargar al departamento


artístico la música o los sonidos, es qué formatos admite nuestro
motor y si todos ellos estarán disponibles en las plataformas para
Respecto al código, tendremos que hacer unos pequeños cambios al las que deseamos lanzar el videojuego.
método render():
En el caso de Slick, veremos que admite música sintetizada en
formato XM, MOD y música digitalizada en formato OGG
Vorbis, WAV sin comprimir y AIFF. Los efectos de sonido admiten
los mismos formatos. La diferencia entre música y efectos de
sonido es el número de reproducciones simultáneas que se soporta
y que los sonidos ofrecen posicionamiento 3D, como luego veremos.

Quizás te llame la atención que no reproduzca música grabada en


ficheros MP3. El motivo es que hay patentes de por medio que
implicaría tener que pagar royalties por cada juego que hiciera uso
En lugar de delegar en Slick que dibuje todas las capas, vamos a del descodificador de MP3. Como alternativa puedes usar OGG
dibujarlas una a una nosotros manualmente. El truco es renderizar Vorbis, que tiene incluso mejor calidad para el mismo tamaño de
las 3 primeras capas (suelo, decoración y obstáculos), luego al archivo y está libre de patentes. No deberías encontrar problemas
jugador y, por último, la nueva capa. en encontrar un conversor de formato que lo soporte.

Los NPCs también pueden participar en esto: dibújalos antes de Los ficheros XM y MOD son conocidos como formatos de
renderizar la última capa y también respetarán los tiles que están música soundtracker. Contienen grabaciones de instrumentos o
a otra altura. Eso sí, ten en cuenta que no se paran ante los sonidos y una partitura que los va reproduciendo en el momento y
obstáculos, con lo que es posible que veas cosas raras cuando pasen tono adecuado para crear la canción. Suelen ocupar poco espacio
por la base de un dibujo de varias alturas. comparados con los MP3 pero aún así pueden llegar a tener gran
calidad. En Internet podrás encontrar páginas con inmensas
Esto funcionará mientras la altura de los sprites no sea mayor que
colecciones de este tipo de música (cuidado con los derechos de autor
la de un tile, que es el caso habitual.
si usas alguna en un proyecto).

Con Slick la reproducción de sonido se realiza mediante 3 librerías:


el soporte de ficheros XM/MOD es tarea de ibxm.jar, mientras que
los ficheros OGG son descodificados y reproducidos por jogg-
0.0.7.jar y jorbis-0.0.15.jar. No olvides incluirlas en tu proyecto si
quieres disfrutar del servicio de reproducción.
Reproduciendo música. float getPosition() Obtiene el tiempo de reproducción actual en
Slick garantiza que siempre va a poder reproducirse una canción, segundos.

pero solo una. Es decir, si hay una canción sonando y solicitamos al void setPosition(float Mueve la reproducción al instante temporal
posicion) de posicion segundos.
motor que reproduzca otra, la primera parará con el fin de dar paso
a la segunda.

Por lo demás, es muy sencillo cargar canciones y reproducirlas. Efectos de sonido


Añadimos una variable para almacenar la referencia: El uso de los efectos de sonido es similar al de la música. De hecho,
se crean de la misma forma salvo por el hecho de que son instancias
de la clase Sound en lugar de Music.

Las diferencias entre ambos son puntuales. Por ejemplo, los sonidos
carecen de métodos para pausar y reanudar la reproducción o para
En el método init() procederemos a cargar la canción y, si queremos,
realizar una transición de volumen. Tampoco podemos solicitar
a ordenar la reproducción:
notificaciones. Pero no todo iban a ser inconvenientes: se permite la
reproducción de varias instancias de
objetos Sound simultáneamente. La cantidad de sonidos depende
del sistema operativo y del hardware, pero normalmente son varias
Descarga la siguiente canción y cópiala con el nombre bi-menu.xm a decenas. Además, permite posicionar en el espacio 3D la
la carpeta data de tu proyecto. reproducción del sonido, con lo que si el jugador dispone de un
sistema de audio envolvente podemos colocar el sonido detrás de él,
¡Así de simple! En este caso estaremos reproduciendo la canción bi-
o incluso virtualmente por encima o por debajo.
menu.xm que se encuentra en la carpeta data. Tres observaciones:
la primera es que la orden de reproducción se puede dar en cualquier Éstos son los métodos más destacables de la clase Sound:
momento, no estamos forzados a hacerlo en el método init(); la
segunda es que la canción se reproducirá solamente una vez y Método Descripción
cuando termine, parará. Si necesitamos que se reproduzca void play() Reproduce el sonido una única vez.
indefinidamente tendremos que usar el método musica.loop(). La void play(float Reproduce el sonido una única vez a la velocidad
última observación es que si se trata de un fichero OGG de gran pitch, float volu- indicada por el pitch (si es mayor 1, más rápido, si
men) es menor que 1, más lento) y con el volumen espe-
tamaño, es conveniente pasar true como segundo parámetro al
cificado en volumen (1 es el volumen normal).
constructor. Con eso se solicita que no cargue el fichero completo en
void playAt(float Reproduce el sonido desde la localización 3D espe-
memoria sino que vaya leyendo lo que necesite en cada momento
x, float y, float z) cificada.
(streaming).
void play(float Igual que el equivalente play, pero posicionando la
Aquí mostramos algunos de los métodos más interesantes de la pitch, float volu- reproducción del sonido.
clase Music: men, float x, float
y, float z)
Método Descripción void loop() Reproduce el sonido en un bucle indefinido.
void play() Reproduce la canción una única vez. void loop(float Igual que el equivalente play, pero reproduce inde-
void play(float pitch, Reproduce la canción una única vez a la ve- pitch, float volu- finidamente.
float volumen) locidad indicada por el pitch (si es mayor 1, men)
más rápido, si es menor que 1, más lento) y void stop() Finaliza la reproducción abruptamente.
con el volumen especificado en volumen (1 es bool playing() Devolverá true si la canción está en reproducción.
el volumen normal).
void loop() Reproduce la canción sin parar.
void loop(float pitch, Igual que el equivalente play, pero reproduce Agreguemos entonces sonido a nuestro proyecto. Empezaremos
float volumen) indefinidamente. añadiendo una variable para almacenar la referencia:
void stop() Finaliza la reproducción abruptamente.
void pause() Realiza una pausa en la reproducción.
void resume() Continúa la reproducción que había sido pau-
sada.
En el método init() procederemos a cargar la canción y, si queremos,
void fade(int duracion, Realiza una transición suave al volumen es-
a ordenar la reproducción:
float volumenFinal, pecificado durante duracion milisegundos.
bool pararDespues) Si pararDespues vale true, al finalizar la
transición detiene la reproducción.
bool playing() Devolverá true si la canción está en repro-
ducción.
Y ahora, desde update() podemos reproducirlo cuando queramos, por
float getVolume() Devuelve el volumen de reproducción actual.
ejemplo, al pulsar la barra espaciadora. Si llamamos a play() sin
void setVolume(float Establece el volumen al indicado por el pará-
asegurarnos de que el sonido no está en reproduciéndose, volvería a
volumen) metro volumen (1.0f es el volumen normal).
empezar desde el principio:
Otras alternativas de distribución pasan por la creación de
un applet Java o un paquete JNLP Web Start, pero no las
comentaremos aquí.

¡Nosotros hemos terminado con los juegos 2D! Pero tú acabas de


comenzar una vez que has programado tu primer videojuego.

La arquitectura de un videojuego en Android


Descarga el siguiente sonido y cópialo con el nombre Heal8-Bit.ogg
a la carpeta data de tu proyecto (cuidado con las mayúsculas y las
minúsculas).

Despliegue de un proyecto

Como hemos comentado, Android es un sistema operativo libre


basado en el kernel de Linux que se encarga de los servicios bases
De hecho, desplegar la aplicación no resulta complicado una vez del sistema como seguridad, gestión de memoria, gestión de
sabemos aprendemos a colocar las carpetas en su sitio correcto. procesos etc. El núcleo también actúa como capa de abstracción
entre el hardware y el resto del software. Sobre este núcleo se
Para comenzar, asegúrate de que tu proyecto funciona superponen las siguientes capas:
correctamente ejecutándolo desde el IDE. Cuando estés seguro de
continuar, ejecuta la opción "Run | Clean and Build Main Project". Esto • Android Runtime: Android incluye un conjunto de
despejará la carpeta dist de tu proyecto, copiará las librerías de Slick bibliotecas base que proporcionan la mayor parte de
en una subcarpeta lib y, finalmente, copiará el archivo .jar que funciones disponibles en las bibliotecas base de Java. Cada
contiene tu aplicación. aplicación corre su propio proceso con su propia instancia
de la máquina virtual Dalvik (una máquina virtual de Java
Pero aún no está todo listo: aún debes copiar manualmente dentro aunque el bytecode con el que opera no es Java bytecode)
de dist las carpetas data y native de tu proyecto con el fin de que
• Bibliotecas: Android incluye un conjunto de bibliotecas en
la aplicación las encuentre. Si lo has hecho correctamente, haciendo
C y C++ usadas para varios componentes del sistema.
doble clic desde el explorador de archivos sobre el fichero .jar debería
Estas bibliotecas incluyen la biblioteca estándar de C,
ejecutarse sin problemas. Puedes borrar el fichero README.TXT que
SQLite y OpenGL ES entre otras.
ha generado NetBeans si quieres, no lo vamos a necesitar.
• App framework: la arquitectura esta diseñada para poder
¡Ya hemos resuelto el problema! Basta con copiar la carpeta en el reutilizar los máximos componentes posibles y esta capa
ordenador que queramos para desplegarlo. No necesita instalación permite utilizar en las aplicaciones elementos del sistema
previa alguna excepto, como es lógico, de la máquina virtual de Java. como el navegador o el reproductor de música, aunque
Y sí, funcionará igual de bien en Windows, MacOS sujeto a unas estrictas normas de seguridad.
X, Linux y Solaris (en teoría, claro). • Aplicaciones: todas las aplicaciones a nivel de usuario
como cliente de correo, mapas, mensajería... escritas en
A partir de este momento, siempre que demos la orden "Run | Build
Java.
Main Project" desde NetBeans, el fichero .jar se actualizará sin tocar
los demás ficheros. Has de tener cuidado de no volver a seleccionar Si abstraes el bloque central "Lógica del videojuego" y piensas en él
"Clean and Build", porque se borrarían las carpetas data y native, con como en un cerebro que lo controla todo, la arquitectura sería muy
el consiguiente engorro. sencilla: los eventos de usuario (toques y arrastres del dedo,
interacción con controles de hardware) son enviados al videojuego,
Un último consejo: recuerda que si añades un nuevo asset a la
el cual los proceso y genera una salida en forma de animación. Este
carpeta data deberás actualizar también la copia que hay en la
proceso dura y se repite el tiempo que el videojuego está en
carpeta dist para no experimentar problemas.
ejecución, de tal manera que se forma un bucle recibiendo eventos
de usuario y generando la salida. Este bucle se llama bucle de juego
o "Game Loop" y, por lo tanto, es el cerebro del videojuego, proceso
los eventos del usuario transformándolos en controladores de
acciones, que sirven para tomar decisiones en cuanto a la animación Debes conocer
a generar.
El Canvas tiene métodos para dibujar todo tipo de objetos. Por
Para dibujar todas las cosas que suceden en una escena de nuestro ejemplo:
videojuego, se utilizan los Framework de Android para animaciones
2D y 3D y en especial, se puede utilizar la librería estándar Open GL • drawARGB: rellena todo el bitmap del canvas del color
para estos menesteres. Con estas librerías se pueden ARGB que se pasa como parámetro.
tratar sprites (imágenes de transparencia) para componer escenas • drawArc: dibuja un arco, acorde a ciertos parámetros.
y generar animaciones. • drawBitmap: dibuja un objeto de formas sencillas.
• drawCircle: dibuja un círculo con un determinado radio.
El sonido es parte importante de un videojuego. En el game loop
también se deben tomar las decisiones referentes a los efectos de • drawLine/s: dibuja una línea o varias líneas.

sonido que se van a a generar en cada momento y se utiliza el • drawOval: dibuja un óvalo encuadrado en un rectángulo.
framework de android para reproducir estos efectos. • drawPath: dibuja un camino con la pintura específicada.
• drawPicture: dibuja una imagen cualquiera.
El Canvas en Android
• drawPoint/s: dibuja uno o varios puntos.
• drawRect: dibuja un rectángulo.
• drawRoundRect: dibuja un rectángulo con los bordes
redondeados.
• drawText: permite dibujar un texto.
• drawTextOnPath: permite escribir un texto siguiendo una
ruta a través de la pantalla.

Los Dibujables (Drawables) en Android.


Un dibujable es una abstracción para definir "cualquier cosa que se
pueda dibujar". Estos son los objetos que querrás utilizar cuando
necesites cargar una imagen de un fichero.

Android es capaz de manejar bitmaps de los siguientes tipos:


Cuando se necesita generar gráficos de forma dinámica, se dibujan
estos gráficos en un objeto "Lienzo" o Canvas. • Png: El preferido.
• Jpg: Aceptable.
Canvas es una librería que solamente va a permitir trabajar con
gráficos 2D pero muy útil para proyectos pequeños a medianos.
• Gif: Desaconsejado.

Canvas funciona como una capa que captura todas las llamadas a Para referenciar a estos elementos desde el código hay que
“draw” y pinta sobre un Bitmap que es la pantalla. Según las escribir R.drawable.nombrefichero. Tan solo tiene una restricción,
necesidades de rendimiento se puede elegir entre pintar creando que el nombre tiene que estar en minúscula y que como caracteres
una View modificada o en un nuevo hilo de ejecución y un especiales solo acepta el guión bajo.
SurfaceView.
En XML se referencia mediante la expresión
El canvas se repinta regularmente para dar forma a la animación. @drawable/nombrefichero.
Además, para pintar un canvas, se necesitan:
Para dibujar en un canvas un bitmap se utiliza la clase
• Las coordenadas x, y donde se van a pintar el objeto. BitmapFactory y el método drawBipmap:
• Una primitiva del dibujo, es decir, qué dibujar. Por ejemplo,
Rect (Rectángulo), Text (Texto), Bitmap (gráfico).
• Un objeto Paint para describir los colores y estilo para el
dibujo.

El sistema de coordenadas de Android está pensado para preservar


el aspecto independiente del tamaño de la pantalla que se esté
Los Sprites en Android.
utilizando. La coordenada (0,0) corresponde al borde superior Los sprites (en inglés, duendecillos) son imágenes con fondo

derecho de la pantalla, mientras que la corrdenada (x_max, y_max) transparente para poder ser integrada de manera natural en una

corresponde al borde inferior izquierdo. escena. Tan sólo se dibujan encima de la escena los píxeles que no
son transparentes. Para poder hacer un sprite necesitas un
Para calcular x_max e y_max, se utiliza el objeto Display que calcula programa open source con multitud de herramientas para retocar
el tamaño de la pantalla en pixels. y editar imágenes. Con gimp puedes dibujar, recortar, convertir a
diferentes formatos y, por supuesto, crear transparencias, elemento
esencial para crear sprites. Para crear una transparencia en una
imagen, se debe añadir un "Canal Alfa" para que actúe como
transparencia en la imagen.
3. Dibujar. Por último con todo los datos
actualizados al último instante se dibuja la
nueva situación en pantalla y volvemos a
empezar.
3. Cerrar el sistema.

Pero si pretende abordar un juego completo, es frecuente no tener


claro cómo debería ser la estructura del juego: qué repetir y cuando.

Por ejemplo, de opengameart.org/content/robot-2 puedes descargar


un bonito robot posando en diferentes posturas e intercalar las
imágenes para animarlo:

Verás que el paquete robot_c-toy.zip contiene una imagen por cada


postura del robot. Para secuenciar cada una de las imágenes
deberás cargar todas en memoria e ir dibujando una cada vez.

Otra estrategia es tomar una única imagen con todas las posturas
y cargarlas de una vez. Después, se puede ir tomando pedazos de la
imagen en secuencia para formar la animación mediante la
instrucción:

donde bitmap es la imagen con todas las posturas, sourceRect es


un objeto de tipo rectángulo con las coordenadas del recorte y
destRect son las coordenadas donde se va a dibujar.

El Framework de animaciones de Android.


Android incorpora un potente sistema de animaciones que permiten El orden no tiene por qué ser exactamente éste, pero habitualmente
realizar cualquier tipo de animacines automáticas sin tener será parecido. Vamos a detallarlo más, para comprobar que se
excesivos conocimientos de física o de matemáticas. entiende:

Para crear animanciones se utiliza la clase Animation, que • Al principio del programa, toda la inicialización, que no se
representa una animación que se puede aplicar a Vistas, Superficies repite.
u otros objetos. Además se puede utilizar la clase AnimationSet, que • Mientras la partida esté en marcha, una de las cosas que
representa un conjunto de animaciones que serán visualizadas al haremos es comprobar si hay que atender a alguna orden
mismo tiempo o unas detrás de otras. del usuario, que haya pulsado alguna tecla o utilizado su
ratón o su joystick/gamepad para indicar alguna acción
Android proporciona varios sistemas para crear animaciones:
del juego.

• Property Animations. • Otra de las cosas que hay que hacer siempre es

• View Animations. "componer" la imagen del juego (fondo + personajes),


generalmente en pantalla oculta, para evitar parpadeos.
• Drawable Animations
• Normalmente, antes de dibujar (quizá incluso antes del
Bucle de un videojuego (Game loop) en Android paso anterior) deberemos comprobar si ha habido alguna
Un videojuego requiere estar comprobando frecuentemente ciertas colisión o alguna otra situación que atender. Por ejemplo,
pautas para actuar en consecuencia por lo general ésto consta de si un disparo enemigo ha chocado con nuestra nave, no
una serie de pasos: deberíamos dibujar la nave, sino una explosión. O quizá
nuestro protagonista recoja un objeto y cambie su
1. Iniciar el sistema. apariencia.
2. Comenzar el Gameloop del juego.
• Cuando ya queda claro cómo debe ser la pantalla, es
1. Procesar eventos. En este paso se procesan
cuando la dibujaremos.
todos los eventos que entran al sistema como
• También hay situaciones especiales que se pueden dar
entrada del usuario, mensajes del sistema
durante la partida, y que pueden suponer interrupciones
operativo, red, etc.
del bucle normal de juego, como cuando el usuario pulsa
2. Actualizar estados. Una vez procesado los
la tecla de pausa, o pide ayuda, o pierde una vida.
eventos se actualizan todos los elementos del
juego: Física, lógica, etc.
• Finalmente, hay que recordar que cuando acaba la partida,
en casi cualquier biblioteca de funciones que usemos
deberemos liberar los recursos que hemos usado (en SDL, • Cosas que no deberían repetirse, como la lectura de las
con SDL_Quit). imágenes que vamos a usar (si no son muy grandes,
bastará con leerlas al principio y memorizarlas).
También debes tener en cuenta que en un juego típico
encontraremos:
• Cosas que sí deberán repetirse, como dibujar el fondo,
comprobar si el usuario ha pulsado alguna tecla, mover
• Cosas que no se repiten, como la inicialización para entrar los elementos de la pantalla si procede, etc.
a modo gráfico, o la liberación de recursos al final del
programa.
Tema 7. Desarrollo de juegos 3D
Instalando el motor de juegos
No vamos a repetir los criterios que existen a la hora de localizar
un motor 3D que se adapte a nuestras necesidades, pero sí conviene
recordar que es una decisión crítica que puede afectar al desarrollo
del proyecto muy negativamente, en caso de que no hagamos una
elección correcta.

Para esta unidad hemos decidido que el motor que nos acompañará
es jMonkeyEngine 3. Los motivos son varios:

• Es software libre.
• Está escrito en Java.
• Tiene una comunidad muy activa que le da soporte.
• Es fácil de manejar y permite realizar aplicaciones con un
gran rendimiento, ya que realiza las operaciones más
Estructura de un proyecto jME3.
críticas usando librerías nativas.
Cuando se crea un proyecto con jMonkeyPlatform, se prepara por
Por si no fuera suficiente, jMonkeyEngine 3 (en adelante jME3) nosotros una estructura de carpetas destinada a facilitar la
incluye un entorno de desarrollo propio derivado directamente organización de los assets que incluyamos. Además, se añaden
de NetBeans, que incluye algunos cambios para hacernos la vida automáticamente las librerías que son necesarias para poder
más fácil a la hora de trabajar con el motor sin perder las ventajas compilar el código fuente.
de poder usar lo que ya conocemos.

Al igual que Slick, jME3 también puede basarse en los servicios


ofrecidos por LWJGL, LightWeight Java Game Library. Recuerda
que LWJGL es una solución que permite la utilización nativa
de OpenGL, OpenAL, OpenCL y de la gestión de dispositivos de
entrada (joysticks,gamepads, etc.) mediante JInput.

¡Démonos un chapuzón en las tres dimensiones!


Nosotros trabajaremos principalmente con las
JMonkeyEngine 3D carpetas src y assets. Respecto a esta última, será ahí donde

jME3 es un motor de juegos 3D de código abierto programado colocaremos los assets externos que queramos incluir en el

en Java cuyos orígenes se remontan al año 2003. En el año 2008, proyecto para ser usado por el código específico:

sin ser estable todavía la versión 2.0 del motor, se culminó el


Carpeta Contenido
abandono del proyecto por parte de los programadores iniciales y
Interface Diseño de ventanas y cuadros de diálogos en
su progreso estuvo detenido por meses. formato NiftyGUI. Aquí se pueden incluir menús,
preferencias, etc. Si tienes interés, consulta
Afortunadamente, en el año 2009 un nuevo equipo de
en Internet cómo crear este tipo de recursos pues
desarrolladores tomó el relevo y comenzó a trabajar en la versión
aquí no lo explicaremos.
3.0 del motor. Dado que se trata de una reescritura completa del
MatDefs Materiales base que podemos usar para crear los
código fuente, aún a principios de 2012 no existía todavía una versión
nuestros.
estable. Al poco tiempo, se lanzó jMonkeyEngine SDK, un programa
Materials Materiales creados por nosotros.
derivado de NetBeans que además de la propia librería incluía un
Scenes Conjuntos completos de modelos, texturas, luces, etc.
entorno de programación con algunas herramientas específicas. La Por ejemplo: un nivel completo de un videojuego.
idea era buena: simplemente con descargar e instalar el SDK ya se Shaders Programas especiales que permiten crear efectos
podía empezar a programar. visuales.
Sounds Recursos sonoros usados por la aplicación.
No hay excesivas diferencias entre el entorno NetBeans clásico y
Textures Imágenes 2D utilizadas para definir el aspecto de los
éste. Básicamente, han incluido un par de categorías de proyectos,
materiales de un objeto.
unas cuantas plantillas y un conjunto de plugins tales como
editores, conversores de formato, importadores, etc.
En realidad nada impide colocar los assets en otros lugares, pero
En la actualidad la ultima versión del JMokeyPlataform estable es
hacerlo así nos permite ser más eficaces a la hora de localizarlos y
la 3.3.
por eso es el procedimiento recomendado.

Respecto al código fuente, una aplicación jME3 debe descender de la


clase Application. Sin embargo, esa clase no ofrece un grafo de
escena ni una cámara predeterminada (luego lo estudiaremos), lo
que dificulta la implementación del juego especialmente cuando
estamos empezando. Por ello, lo normal suele ser extender la clase
SimpleApplication que implementa todas esos elementos que
hemos nombrado antes.

Además de la estructura de carpetas, cuando iniciamos un nuevo


proyecto, el entorno crea por nosotros una clase principal que deriva
de SimpleApplication.

¡Hola mundo! Conceptos básicos 3D


Ya estamos listos para empezar, así que arranca el En esta sección, vamos a estudiar algunos de los conceptos más
entornojMonkeyPlatform. Crea un nuevo proyecto, elige como utilizados en el ámbito de las 3D. Donde sea posible, haremos una
categoría jME3 y selecciona BasicGame. Llámale HolaMundo. aplicación práctica de ellos para facilitar su comprensión.

Ejecuta el proyecto tal cual, sin cambios, y tras la compilación Comenzaremos hablando de lo que entendemos como mundo en un
deberías poder observar la siguiente pantalla: motor 3D. Tras ello, explicaremos cómo podemos referenciar puntos
en el espacio o cómo especificar direcciones para continuar con los
conceptos de vértice, polígono y malla.

El siguiente concepto que aprenderemos será el de grafo de escena.


Comprender cómo funciona este elemento es fundamental para
poder programar con garantías un juego en tres dimensiones. De
hecho, empezaremos a aplicar esos conocimientos para actualizar el
estado del mundo, transformando objetos o, lo que es lo mismo,
moviéndolos, rotándolos y cambiando su tamaño.

Para ello será necesario interactuar con el usuario mediante el


teclado, el ratón, el joystick y el gamepad. Más adelante, daremos
unas pinceladas a los materiales que dan el aspecto a los modelos
gráficos que aparecerán en el juego, aunque para que se puedan
Esta ventana te permite elegir los ajustes bajo los que quieres apreciar será necesario programar la iluminación de la escena.
ejecutar tu proyecto. El desplegable más a la izquierda contiene una
Otro concepto que estudiaremos será el de las animaciones de
lista de resoluciones de pantalla por defecto (cuanto mayor sea la
modelos, pues su utilización en un juego otorga dinamismo gráfico
resolución, menor será el rendimiento); el siguiente determina la
a las acciones que suceden en el mundo virtual. Y hablando de
profundidad de color, es decir, cuántos bits vamos a dedicar para
dinamismo, la inclusión de un motor de físicas hace que el
almacenar y dibujar cada píxel de la imagen. El desplegable más a
comportamiento de los objetos sea natural, al menos en cuanto a
la derecha permite activar el suavizado de imagen (antialiasing) que
fuerzas aplicadas y colisiones entre ellos. Esto se verá terminando
reduce los cambios bruscos en los bordes de los polígonos a costa
este punto junto con una pequeña introducción a los shaders y a la
de reducir el número de fotogramas por segundo.
creación de efectos de sonido.
La opción Fullscreen permite que la aplicación se reproduzca a
pantalla completa. Observa que al seleccionar la opción, las
resoluciones de pantalla cambian acorde a las posibilidades de tu
monitor y tu tarjeta gráfica. Por último, la opción VSync tiene un
efecto similar al que ya estudiamos en los motores 2D: limita el
volcado de imagen renderizada en la memoria de vídeo al momento
en que no se está dibujando en pantalla, evitando un posible efecto
de parpadeo.

Deberías ver un cubo azul de frente. Puedes moverte por el mundo


Mundo.
virtual se pueden usar las teclas W, A, S y D como si fuera un juego
Los motores 3D intentan aproximarse mejor al mundo real que los
de disparo en primera persona. Con el ratón podrás cambiar la
juegos 2D. De hecho, estos motores trabajan con el concepto
dirección hacia donde miras. Otras teclas útiles son Q (subir), Z
de mundo (world, en inglés), que es un espacio virtual que contendrá
(bajar), C (mostrar en la consola información sobre la cámara) y M
todos elementos que se pueden visualizar y con los que el usuario
(mostrar estadísticas de uso de memoria en la consola). Para salir
puede interactuar.
del juego podemos pulsar la tecla ESC.

Dichos objetos tendrán un aspecto y una forma determinados.


Además, podremos moverlos a nuestro antojo, escalarlos,
deformarlos, hacerlos desaparecer o reaparecer; todo según LAS UNIDADES QUE SE USAN EN JME3 SE DENOMINAN WU (WORLD UNITS O
ordenemos desde el código específico. UNIDADES DE MUNDO). NORMALMENTE SE CONSIDERA QUE 1 WU ES
EQUIVALENTE A 1 METRO Y HAY QUE INTENTAR RESPETARLO CUANDO CREEMOS
De igual forma que los seres humanos podemos observar
NUESTRO MUNDO, ESPECIALMENTE SI ACTIVAMOS EL MOTOR DE FÍSICAS. PERO
únicamente una parte del mundo en un momento dado (la que está
NO TE CONFÍES, LA EQUIVALENCIA REAL DEPENDE DE LO QUE ESTABLEZCAN EL
frente a los ojos), cuando representamos el mundo 3D en una
DISEÑADOR Y EL PROGRAMADOR.
pantalla 2D se verá una solamente una parte de éste. Como vimos
anteriormente, a ese proceso se denomina proyección y depende del
Vectores.
punto de vista que tengamos del mundo.
Un vector tiene un aspecto similar a un conjunto de coordenadas,
A diferencia de la mayoría de los motores 2D, aquí no tendremos por ejemplo (2,-3,-1), pero, a diferencia de estos últimos, un vector no
que dibujar manualmente los elementos, sino que el motor se determina una posición en el espacio, sino una dirección.
encargará de controlar qué elementos componen el mundo y
Puedes imaginarte un vector como una flecha que parte del origen
mostrará solamente aquellos que entren dentro del punto de vista
(0,0,0) y termina en la posición indicada por sus coordenadas.
del jugador.
Se utilizan vectores, por ejemplo, para indicar hacia donde se dirige
¿Cómo gestiona un motor 3D la lista de elementos de una forma
el movimiento de un objeto o hacia donde apunta una cámara o una
eficiente? Mediante una estructura de datos llamada grafo de
luz.
escena, que veremos en gran detalle en la próxima sección.

Dada la complejidad de un mundo de estas características, que


puede tener cientos de objetos, lo normal es que se diseñen usando
herramientas de modelado para luego ser cargados por el motor. A
estos assets se les denomina escenas. El código específico podrá
luego personalizar los contenidos si es necesario.

Sistemas de coordenadas
Las coordenadas permiten localizar un punto. Los sistemas de
coordenadas, por tanto, nos dicen cómo situarnos en el espacio. En
En la ilustración, a representa el vector de coordenadas (ax, ay, az)
el caso de jME3, se usa el sistema de tres dimensiones de la figura.
que está dibujado en color negro. Ahí también se pueden apreciar 3
Todas las coordenadas son relativas al origen: el punto (0, 0, 0). Como
vectores de color azul, rojo y verde: se corresponden con los vectores
ves, se necesitan 3 valores para concretar una posición: uno para el
(1,0,0) , (0,1,0) y (0,0,1) e indican la dirección de los ejes X, Y y Z
eje X (valores positivos hacia la derecha), otro para el eje Y (valores
respectivamente.
positivos hacia arriba) y otro para el eje Z (valores positivos saliendo
de la pantalla hacia ti). Recuerda: a pesar de su aspecto similar, unas coordenadas se
refieren a un punto en el espacio concreto mientras que un vector
El punto origen es donde coinciden todos los ejes y, como hemos
se refiere siempre a una dirección.
dicho antes, se corresponde con el conjunto de coordenadas (0, 0, 0).
Por ejemplo, si el origen fuera la parte central de la superficie de tu Aprovecharemos para presentarte uno de los objetos más utilizados
pantalla, el conjunto (-2, 2, 2) haría referencia a un punto situado en en jME3: com.jme3.math.Vector3f. Esta clase es capaz de trabajar
el cuadrante superior izquierda de la pantalla y que sobresaldría de con grupos de 3 coordenadas y puede usarse para almacenar una
la superficie. posición o un vector y realizar operaciones con ellos.

Es importante destacar que un conjunto de coordenadas es Vector3f v1 = new Vector3f( -20, -30, 40 );
simplemente una posición en el espacio, no debes confundirlo con el
concepto de vector que veremos más adelante. Sus métodos son bastante numerosos: calcular distancia respecto
a otro punto, multiplicaciones vectoriales o escalares, proyecciones,
sumas, etc. Todas ellas son muy interesantes si quieres meterte a
fondo con las matemáticas que hay detrás del mundo de las 3D,
pero nosotros utilizaremos unas pocas solamente.

Hay algunos objetos estáticos de este clase que son muy útiles. Por
ejemplo Vector3f.ZERO equivale al origen o, lo que es lo mismo, las
coordenadas (0, 0, 0).

Vértices, polígonos y mallas.

Ya vimos hace un par de unidades que los objetos que puedes ver
Reflexiona en un juego 3D están generalmente formados por
una malla (mesh) de polígonos. Estos polígonos están conectados
Cuando decimos que un punto está en la posición (1, 0, 0), ¿cuánto
entre sí como una red y pueden definir formas muy complejas.
de lejos estamos del origen? ¿1 centímetro? ¿1 metro? ¿1 kilómetro?
Fíjate en que incluso las superficies curvas como una esfera se divide Por ejemplo, fíjate en la moto y las dos personas que están a su
en polígonos para poder dibujarse: izquierda; los tres son geometrías. Cada uno de ellos dependen de
un nodo de color azul. Supón que sus posiciones reales en el mundo
están establecidas de tal manera que parezca que están sentadas
sobre la moto. Si muevo el nodo del que dependen, el de color rojo, el
cambio afectará de igual forma a los nodos azules y estos, a su vez,
a las geometrías que cuelgan de ellos. ¿El resultado? La moto y sus
ocupantes se moverán a la vez a la nueva posición. Esto ahorra
tiempo y código al programador.

Los polígonos están, a su vez, definidos por los vértices (puntos en Cada spatial es, por tanto, relativo a su spatial padre, del cual
3D) que los componen. Como mínimo, para definir un polígono se depende directamente. Ejemplo: puedo construir una esfera cuyo
necesitan tres vértices. El tipo más común de polígono en el mundo centro esté en la posición (0,0,0). Esa coordenada recibe el adjetivo
3D son los triángulos, los cuáles poseen tres vértices. de "local" porque solo tiene significado dentro de la geometría. Sin
embargo su posición real en el mundo dependerá de la que tengan
Cuando se utiliza un programa de modelado en 3D es necesario los spatials de los que cuelga.
tener esto en cuenta para poder definir la forma del objeto que
deseamos implementar. Añadiendo spatials al grafo (I). Geometrías.
Abre el proyecto HolaMundo que creamos antes y abre el código
Grafo de escena. fuente desde la vista en árbol de la parte izquierda:
Ya hemos explicado que un motor 3D basa su funcionamiento en la
construcción de un mundo al cual vamos añadiendo elementos. Con
el fin de gestionarlos de forma rápida y eficiente, internamente se
almacenan en forma de árbol. Esto nos facilita la clasificación de los
elementos en categorías y permite establecer las relaciones que
existen entre ellos.

Observa esta ilustración:

Ahora fíjate especialmente en esta sección de código que se


corresponde con el método simpleInitApp() :

Eso es un grafo de escena! En jME3 los objetos que forman parte


del grafo se denominan spatials. Todos ellos dependen de rootNode,
que está arriba del todo.

En la figura puedes observar que los spatials pueden ser de dos Analicemos el código y veamos cómo casa con el grafo de escena:
tipos: nodos y geometrías.

• Los nodos (nodes) están representados por cuadrados en


el dibujo. Su característica más interesante es que de ellos
podemos "colgar" (attach) otros spatials, formando una
La primera de estas líneas crea una caja de tamaño 1x1x1 cuyo
jerarquía; también podemos desacoplarlos
centro es la posición Vector3f.ZERO, es decir, las coordenadas (0,0,0).
(detach )cuando nos interese. Cualquier cambio que
Recuerda que las unidades se denominan wu, o world units.
hagamos en un nodo afecta a todo lo que cuelgue de él. A
ellos se les puede aplicar una transformación, como un La segunda línea crea un spatial spatial de tipo geometría con el
cambio de posición, un cambio de tamaño o una rotación. nombre “Box” y le asigna la forma del cubo que acabamos de crear.
• Las geometrías (geometries) vienen representadas por Pero con eso no basta: el objeto tiene forma pero no color ni nada
dibujos en la figura (el guerrero, la moto, el terreno, etc.) que defina su aspecto. Eso lo arreglamos añadiéndole un material:
y son objetos 3D visibles, con una forma determinada
(mesh) y un aspecto (material). A este tipo
de spatials también se les puede aplicar una
transformación.
a rootNode. Con esto hemos conseguido que cualquier
transformación que hagamos al nodo “Pivote” se propague
automáticamente a las geometrías que dependen de él.

Las variables geom2 y n las hemos creado como miembros


privados del objeto en vez de como variables locales porque más
Luego veremos los materiales en detalle, pero podemos adelantar
adelante las usaremos en otros métodos.
que lo que hemos hecho con la primera línea es indicarle que
queremos un material que no necesita iluminación para verse. En
la siguiente línea cambiamos una propiedad del material, en este
caso el color a una tonalidad azul. Prueba con otros colores: si borras
la palabra Blue y pulsas Ctrl+Espacio se te mostrará el desplegable
de autocompletado del IDE con muchos colores donde elegir.
Finalmente asignamos el material a la geometría que acabamos de
crear.

rootNode.attachChild(geom);
Reflexiona
La última línea indica al motor que queremos añadir la geometría
al nodo raíz, rootNode. Dado que todo lo que cuelga ¿Qué ocurriría si añadimos justo antes de asignar el material
de rootNode forma parte del mundo, lo que acabamos de hacer es a geom2 la siguiente línea?
incorporarlo a este último: por eso podemos verlo.
mat.setColor("Color", ColorRGBA.Pink);
Añadiendo spatials al grafo (II). Nodos.
QUE TANTO EL CUBO COMO LA ESFERA SE VUELVEN DE COLOR ROSA. QUIZÁS
Añade los siguientes imports imports al proyecto:
TE PREGUNTES POR QUÉ EL CUBO NO SE VE AZUL SI LE ASIGNAMOS EL MATERIAL

CUANDO TENÍA ESE COLOR. EL MOTIVO ES QUE LO QUE ASIGNAMOS A LAS


GEOMETRÍAS SON REFERENCIAS AL OBJETO QUE CONTIENE EL MATERIAL Y NO

EL MATERIAL EN SÍ. POR ESO, CUANDO CAMBIAMOS ALGUNA PROPIEDAD DEL


MATERIAL SE REFLEJA INMEDIATAMENTE EN TODOS LAS GEOMETRÍAS QUE
Y cambia el método simpleInitApp() por éste:
HAGAN REFERENCIA A ELLA.

Actualizando el mundo.
Si volvemos la vista atrás a los motores 2D, seguramente recuerdes
que el bucle de juego (game loop) en Slick alternaba entre llamadas
a dos métodos: update() y render(). En el primero, actualizábamos
la posición de los sprites, leíamos el teclado, etc., en resumen:
implementábamos la lógica del juego (game logic). En el segundo
dibujábamos todos los elementos.

Algo similar ocurre en jME3. Aquí tenemos un método


llamado .simpleUpdate(float tpf) donde podemos implementar la
lógica de juego. En lugar de recibir como parámetro el número de
milisegundos transcurridos desde la última llamada, aquí nos
proporcionan el tiempo expresado en segundos que durará
el frame actual. Todos los cambios que hagamos a los objetos
tendrán que ser proporcionales a tpf para evitar que el juego se
ejecute más rápido o más lento según la máquina.

Vamos a aprovecharlo para hacer transformaciones a


los spatials de nuestro grafo:

Lo que hemos hecho es crear otra forma, esta vez una esfera de
radio 1 wu (los otros parámetros del constructor se verán más
adelante) y asignarlo a una nueva geometría a la que le aplicaremos
Concretamente estamos indicando al nodo n que queremos rotarlo
el mismo material del cubo.
en el eje Y (el vertical). Como las dos geometrías usan como
Y fíjate: en lugar de incorporar las geometrías directamente referencia al nodo n, cuando lo rotemos, ellas lo acompañarán.
a rootNode lo hemos hecho en un nuevo nodo que hemos llamado
Ejecútalo, y comprobarás apenas se aprecia la rotación en el cubo y
“Pivote”. Finalmente, hemos añadido el nodo recién creado
nada en la esfera. ¿Qué hemos hecho mal? Nada, en realidad están
rotando correctamente pero no lo vemos claramente ya que el tecla espacio, pulsando el botón izquierdo del ratón, girando
material que hemos asignado a las geometrías es siempre un color el joystick a la derecha, pulsando el botón A del gamepad, etc.).
fijo. Cambia los parámetros de .rotate a (0, 2*tpf, 2*tpf) y lánzalo.
Veámoslo con un ejemplo: añade el siguiente import al código del
Al girar ahora también en el eje Z se apreciará la rotación sin
apartado anterior:
problemas.

Para verlo mejor hay que cambiar el material. Cuando creas la


instancia de la clase Material sustituye el parámetro por
"Common/MatDefs/Misc/ShowNormals.j3md" y ejecuta el proyecto.
Obtendrás un error de ejecución, y es que el material que acabamos y modifica esta parte del código:
de añadir no tiene una propiedad “color”, así que comenta las
líneas mat.setColor() y prueba de nuevo. Este material ilumina las
caras del objeto con un color distinto según la dirección hacia donde
miren, con lo que la rotación debe verse perfectamente.

Si en lugar de aplicar .rotate() sobre n lo hacemos sobre geom2 la


transformación sólo afectará a esa geometría.

Otra forma de ver bien cómo está hecho un objeto es activar la


vista wireframe; esto provoca que en vez de colorearse los polígonos
Ejecuta el código. En principio no pasará nada, pero si pulsas la barra
del material simplemente se muestran sus lados. Puedes activarlo
espaciadora o el botón izquierdo del ratón, verás que el pivote
para cualquier material añadiendo esta
comenzará a girar. Al finalizar la pulsación, dejará de girar.
línea: mat.getAdditionalRenderState().setWireframe(true);mat.get
AdditionalRenderState().setWireframe(true); Cambiando true por f Lo que hemos hecho es definir una acción, “Rotar” y la hemos
alse recuperamos el comportamiento normal del material. asociado a la pulsación de la barra espaciadora y al botón del ratón.
La línea inputManager.addListener() indica que queremos que el
Reflexiona
evento rotar sea procesado analógicamente por un objeto
¿Qué cambios tendrías que hacer para rotar solamente el cubo? llamado analogListener,que es definido justo abajo. Pronto veremos
que significa eso de “analógicamente”. Dentro del
Basta con aplicar geom.rotate(), pero para que funcione, antes, método .onAnalog() de dicho objeto compararemos el nombre de la
tenemos que sacar la declaración de geom del acción para asegurarnos de que es “Rotar”. Esto nos permite
método simpleInitApp() y hacerla un miembro de la clase. Si no lo reutilizar el mismo objeto para múltiples acciones.
hacemos, desde simpleUpdate() no podremos acceder a la
referencia. Creación de mappings.
En el ejemplo anterior hemos
Controles de usuario. usado inputManager.addMapping() para crear una acción (en este
Para que el jugador pueda participar en nuestro mundo tenemos caso de nombre “Rotar” y le adjuntamos dos disparadores: uno de
que permitir que sus acciones sean procesadas por el motor. De tecla y otro de botón del ratón. A ese proceso se
hecho, como ya vimos antes, existe un comportamiento por defecto llama mapping en jME3 y, genéricamente hablando, binding en la
en jME3 que permite al jugador moverse por el mundo usando las gran mayoría de los motores. Los disparadores se van añadiendo
teclas W, A, S, D para desplazarse y el ratón para cambiar la detrás del nombre de la acción, pudiendo colocar tantos como
dirección. necesitemos.

En la mayoría de motores de juegos 3D, jME3 incluido, se suele usar Puedes añadir varios disparadores de la misma clase, y no estamos
el concepto de action (acción) y de binding (atadura). Básicamente limitados a los del ejemplo:
se trata de definir las posibles acciones que puede realizar el
usuario: saltar, moverse hacia adelante, disparar, cambiar de arma,
etc. y luego asociar cómo las puede lanzar el jugador (pulsando la
Clase de disparador Descripción
KeyTrigger(int tecla) Pulsación de una tecla del teclado KeyIn-
put.KEY_W, .KEY_P, .KEY_2, .KEY_NUM-
PAD2 (el 2 del teclado numérico), etc. Pulsa
Ctrl+Espacio detrás de KeyInput.en el IDE para
verlos todos.
MouseButtonTrigger(int bo- Pulsación de un botón del ratón. El parámetro
ton) boton puede ser el botón izquierdo (MouseIn-
put.BUTTON_LEFT), derecho (.BUTTON_RIGHT)
o central (.BUTTON_CENTER).
Materiales.
MouseAxisTrigger(int eje, Movimiento creciente en uno de los ejes del
Los objetos que coloquemos en nuestro mundo virtual tienen que
boolean invertido) ratón. La variable eje dice qué se controlará:
tener una forma (que se deriva de su malla) y un material que
el eje vertical (hacia arriba, MouseIn-
define su aspecto. Todos los materiales en jME3 son instancias de
put.AXIS_Y), el horizontal (hacia la dere-
la clase Material y existen algunos predefinidos que pueden usarse
cha, .AXIS_X) o la rueda de desplazamiento
(.AXIS_WHEEL). Si invertido vale true, el dis- de base para construir los nuestros.
parador se lanzará cuando disminuya la posi-
Empezaremos cambiando los materiales de nuestro HolaMundo.
ción en dicho eje, es decir, en el caso del eje
Éstas son las líneas que definen el material:
vertical será cuando vaya hacia abajo o en el
caso del eje horizontal cuando se mueva a la
izquierda.
JoyButtonTrigger(int joy, int Similar a MouseButtonTrigger pero
boton) para joysticks y gamepads. Hay que especifi-
car primero en joy el identificador del disposi-
tivo. boton será el identificador del botón que
Si para ver mejor la rotación cambiaste el material a
queremos controlar.
JoyAxisTrigger(int joy, int eje, Similar a JoyButtonTrigger pero para joysti- "Common/MatDefs/Misc/ShowNormals.j3md" y comentaste la línea
boolean invertido) cks y gamepads. Hay que especificar primero donde se establecía el color, vuelve a dejarlo todo como aparece ahí
en joyel identificador del dispositivo Los ejes arriba.
aquí serán JoyIn-
Lo primero que tendríamos que preguntarnos es qué es la
put.AXIS_POV_X y .AXIS_POV_Y.
variable assetManager, pues no aparece en ningún otro sitio del
código fuente. Este objeto nos permite acceder a los recursos de
Respecto a la generación de eventos nuestro juego, realizando la búsqueda del asset que le solicitemos
con inputManager.addListener() hay que destacar que existen dos en todos los sitios que hayamos configurado previamente. Por
tipos de objetos receptores de defecto buscará en la carpeta assets de nuestro proyecto y en otras
notificaciones: AnalogListener y ActionListener. rutas internas del motor que contienen datos predefinidos. El
material concreto que hemos pedido es uno de esos datos que
Existen varias diferencias entre ambos: La primera y fundamental
vienen preinstalados con el motor y tiene la particularidad de que
es que AnalogListener se llamará mientras el disparador
no necesita iluminación de ningún tipo para ser visible. Si queremos
permanezca activo; ActionListener, por otro lado, sólo se llamará
que al material le afecten las fuentes de luz
una única vez cuando se detecte el disparo y otra vez cuando cese.
usaremos Common/MatDefs/Light/Lighting.j3md. En la tarea
La segunda es que AnalogListener recibirá un valor de intensidad
veremos ese material con más detalle.
de disparo, mientras que ActionListener sólo indicará si existe
disparo o no. Cada material tiene unas propiedades propias y debemos
conocerlas porque si intentamos cambiar un valor en una de ellas
También varían el nombre de los métodos llamados y sus
y el material no la soporta provocaremos un error de ejecución que
parámetros:
detendrá el juego en seco.

• AnalogListener.onAnalog(String accion, float valor, float Del material predefinido Unshaded.j3md vamos a destacar dos
tpf): el parámetro valor indicará el grado de intensidad del propiedades que son muy interesantes: "ColorMap" y "Color". Esta
disparo y puede tomar muchos valores (un joystick puede última indica el color que tendrán todos los polígonos que forman
estar hacia arriba del todo o sólo hasta la mitad). la malla del objeto. Si no queremos restringirnos a usar los colores
• ActionListener.onAction(String accion, boolean pulsado, estándar que vienen en el motor, podemos crear el nuestro propio.
float tpf): el parámetro pulsado indicará si es una llamada Aprovechamos la ocasión para explicar cómo podemos definir
para indicar que el control se ha pulsado o bien para colores enjME3: se hace con la clase ColorRGBA y el color se consigue
indicar que ha dejado de pulsarse. mezclando los tres colores luz primarios: el rojo, el verde y el azul.

En ambos casos se incluye el parámetro accion para indicarnos el mat.setColor("Color", new ColorRGBA(rojo, verde, azul, alfa));
nombre de la acción que ha provocado el disparo y tpf para
indicarnos el tiempo que dura el frame actual y cuyo valor tenemos Cuenta bien los parámetros. Hay uno, alfa, que no conocemos aún.

que considerar de cara a las actualizaciones. En el campo de la infografía, el concepto de alfa es equivalente a
transparencia. Un color que tenga un alfa menor que 1 es traslúcido, Cuidado: si el modelo que queremos importar no tiene material será
es decir, dejará entrever lo que hay detrás de él. Lamentablemente obligatorio asignarle uno antes de incluirlo en el grafo de escena.
el material Unshaded ignora el valor del parámetro alfa. Puedes usar el material
base "Common/MatDefs/Misc/ShowNormals.j3md"Common/MatDe
Volviendo a los colores, un valor de 0 en uno de los tres colores
fs/Misc/ShowNormals.j3md" con este tipo de objetos para
primarios implica que no aporta nada al color final, mientras que
visualizarlo si no quieres complicarte mucho.
un valor de 1 indica que está totalmente presente en la mezcla. Por
ejemplo, un color(1,0,0) será un rojo puro, un color(1,1,1) será blanco al Vamos a importar el modelo de una silla de madera. Crea un nuevo
sumar todos los primarios, un color (0,0,0) será negro por la proyecto e incluye este código en el
ausencia de colores y un color (0.5f, 0.5f, 05f) será un gris método simpleInitApp()simpleInitApp():
intermedio ya que todos los colores aportan la mitad de su valor
máximo.

La otra propiedad del material Unshaded es "ColorMap", a la que hay


que asignar una textura, que es una imagen 2D que el motor gráfico
utilizará de una forma determinada para darle el aspecto al
material. En este caso, la textura que asignemos se utilizará como
una pegatina que se pega a la superficie de los polígonos.

Cambia la línea mat.setColor() por la siguiente:

Si intentas ejecutar el programa dará un error de ejecución porque


no encuentra la textura entre los assets de tu juego. Para arreglarlo
vete a las propiedades del proyecto e incorpora la librería jme3-test-
data. Con eso estás incluyendo en tu proyecto ése y
muchos assets más que usaremos más adelante.

Modelos.
Ya hemos visto que a los spatials de tipo geometría se les puede
asignar una forma. Esa forma está compuesta de polígonos que El fichero contiene el modelo de la silla en formato Blender y en
forman una malla (mesh) y sobre ellos es donde se aplica el formato Ogre3D, acompañados por sus texturas y luces.
material. Lo normal no suele ser crear las mallas desde el código Aunque jME3 puede leer este último tipo de formato, el método de
específico sino obtenerlas de un recurso. A ese recurso le llamamos trabajo recomendado es convertirlo al formato nativo de jME3, de
modelo. extensión .j3o. ¡Cuidado! Cuando se despliegue el proyecto no se
copiarán los modelos y escenarios que no estén en formato .j3o. La
Los modelos puede constar solamente de la malla, aunque también
conversión se realiza haciendo clic con el botón derecho
puede acompañarle el material que lo recubre o, como veremos más
sobre chair.scene en el árbol del proyecto y seleccionando la opción
adelante, animaciones que cambian la forma dinámicamente
“Convert to j3o Binary”.
cuando se lo indicamos.

Para crear un modelo, lo normal es usar un programa de modelado


con 3D Studio Max, Maya o Blender. Este último es software libre y
existen mecanismos para integrarlo de forma sencilla con jME3.

Importando un modelo.
Incorporar modelos a nuestro proyecto es algo muy sencillo. Lo
primero es copiar el modelo que queremos usar dentro de la
carpeta assets/Models de nuestro proyecto. Dado que normalmente
un modelo puede incluir no sólo la malla sino también sus texturas
e incluso animaciones, suele componerse de varios ficheros. Es por
ello que de cara al mantenimiento del proyecto se recomienda que
cada modelo tenga su propia subcarpeta dentro de Models; la única
excepción obvia es tener varios modelos que compartan materiales,
pues almacenarlos en la misma carpeta ahorra espacio. La carga de Las líneas que están detrás de rootNode.attachChild(silla) sirven
escenas se realiza exactamente igual que con los modelos, pero por para crear una luz de ambiente que nos permita ver el modelo con
motivos de organización se recomienda almacenarlas claridad. Pronto hablaremos de la iluminación.
en assets/Scenes.
Reflexiona que pueden aplicarse a cualquier Spatial, lo que incluye las
clases Node y Geometry.
Habrás comprobado que la silla tiene un tamaño considerable.
¿Recuerdas qué tipos de transformaciones se podían aplicar a Iluminación.
un spatialspatial? ¿Qué transformación podrías realizar para que Tal y como sucede en el mundo real, algunos materiales no se
sea más pequeña la silla? pueden apreciar si no son iluminados por una fuente de luz. Además,
también existen muchos tipos distintos de luces: luz ambiente, luces
PRINCIPIO DEL FORMULARIO
direccionales (como los focos), etc. Cada una de ellas ilumina de una
LAS TRANSFORMACIONES ERAN TRASLACIÓN, ROTACIÓN Y ESCALA. EN ESTE forma distinta los objetos de una escena.
CASO, SERÍA LA ÚLTIMA DE ELLAS. PODRÍAMOS AÑADIR SILLA.SCALE(0.4F); QUE
Recuerda también que hay varios tipos de materiales base que no
REDUCE EL TAMAÑO DEL MODELO A UN 40% DEL ORIGINAL.
necesitan iluminación, como por ejemplo Unshaded y ShowNormals.

Haz pruebas con las tres transformaciones usando los Estos son las clases que implementan los tipos de luces soportados
métodos silla.move(), silla.rotate() y silla.scale(). Con el
por jME3:
autocompletar del IDE comprobarás que algunas de ellas admiten
varias alternativas de parámetros a la hora de llamarlas. Recuerda
Tipos de luz Descripicón y ejemplos de uso
PointLight Tiene 3 propiedades: localización, radio y color. Brilla en todas las direcciones alrededor de su posición hasta llegar al radio. La
intensidad decrece conforme nos alejamos del centro. Es útil para simular bombillas, antorchas, velas, etc. Puede lanzar sombras.

Coloca una luz de color verde en el origen que ilumina hasta 3 metros alrededor.
DirectionalLight Tiene 2 propiedades: color y dirección.Ilumina toda la escena desde la dirección indicada. Todos los rayos de luz vienen paralelos,
como si vinieran de una fuente que está a distancia infinita. Permite simular el efecto de un sol, por ejemplo. Puede lanzar
sombras.

Coloca una luz blanca que viene de la dirección (-1, -1, -1).
SpotLight Tiene 5 propiedades: localización, dirección, color, ángulo interior y ángulo exterior. Es una luz similar a la de un foco: el ángulo
interior marca el máximo de luz y el exterior donde deja de iluminar. Permite simular el efecto de una linterna, un foco o una
farola.

Coloca un foco que ilumina de color blanco lo que tiene delante la cámara hasta 100 metros y está entre 5 y 45 grados alrededor
del centro de luz.
AmbientLight Tiene un único parámetro: el color. La luz ilumina toda la escena globalmente, brilla igual por todos lados, por eso no suele usarse
sola ya que los resultados no son naturales. No crea sombras. Si el color no es blanco puede alterar el color de los demás
materiales de forma general.

Crea una luz ambiente de color amarillo.

Como habrás podido comprobar, para añadir una luz al mundo Reflexiona
utilizamos el método rootNode.addLight() .
Con rootNode.removeLight() podemos retirarla del grafo. Hay otros controles que son muy útiles. Busca en la
documentación Javadoc para ver los detalles.
Moviendo luces y proyectando sombras.
• ChaseCamera(Camera cam) permite a la
Si quieres que una luz se mueva por el mundo se puede hacer de
cámara cam seguir al spatial desde una corta distancia y
dos maneras: posicionándola manualmente en dentro del
permite rotar la vista con el ratón. Puedes usarlo para
método simpleUpdate() o bien indicándole que siga
juegos en tercera persona.
automáticamente al spatial que le indiquemos.
• RigidBodyControl, CharacterControl, VehicleControl, Ghost
El segundo procedimiento se realiza mediante un Control y RagDollControl hacen que el spatial al que lo
objeto LightControl, que es de tipo Control. Este tipo de objetos asignemos responda de forma realista a las fuerzas que
permiten asociarse a un spatial de manera que controlan le apliquemos, las colisiones con otros objetos y le afecte
transformaciones de forma autónoma. En el caso concreto la gravedad. Los veremos en detalle más adelante.
de LightControl, cuando se asocia a un spatial éste actualizará la • MotionTrack() permite que un spatial siga un camino
posición de la luz sin nuestra intervención: determinado de forma automática.

Por defecto la proyección de sombras está desactivada ya que


consume gran cantidad de potencia de cálculo. En caso de que te
interese introducirla en tu proyecto, puedes usar uno de estos tres
generadores de sombras:

Tipos de luz Descripicón y ejemplos de uso


BasicSha- Dibuja sombras rápidamente sin mucho coste de procesador. Están optimizadas para proyectar sombras en superficies planas. El
dowRende- segundo parámetro del constructor es el tamaño del mapa de sombras, cuanto mayor sea mejor resolución tendrán las sombras
rer pero también aumentará el coste computacional y de memoria.

Proyecta sombras desde la dirección (-1, -1, -1).


PssmSha- Las sombras se generan usando un algoritmo denominado Parallel-Split Shadow Map. El resultado es mucho más realista incluso
dowRende- en superficies curvas. Consume más recursos que el anterior. El tercer parámetro del constructor es el número de mapas dibujados:
rer a mayor número mejor calidad y menos rendimiento.

Proyecta sombras desde la dirección (-1, -1, -1).


Dado que los recursos de cálculo son limitados, se recomienda Acabamos de obtener un nuevo canal de animación del controlador
desactivar la proyección de sombras para todo el grafo y activar y le hemos instruido a que ejecute la animación “stand” (poner de
selectivamente aquellos spatials que nos interesan según deban pie). Las animaciones tienen un nombre que se asignan desde el
proyectar y/o recibir sombras. Esto se hace con el programa de modelado. En el caso concreto del ejemplo, las
método spatial.setShadowMode(modo), donde modo puede animaciones que están definidas son “Dodge” (cubrirse), “Walk”
ser ShadowMode.Off si no proyecta ni recibe, .Cast si sólo proyecta, (andar), “stand” (ponerse de pie), “pull” (tirar) y “push” (empujar). Dado
.Receive si sólo recibe o .CastAndReceive si hace las dos cosas que el modelo ya estaba de pie no notaremos cambio alguno.
(cuidado, consume muchos recursos).
Las últimas líneas de simpleInitApp() añaden una acción
Opcionalmente se puede activar la oclusión ambiente para llamada "Andar" y le asignan como disparador la tecla espaciadora.
complementar los generadores anteriores. Lo que hace es calcular Cuando se pulsa se llama al método onAction() de actionListener,
las sombras que los objetos cercanos se lanzan entre sí: que ejecuta las siguientes líneas:

Animaciones.
Existe un tipo de modelo especial: los modelos animados. En lugar
de contener una forma estática, los modelos animados tienen una
geometría variable que les permite cambiar su forma suavemente
según las instrucciones del código específico. Veamos un ejemplo.
Crea un nuevo proyecto, añade la librería jme3-test-data y usa el
código fuente que aparece al final del apartado.

Ejecútalo y pulsa la barra espaciadora. ¡Genial! ¿Pero cómo funciona?


Analicemos primero el método simpleInitApp():

AnimControl control = modelo.getControl(AnimControl.class);

Esas línea obtiene una referencia al objeto que controla la animación Recuerda que el método se llamará en dos ocasiones: una cuando
del spatial donde hemos cargado modelo. pulsemos la barra espaciadora (keyPressed valdrá true) y otra vez
cuando la soltemos (keyPressed será false). En el primer caso
Reflexiona
querremos activar la animación “Walk” (andar) y en el segundo la
Nosotros no lo necesitamos para el ejemplo, pero opcionalmente animación “stand” (de pie).
podríamos haber usado el método control.addListener() para añadir
Sea cuál sea la acción, primero comprobaremos que no se esté ya
un objeto de tipo AnimEventListener al controlador. De haberlo
ejecutando la animación que deseamos. Si es así, le decimos al canal
hecho, el controlador habría llamado a los métodos de dicho objeto
que comience con la nueva animación, pero que realice una
en distintos momentos de la animación (por ejemplo, cuando
transición de la posición actual del modelo hasta la nueva que dure
terminara o cuando cambiara de una animación a otra).
medio segundo. Así el cambio será suave. Con la última línea
Dado que podemos tener más de una animación funcionando sobre indicamos cuántas veces queremos repetir la animación. Si
un mismo modelo (por ejemplo, el brazo de la izquierda está ponemos LoopMode.Loop, la animación se repetirá
blandiendo una espada mientras que la mano derecha está automáticamente hasta que la cambiemos o paremos.
moviendo un escudo), las animaciones se ejecutan en canales. Cada Con LoopMode.DontLoop se ejecutará una vez y parará.
nodo puede tener más de un canal de animación simultáneo, pero
cada canal sólo admite una animación. Fíjate: Físicas y colisiones
jME3 incluye un motor de físicas llamado JBullet que se integra
perfectamente en nuestro mundo virtual. Por defecto viene
desactivado e incluso una vez activado será necesario indicarle qué
geometrías queremos controlar. El motivo es que la capacidad de
cálculo que sería necesaria para simular todas las propiedades
físicas incluso de una pequeña escena sería demasiado hasta para
un ordenador potente.

El motor de JBullet es un simulador de física newtoniana en el que


se pueden aplicar fuerzas a los objetos; luego, éstos pueden chocar
entre sí transmitiéndose energía entre unos y otros de una forma
realista.
Para activar el motor basta con escribir crear una propiedad de la gravedad. Los objetos dinámicos pueden apoyarse en este tipo de
tipo bulletAppState: objetos, lo que los hace útiles para implementar ascensores o
lanzadores. Para convertir un objeto en cinemático hay que ejecutar
private BulletAppState bulletAppState;
el siguiente método:
y añadir dos líneas a simpleInitApp():
objeto_phy.setKinematic(true);

Detectando colisiones
Como es lógico, en la mayoría de los juegos necesitaremos detectar
las colisiones que se produzcan entre los objetos. jME3 nos lo pone
La primera línea crea un simulador y la segunda lo acopla al bucle fácil, ya que el motor de físicas tiene que tenerlas en cuenta para
interno del motor de juegos. responder de forma natural al movimiento de los objetos.

Para incorporar una geometría al motor de físicas tendremos que Con este comando indicaremos al motor que queremos estar al
crear un objeto de control que conecte la geometría con un modelo corriente de las colisiones que se produzcan:
físico. Uno de los objetos de control más utilizados
es RigidBodyControl que considera la geometría como un cuerpo bulletAppState.getPhysicsSpace().addCollisionListener(collisionListe
sólido con una masa determinada. ner);

Donde collisionListener es una instancia de la


clase PhysicsCollisionListener que implementa el
método .collision(PhysicsCollisionEvent event). Cuando se produzca
una colisión, se llamará al método y mirando en el
parámetro event podremos saber qué nodos son los que han
En ese ejemplo, hemos asignado un control RigidBodyControl a la
chocado (event.getNodeA() y event.getNodeB()).
geometría objeto_geom. El motor de físicas tendrá en cuenta que
el objeto tiene una masa de 1 kilogramo (se pasa como parámetro Por ejemplo, el siguiente objeto mostrará por consola las colisiones
al constructor) para sus cálculos. ¿Por qué la masa del objeto es que ocurran:
importante? Pues porque cuanto mayor masa tenga, más esfuerzo
tendremos que realizar para moverlo tal y como ocurre en la
realidad. De la misma forma, dos objetos que vayan a la misma
velocidad producirán efectos muy distintos al chocar contra un
mismo obstáculo si uno de ellos tiene 10 veces más masa que el
otro.

La segunda línea asocia el controlador de físicas a la geometría. Con Ten en cuenta que el suelo y las paredes cuentan como nodos que
esta asociación, el controlador podrá modificar la posición y la pueden colisionar y cualquier objeto que esté apoyado en ellos estará
rotación del objeto respondiendo al entorno. Por último, mediante la emitiendo eventos de colisión continuamente hasta que esté en
última línea avisamos al motor de físicas que queremos que tenga reposo. Para evitarlo, comprueba los elementos devueltos
en cuenta un nuevo objeto en sus cálculos. Cuidado: en el momento por .getNodeA() y .getNodeB() para ignorar los eventos relacionados
que incorpores un objeto al simulador de físicas le afectará la con los objetos estáticos.
gravedad, como en el mundo real.
Juegos de disparo en primera persona.
¡Eso es todo! Por supuesto, existen otros tipos de controladores,
Uno de los géneros más extendidos que hacen uso de motores 3D
algunos de los cuáles veremos más adelante.
son los juegos de disparo en primera persona. jME3 tiene un soporte
Reflexiona especial para este tipo de juegos mediante el uso de algunas clases
que nos simplifican algunas tareas, como por ejemplo la
Quizás tengas curiosidad por ver cómo ve el motor de físicas los clase CharacterControl, que implementa un objeto especializado en
objetos que añadimos a la simulación: añade esta línea para acrtivar el control del jugador.
el modo de depuración:
Nuestra versión está traducida al español y un poco cambiada.
bulletAppState.getPhysicsSpace().enableDebug(assetManager); Puedes descargarla el archivo comprimido que incluye el main.java y
la escena town.zip.
JBullet distingue entre dos tipos de objetos: estáticos y dinámicos.
El que acabamos con el ejemplo es dinámico. Los objetos estáticos
tienen masa 0 y no se pueden mover. Un objeto dinámico, por otro
lado, reaccionará adecuadamente al chocar contra un objeto estático.
Por eso el suelo y las paredes suelen programarse como objetos
estáticos, lo que ahorra tiempo de cálculo.

Existe un tercer tipo de objeto: cinemático (del inglés kinematic). Es


un cuerpo rígido sólido con masa al que no le afectan las fuerzas ni
Ejecuta el proyecto y luego vuelve para que podamos analizar parte formas representadas. Por último, los vértices se conectan entre sí
del código fuente. formando formas primitivas que se renderizan como superficies.
Cada superficie se representa como un conjunto de píxeles, que
Fíjate en que el código usa una nueva forma de cargar assets. Se
antes de dibujarse en pantalla se pasan a través de un pixel shader,
pueden incluir en un archivo .zip y copiarlo en el raíz del proyecto.
conocido en OpenGL como fragment shader. En este último paso,
Con assetManager.registerLocator() añadimos dicho archivo en la
los píxeles pueden cambiar de color o de profundidad en función de
ruta de búsqueda.
lo dictado por el programa.
Otra novedad es cómo generamos la forma de colisión de la escena.
Estos procesos pueden realizarse secuencialmente varias veces
La siguiente llamada hará el cálculo por nosotros a partir de la
cambiando los programas en cada pasada. Una vez este proceso
referencia:
concluidos, se ordenan los puntos de todas las superficies según la

Ejecuta el proyecto y luego vuelve para que podamos analizar parte profundidad en la que se encuentran de forma que sólo se dibujan

del código fuente. los más próximos a la proyección. Es entonces cuando la imagen ya
estará lista para mostrarse en pantalla.
Fíjate en que el código usa una nueva forma de cargar assets. Se
pueden incluir en un archivo .zip y copiarlo en el raíz del proyecto. En el ejemplo de la figura se puede ver el efecto de aplicar un
Con assetManager.registerLocator() añadimos dicho archivo en la programa de cel shading a una escena. Esta técnica convierte una
ruta de búsqueda. proyección 3D en una simulación de dibujo plano 2D. Internamente,
se usan varios programas de shaders con el fin de obtener dicho
Otra novedad es cómo generamos la forma de colisión de la escena. efecto.
La siguiente llamada hará el cálculo por nosotros a partir de la
referencia: En jME3 se pueden implementar vertex shaders y fragment
shaders usando GLSL, que es el estándar de OpenGL para describir
formaEscena = CollisionShapeFactory.createMeshShape((Node) este tipo de programas. De hecho, jME3 usa
escena); internamente shaders para implementar los materiales y otros
efectos especiales como el agua. Lógicamente, las posibilidades son
Por último podrás comprobar que el jugador viene representado en
enormes, pero se salen fuera del ámbito del módulo profesional.
el motor de físicas por una forma de colisión de cápsula. Esta forma
es ideal para representar a jugadores que tienen que pasar por
Efectos de sonido.
sitios estrechos y subir peldaños, al menos desde el punto de vista
Usar sonidos en jME3 no presenta ninguna complicación. Es
de la colisión. La forma estará siempre en vertical, como es de
suficiente con cargar un asset que contenga el sonido y
esperar para el jugador. El primer parámetro del constructor
obtendremos un nodo que podemos situar en el grafo de escena.
de CapsuleCollisionShape es el radio, el segundo la altura y el tercero
el eje sobre el que la cápsula estará siempre apoyada (0=eje X, 1=eje Crea un proyecto nuevo e incorpora la librería jme-test-data.jar ya
Y y 2=eje Z). que contiene algunos sonidos de ejemplo. Para cargar un sonido
basta con ejecutar:
El control CharacterControl incluye un segundo parámetro que
indica el tamaño del escalón: todo desnivel inferior a esa altura
podremos subirlo sin necesidad de saltar. Las tres siguientes
llamadas .set establecen la velocidad del salto, la velocidad de caída
y la gravedad respectivamente.
El parámetro false indica que queremos cargar el sonido completo
en memoria. En caso de que el recurso sea una canción o algo
Shaders
excesivamente largo, hay que colocar true para que vaya leyendo el
Al comienzo, las tarjetas gráficas con aceleración 3D no tenían la
sonido poco a poco conforme se reproduzca.
posibilidad de modificar el comportamiento de los procesos de
renderizado (rendering pipelinerendering pipeline) lo cual limitaba Reflexiona
las opciones a la hora de implementar algunos efectos especiales.
Desde el año 2000, dicha limitación desapareció con la introducción ¿Por qué es lógico que un motor de juego 3D nos devuelva una
de los shaders. Los shaders son programas que se ejecutan en referencia de tipo Node cuando cargamos un sonido?
la GPU de la tarjeta gráfica y que controlan algunos de esos
DADO QUE LA ACCIÓN TOMA LUGAR EN UN ESCENARIO 3D, LO LÓGICO ES QUE
procesos.
EL SONIDO TAMBIÉN SE PUEDA LOCALIZAR ESPACIALMENTE. SI ASOCIAMOS LA
Cuando se quiere dibujar una escena, el motor 3D envía a la tarjeta REPRODUCCIÓN DE UN SONIDO A UN NODO LO PODREMOS COLOCAR EN LA

gráfica una secuencia de vértices. Dichos vértices son procesados POSICIÓN DEL GRAFO QUE NOS INTERESE (POR EJEMPLO, JUNTO A LA
uno a uno por un vertex shader donde pueden sufrir cambios. Para GEOMETRÍA DE UN TANQUE) Y SE MOVERÍA CON EL RESTO DE LOS ELEMENTOS

la ejecución de dicho shader sólo se dispone de la información ASOCIADOS. DE ESTA FORMA EL SONIDO PARECERÁ VENIR DE DONDE DESEAMOS.

aislada de dicho vértice, no se puede acceder a información


relacionada con los otros vértices o si quiera el orden de Un AudioNode tiene otros métodos interesantes:

procesamiento. A continuación, la lista de vértices cuyas propiedades


ha sido alterada pasa al geometry shader. Aquí sí puede alterarse
el número de vértices, lo que permite eliminar o añadir detalle a las
Método Descripción irá incluida en la distribución. Elige la categoría Application y rellena
GetStatus() Devuelve Status.Playing si el sonido está re- los campos:
produciéndose, .Stopped si está parado
o .Paused si está en pausa.
pause() Hace una pausa en la reproducción.
play() Inicia la reproducción, en paralelo a las ante-
riores si ya se estaba reproduciendo.
playInstance() Inicia la reproducción, en paralelo a las ante-
riores si ya se estaba reproduciendo.
setLocalTransla- Mueve la fuente de sonido a la posición indi-
tion(Vector3f cada en coordenadas locales.
pos)
setLooping(boo- Pasando un valor true indicamos que quere-
lean bucle) mos repetir el sonido indefinidamente.
setMaxDis- Establece la distancia máxima a partir de la
• Creación de un archivo .jar multiplataforma: basta con
tance(float dist) cual el sonido no será audible.
ejecutar la orden "Run | Build Main Project". En este caso
setMaxDis- Indica si queremos activar el audio posicio-
habrá que asegurarse que los assets están colocados en
tance(float dist) nal, es decir, que el volumen con el que se
su sitio. Para llevarse el juego a otro equipo será suficiente
escuche el sonido en cada altavoz dependerá
con copiar la carpeta dist del proyecto.
de la posición de la fuente respecto a la del
jugador. • Creación de un ejecutable nativo: esta opción está
setPositio- Indica si queremos activar el audio posicio- disponible para Windows, MacOS X y Linux. Hay que
nal(boolean posi- nal, es decir, que el volumen con el que se tener cuidado con aquellos assets que no estén dentro de
cional) escuche el sonido en cada altavoz dependerá la carpeta, pues puede que no se incorporen al ejecutable
de la posición de la fuente respecto a la del automáticamente. Para usar esta forma de distribución,
jugador. hay que ir a las propiedades del proyecto y elegir la
setPositio- Indica si queremos activar el audio posicio- sección Desktop. Finalmente, hay que marcar las
nal(boolean posi- nal, es decir, que el volumen con el que se plataformas para las que deseemos generar el ejecutable.
cional) escuche el sonido en cada altavoz dependerá Una vez ejecutado "Run | Build Main Project" aparecerán
de la posición de la fuente respecto a la del
en la carpeta dist.
jugador.
setPitch(float Establece la velocidad de reproducción del so-
tono) nido. El valor 1 es la velocidad original, 1.5 será
un 50% más rápido, mientras que 0.8 será
un 20% más lento.
setVolume(float Establece el volumen de reproducción, donde
vol)) 1 es el valor original.
setVolume(float Establece el volumen de reproducción, donde
vol)) 1 es el valor original.
stop() Detiene la reproducción.

No olvides que hay que adjuntarlo al grafo de


escena: rootNode.attachChild(audio);

Por otro lado, para que el motor pueda hacer sonar el audio
posicional de manera convincente, necesitamos especificar la Creación de un applet Java: si lo que queremos es ejecutar el
localización y dirección desde donde deseamos escuchar. Existe un proyecto en un navegador web, ésta es tu opción. Ten en cuenta que
objeto global llamado listener que almacena dichos datos. Si aquí también puedes encontrar problemas con los recursos
queremos que siga a la cámara, tendríamos que colocar las dos externos que no estén dentro de la carpeta assets. Además,
líneas siguientes en el método simpleUpdate(): un applet Java no puede capturar el ratón más allá de sus límites,
con lo que el comportamiento del movimiento del ratón puede a
veces ser sustituido por otros procedimientos (por ejemplo, pinchar
y arrastrar para mover la cámara). La generación del applet se
realiza de forma similar a los anteriores. La
Despliegue de una aplicación carpeta dist/applet contendrá los ficheros que has de subir a la
jMonkeyPlatform nos ofrece múltiples posibilidades a la hora de página web.
desplegar la aplicación: crear un archivo .jar multiplataforma, crear
ejecutables para una plataforma concreta o crear
un applet Java para los navegadores web. Lo primero es indicar los
datos del proyecto desde sus propiedades, dado que esa información
En la propia aplicación podemos acceder a una tienda como si de la
App Store se tratase, donde encontraremos multitud de recursos
gratuitos y de pago. Incluso podremos extender la herramienta
mediante plugins que obtendremos en dicha tienda.

Crear videojuegos en 3D con Android


Unity es un motor de videojuego multiplataforma creado por Unity
Technologies que se ha convertido en una plataforma muy popular
entre los desarrolladores de dispositivos móviles. Unity está
disponible como plataforma de desarrollo para Microsoft
Windows, OS X. La plataforma de desarrollo tiene soporte de
compilación con diferentes tipos de plataformas. Unity tiene dos
versiones: Unity Professional (pro) y Unity Personal.

Esta herramienta está accesible al público en diferentes versiones,


gratuita y profesional, cada cual con sus ventajas y limitaciones,
evidentemente la más completa es la profesional pero es necesario
hacer un desembolso que no todo el mundo puede permitirse y
sobre todo si estamos comenzando a utilizar dicha herramienta que
para ello hay una versión Beta para poder practicar de forma
gratuita que se puede descargar a través de la página web oficial
de Unity.

Estas versiones que he comentado están enfocadas para desarrollar


videojuegos para PC, Mac y Web a través de un plugin para su
visionado. Sin embargo además de estas versiones “básicas” existen
añadidos que permiten trasladar nuestro desarrollo a dispositivos
móviles, evidentemente volviendo a realizar un desembolso para
obtener dicha licencia y poder utilizar estas funcionalidades extras
en esta herramienta.

Además de esta herramienta, existen en el mercado otras como


son UDK de Epic Games o CryEngine de Crytek. Sin embargo una de
las ventajas que obtenemos con Unity 3D es que no estamos
anclados a Windows para poder realizar nuestros desarrollos, ya
que dispone de versión tanto para Windows como para Mac.

Unity 3D es una herramienta que nos ayuda a desarrollar


videojuegos para diversas plataformas mediante un editor y
scripting para crear videojuegos con un acabado profesional en 3D.
Unity 3D nos provee de un editor visual muy útil y completo donde
mediante unos pocos clicks podremos importar nuestros modelos
3D, texturas, sonidos, etc. para después ir trabajando con ellos.
Además incluye la herramienta de desarrollo MonoDevelop con la
que podremos crear scripts en JavaScript, C# y un dialecto de
Python llamado Boo con los que extender la funcionalidad del editor,
utilizando las API que provee y la cual encontramos documentada
junto a tutoriales y recursos en su web oficial.
UNIDAD 2: Programación de videojuegos 3D

Roberto Rodríguez Ortiz


Versión 1.2 Noviembre 2020

Este documento se publica bajo licencia Creative Commmons “Reconocimiento-


CompartirIgual (by-sa)”
Tabla de contenido
1. Introducción .......................................................................................................................... 4

2. Creación del entorno............................................................................................................. 5

2.1. Añadiendo un terreno ................................................................................. 5

2.2. Configuración básica de un terreno ............................................................. 5

2.3. Herramientas de terreno ............................................................................. 7

2.4. Definiendo una textura base ....................................................................... 9

2.5. Geometría del terreno ............................................................................... 11

2.6. Añadiendo vegetación .............................................................................. 12

2.7. Añadiendo un cielo ................................................................................... 14

2.8. Añadiendo niebla...................................................................................... 16

2.9. Color de ambiente ..................................................................................... 16

2.10. Añadiendo luces .................................................................................... 17


3. Añadiendo un controlador en primera persona ................................................................. 19

3.1. Personalizando el controlador ................................................................... 20

3.2. Editando el Script .................................................................................... 22

3.3. Últimos detalles........................................................................................ 23


4. Incluyendo objetos en la escena ......................................................................................... 23

5. Fuentes ................................................................................................................................ 24
1. Introducción

Unity3D como su nombre indica está especialmente diseñado para


trabajar en un entorno 3D, a través de esta unidad vamos a iniciarnos
en el desarrollo de un escenario tridimensional en el cual podamos
crear cualquier tipo de juego o experiencia interactiva.

Crearemos un proyecto vacío llamado TheJungle, indicando que será


en 3D:

En este caso tendremos una cámara con vista en perspectiva y una luz
direccional:
2. Creación del entorno
2.1. Añadiendo un terreno

Ya tenemos nuestro proyecto y la escena en blanco, ahora vamos a


añadir un terreno para empezar a crear nuestro nivel. Mientras
creamos el terreno veremos algunas características y opciones básicas
de los terrenos.

Unity maneja los terrenos de la misma forma que muchos otros


motores, como una malla plana que podemos texturizar y esculpir sin
salir del editor.

Para insertar un nuevo terreno vamos a “GameObject->3DObject-


>Terrain” desde el menú principal.

Lo que tenemos ahora no es particularmente bonito. Si no puedes ver


el terreno, desactiva las luces en la vista de escena. También podrás
apreciar que el terreno aparece en la jerarquía y un asset se ha añadido
a la librería en la vista de proyecto.

2.2. Configuración básica de un terreno

Ahora que tenemos nuestro terreno en la escena, debemos definir


algunas propiedades importantes, como la longitud del terreno y
algunas propiedades para controlar como de detallado debe ser el
terreno.
Para modificar estas propiedades iniciales debemos seleccionar el
terreno en la jerarquía de la escena y se mostrarán en el inspector.

Expliquemos que son algunas de las propiedades:

 Width: El ancho en
metros de nuestro
terreno.

 Lenght: La longitud en
metros del terreno.

 Height: La máxima altura


en metros del terreno.

 Heightmap Resolution:
La resolución del
heightmap. Debe tenerse
en cuenta que debe ser
potencia de 2 + 1.
(Ejemplo: 129,513)

 Detail Resolution: La resolución del mapa de detalles, cuanta más


resolución, más precisión a la hora de dibujar los detalles sobre el
terreno y colocar objetos.

 Control Texture Resolution: La resolución de las texturas


pintadas sobre el terreno, más resolución = más detalle, menor
solución = más rendimiento.

 Base Texture Resolution: Esta es la resolución base de la textura


que se renderiza desde distancia (LOD).

Son bastantes cosas que recordar, especialmente si no has usado


herramientas de creación de terrenos de otros motores antes. Tomarán
sentido después de que trabajes un poco con terrenos, experimentar
siempre es importante.
Yo usare estos valores; pero no significa que debáis tomar los mismos,
podéis personalizar vuestro propio terreno.

Width: 500m

Lenght: 500m

Height: 500m

Heightmap Resolution: 513

Detail Resolution: 1024

Control Texture Resolution: 512

Base Texture Resolution: 1024

Ahora deberías tener un terreno plano y gris en tu vista de escena,


como en la imagen a continuación.

2.3. Herramientas de terreno

Como puedes ver nuestro terreno es fino y no muy bonito, para


modificar nuestro terreno haremos uso de las herramientas de
edición de terrenos. Para acceder a las herramientas necesitamos
seleccionar el terreno en la jerarquía, por lo que haz clic sobre él.
Cuando hagas esto notaras que el inspector cambia para mostrar las
herramientas de edición de terrenos como en la imagen anterior.
El inspector estará dividido en 3 áreas:

 Transform: Permite mover y escalar el terreno sobre los ejes


x,y,z.

 Terrain: Contiene varias


herramientas y propiedades
para el terreno, sobre las que
hablaremos ahora.

 Terrain Collider: Contiene las propiedades de colisión para el


terreno.

En el panel Terrain veremos una fila de botones; estos son los


botones editores de terreno, cada uno permite realizar diferentes
tareas. Aquí tenéis una descripción de lo que permite hacer cada uno
de los botones, en orden de izquierda a derecha:

Paint Terrain

 Raise / Lower: Nos permite


levantar y hundir la
geometría del terreno usando
un pincel.

 Set Height: Pitamos el


terreno con una altura límite.

 Smooth Height: Nos permite suavizar un terreno para eliminar


esquinas, por ejemplo.

 Paint Texture: Nos permite pintar texturas sobre la superficie del


terreno.

Place Trees: Nos permite colocar árboles.

Paint Detail: Nos permite dibujar los detalles del terreno como hierva.

Terrain Settings: Accede a las propiedades del terreno donde podemos


cambiar varias propiedades.
2.4. Definiendo una textura base

Lo primero que debemos hacer es definir una textura base para el


terreno. La primera textura que añadas será aplicada al terreno
entero, cualquiera que añadas después será pintada en capas
superiores.

Cuando seleccionas la herramienta Paint Textures veras que aparecen


algunas preferencias nuevas, como en la imagen a continuación.

La primera sección permite seleccionar el tipo


de pincel, muy similar a los pinceles de
Photoshop.

Debajo de los pinceles tienes el espacio de


selección de texturas, donde podemos
seleccionar la textura con la que queremos
pintar.

Por último encontramos las propiedades del


pincel que nos permiten modificar el tamaño,
la opacidad y la fuerza del pincel.

Debemos seleccionar una textura para pintar


antes de hacer cualquier cosa, haz clic en “Edit Terrain Layers” y
selecciona “Create Layer” , en versiones recientes se ha sustituido la
nomenclatura de Texture por Layer (capa).

Podemos seleccionar una textura del cuadro


desplegable. Selecciona “GrassHillAlbedo” para nuestro
ejemplo.

Si no te aparece ninguna textura, podemos importar el


paquete estándar Standard Assets y seleccionamos el
Environment.
Cuando hayas hecho clic sobre “add” veras que la textura aparece
ahora en el panel sobre el botón “Edit Textures”; también notaras que
la textura se ha pintado sobre el terreno.

Debería parecerse a la imagen a continuación (haz zoom sobre el


terreno para conseguir una mejor vista de la textura).

Si rotas la cámara alrededor lo bastante lejos del terreno veras las


“juntas” de la textura por el terreno. Si quieres añadir más texturas,
estas se pintarán en el terreno sobre esta textura.
2.5. Geometría del terreno

Nuestro terreno sigue siendo plano y aburrido. Ya que el propósito de


este tutorial es que tengas los conocimientos para hacer algo simple
no vamos a tomarnos mucho tiempo en que nuestro terreno quede
espectacular, veamos las herramientas básicas de geometría que
serán suficientes para el cometido de este tutorial.

Si seleccionas la herramienta “Raise / Lower” veras que las


propiedades cambian. Las opciones serán similares a las que hemos
visto con la herramienta de pinceles; estilo de pincel, tamaño y
opacidad (¡cuidado! si está a cero no modificaremos el terreno). Una
opacidad baja creara la geometría despacio, una opacidad mayor hará
una geometría más intensa.

Prueba a usar la herramienta dibujando sobre el terreno. Si mantienes


shift al hacer clic hundirá el terreno, pero no por debajo de 0.

Consejo:

Es recomendable esculpir por pasos, primero las partes grandes y


bruscas, como montañas, ríos, caminos y luego refinamos los detalles
desde los más grandes a lo más pequeños. De esta forma
conseguiremos un resultado mucho mejor.

Con esto terminamos la explicación de los terrenos, como siempre lo


mejor que podéis hacer es trastear por vosotros mismos esculpiendo y
texturizando un terreno.

Añade otra textura, de aspecto rocoso por ejemplo:


Y aplícala sobre un terreno elevado:

2.6. Añadiendo vegetación

Para dar vida a nuestra escena podemos añadir vegetación, tanto


árboles como hierba.

Desde el editor del terreno


tenemos la opción de añadir
árboles de forma similar a como
texturizamos el terreno,
añadimos a través de Paint
Trees los modelos de árbol y
posteriormente utilizando una
brocha (Brush) podemos situar
los árboles de forma rápida en el
terreno.

Si queremos utilizar una variedad mayor de árboles, podemos


descargarlos desde el asset store, si buscamos tree en nuestros assets
podemos seleccionar assets gratuitos de la store de Unity3D y
descargarlos directamente en nuestro proyecto.
De igual modo procederemos con la hierba:
El resultado, con poco esfuerzo, puede ser espectacular:

2.7. Añadiendo un cielo

En Unity tenemos varias opciones para crear un skybox. La primera es


adjuntar un skybox a la cámara o adjuntarla a la escena directamente,
haremos esto más tarde.

Vayamos a “Windows->Rendering->Lithgning”
para mostrar las propiedades de renderizado que
se mostraran en una ventana emergente.

En las propiedades de iluminación tendremos


varias pestañas, en la de entorno “Environment”
veremos una entrada llamada Skybox. Si hacemos
clic sobre esta entrada podremos seleccionar un
material para el cielo.
El paquete standard de assets incluido con Unity trae un material
estándar, podéis encontrar más skybox en la tienda de Unity. Por
ejemplo:

En este caso hemos descargado/importado


el paquete Classic Skybox Una vez hayas
seleccionado un material para el cielo lo
veras aparecer en la vista de escena
inmediatamente.

Ahora deberías de ver algo como la imagen inferior.

Con el cielo en la escena hemos avanzado bastante, ahora parece un


mundo para un juego.
2.8. Añadiendo niebla

Unity incluye un sistema de niebla, que también se encuentra en las


opciones de iluminación. Si activas la niebla en el checkbox veras que
la escena se vuelve brumoso. Debajo del checkbox tenemos algunas
propiedades para nuestra niebla.

La primera es el color, por defecto gris. Podemos cambiarlo según


nuestras necesidades, por ejemplo si estamos trabajando en un área
desértica podemos cambiar el color a amarillo o naranja, en un mundo
mágico, quizás verde.

Debajo del color podemos definir la densidad de la niebla. Una densidad


mayor disminuirá la visibilidad y una densidad menor la aumentara.

2.9. Color de ambiente

Aunque Unity no incluye un paquete AAA de iluminación global si tiene


capacidades para efectos de iluminación globales.

Debajo de las propiedades de la niebla podemos ver “Ambient Light”,


por defecto como gris. Si queremos dar a la escena un esquema
diferente de iluminación, cambiar la luz ambiental es un buen principio.

Nota: Recuerda que debes tener la iluminación activada en la vista de


escena para que la luz ambiental se muestre. Fíjate en la captura bajo
estas líneas para ver la última escena con niebla y luz ambiental.
2.10. Añadiendo luces

Por último debemos añadir una luz a nuestra escena, llamada luz solar.

Usando la luz de forma creativa podremos crear efectos sorprendentes,


pero incluso con algo tan simple como la luz del sol podemos hacer
nuestra escena mucho mejor.

Para añadir una luz vamos a “GameObject -> Create Other ->
Directional Light”.

Para que la luz tenga efecto sobre la escena es importante tener


activado el botón en la vista de escena. Tan pronto como coloquemos
la luz direccional veremos su efecto.

No importa donde coloquemos la luz direccional, su efecto es global a


toda la escena. Lo que importa es la dirección, por lo que debes ir a la
herramienta de rotación y rotar la luz para que afecte a la escena como
queremos.

Podemos crear múltiples luces para crear diferentes entornos o


iluminar zonas donde la fuente de luz actual no llega.

Si seleccionas la luz, veras varias propiedades en el inspector. Podemos


ajustar el color de la luz para, por ejemplo, proyectar un color amarillo
o naranja sobre la escena, o dar una ambientación fría con un color
azul.
Cuando creemos una luz solar, queremos
que esta luz se renderice como un sol.
Para hacer esto, haz clic en el checkbox
“Draw Halo” y selecciona “Sun” del menú
desplegable Flare.

Si no tienes disponible ningún efecto en


Flare puedes importar el paquete Effects
desde Assets -> Import Package.

Entonces expande la sección de sombras y selecciona “Soft Shadows”


del menú desplegable Type, para que esta luz realice sombras sobre el
terreno.

A estas alturas ya deberías tener una escena bonita, o quizás no tanto.


Te recomiendo que modifiques el terreno con bastante variación, unas
colinas suaves y otras más bruscas, incluso alguna vertical ya que
ahora vamos a introducir un controlador en primera persona que
caminara por nuestra escena.
3. Añadiendo un controlador en primera persona

Unity incluye un controlador estándar en primera persona que hace


que los juegos con vista en primera persona sean realmente sencillos
de configurar. Para colocar este controlador en la escena, debemos ir
a la vista de proyecto y seleccionar “Standard Assets -> Characters-
>FirstPersonCharacter->Prefabs”. Dentro de esa carpeta veras un
prefab llamado “RigidBody FPS Controller”. Si no lo tienes disponible
puedes importarlo desde Assets ->Import Package->Characters.

Arrastra este objeto a la vista de escena y posiciónalo de forma que el


cilindro toque el terreno, pero no esté por debajo.

Cuando el controlador esté en la escena, haz clic en el botón de


reproducir para jugar en tu escena con el controlador.
Los controles son:

 Flechas: Movimiento

 Ratón: Control de la cámara

 Espacio: Saltar

Si en este punto tu controlador cae al vacío atravesando el plano


asegúrate de que no esté colocado en la escena atravesando el terreno.

Si te mueves alrededor te encontraras con que ciertas características


básicas están incluidas en el controlador, como por ejemplo zonas
donde el jugador no pueda pasar. La mejor forma de probar esto es
probar a subir una inclinación vertical o bastante vertical. Para salir del
modo de juego simplemente hacemos clic sobre el botón de
reproducción de nuevo para volver a la vista de editor.

3.1. Personalizando el controlador

No es muy divertido ni común arrastrar los prefabricados a la escena y


dejarlos como están, al menos querremos personalizarlo para que
funcione como nosotros queremos que lo haga. El controlador FPS que
hemos colocado en la escena puede ser personalizado a través del
inspector, por lo que vamos a seleccionar el controlador en la jerarquía
y echar un vistazo en el inspector, veremos que hay bastantes opciones
de personalización.
Aquí tenéis una captura de pantalla:

Como se puede apreciar contiene un Rigidbody, un Collider de tipo


capsula para las colisiones y un script RigidbodyFirstPersonController a
través del cual podemos configurar su comportamiento.

Las propiedades más interesantes pueden ser la de movimiento donde


se puede configurar las velocidades en varias direcciones y el slope que
limita el ángulo por el que puede caminar el jugador y que se configura
por medio de una curva (no puede servir para evitar que el jugador
salga del esceneario).
3.2. Editando el Script

Mientras que editar las propiedades básicas debería cumplir con los
requisitos de tu juego la realidad es que no es así. En bastantes
ocasiones, particularmente con elementos como los controladores de
personajes, necesitamos hacer más cambios.

Estos cambios se realizan editando el script mismo.

Para editar el script del controlador, seleccionamos el controlador en la


jerarquía, entonces, en el inspector buscamos la parte de
RigidbodyFirstPersonController (Script). Estas propiedades son para el
FPS, un script que puedes editar.

Una forma rápida de acceder al código es hacer clic en el botón cerca


de RigidbodyFirstPersonController (Script). Esto nos mostrara el editor
de scripts, donde podremos hacer los cambios sobre el script.

Para empezar haremos un cambio muy simple, eliminaremos la


capacidad de saltar del script. Si buscas la linea donde dice:

if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump)


{
m_Jump = true;
}

Este bloque de código reacciona cuando el jugador pulsa el botón


predefinido como Jump, o saltar en castellano. Podemos eliminar esto
comentando o simplemente borrando el bloque entero. Para
comentarlo, coloca /* antes del código y */ después. Ahora guarda el
script y vuelve al editor.

Si ejecutas tu juego ahora veras que ya no puedes saltar. Deja el


código sin comentar de nuevo, guarda y vuelve al editor. Ejecuta tu
juego y la función de saltar estará ahí de nuevo.
3.3. Últimos detalles

Te habrás dado cuenta de que el script del controlador acepta


comandos de entrada, pero, ¿de dónde?

Si vas a “Edit -> Project Settings -> Input” veras el controlador de


entrada para tu juego. Puedes configurar el número de entradas
usando el comando size y editar cada una de ellas según tus
necesidades. Cambiar el nombre cambiara el nombre de la entrada,
por lo que tu juego tendrá su esquema único de entrada.

Seguramente no quieras tener tu juego solo en el editor, querrás poder


jugar desde un navegador web o desde un ejecutable. Para hacer esto
necesitar crear una build. Ve a “File -> Build and Run”. En el cuadro de
dialogo haz clic en “Add Current” para añadir la escena actual y
selecciona la plataforma. Tras esto el juego se compilara en la carpeta
especificada.

4. Incluyendo objetos en la escena

Unity3D proporciona una serie de objetos 3D con geometría muy


básica, cubo, esfera…, hay que tener en cuenta que para generar
objetos de forma detallada debemos utilizar editores especializados
como Blender o 3DStudio.

Vamos a incluir un cubo en nuestra escena, GameObject->3D Object-


>Cube.
Se muestra sin ningún tipo de
textura, lo que hace que parezca
fuera de lugar. Vamos a aplicarle
un material de forma que parezca
un elemento más coherente con el
entorno, por ejemplo un material
de tipo madera (bark pine):

El resultado:

Para profundizar en como representa Unity3D los


modelos en la escena podemos utilizar la
documentación.

5. Fuentes

 Empezando en Unity3d, UnitySpain

También podría gustarte