<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>sgoliver.net blog</title>
	<atom:link href="http://www.sgoliver.net/blog/index.php?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.sgoliver.net/blog</link>
	<description>Pensamientos varios sobre programación, Android, .NET y Java</description>
	<lastBuildDate>Sat, 25 May 2013 02:21:35 +0000</lastBuildDate>
	<language>es-ES</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Action Bar en Android (II)</title>
		<link>http://www.sgoliver.net/blog/?p=3644&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=action-bar-en-android-ii</link>
		<comments>http://www.sgoliver.net/blog/?p=3644#comments</comments>
		<pubDate>Thu, 14 Mar 2013 18:52:08 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[bar]]></category>
		<category><![CDATA[pestañas]]></category>
		<category><![CDATA[tabs]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3644</guid>
		<description><![CDATA[Como comenté en el artículo anterior del curso Android proporciona un mecanismo que nos permite &#8220;integrar&#8221; (por llamarlo de alguna manera) una barra de pestañas con la action bar. En este momento muchos de vosotros seguro que habréis pensando: &#8220;Yo ya he aprendido a usar el control TabWidget en un artículo anterior de tu curso. ¿por [...]]]></description>
				<content:encoded><![CDATA[<p>Como comenté en el <a title="Action Bar en Android (I)" href="http://www.sgoliver.net/blog/?p=3620">artículo anterior</a> del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">curso</a> Android proporciona un mecanismo que nos permite &#8220;integrar&#8221; (por llamarlo de alguna manera) una barra de pestañas con la action bar.</p>
<p>En este momento muchos de vosotros seguro que habréis pensando: &#8220;<em>Yo ya he aprendido a usar el control <a title="Interfaz de usuario en Android: Pestañas (Tabs)" href="http://www.sgoliver.net/blog/?p=2112">TabWidget</a> en un artículo anterior de tu curso. ¿por qué iba a querer aprender otra forma de utilizar pestañas?</em>&#8220;. Pues bien, además de convertirnos instantaneamente en programadores mucho más modernos y elegantes por utilizar <em>fragments</em> para nuestros tabs :), el hecho de enlazar una lista de pestañas con la action bar tiene una serie de ventajas adicionales, relacionadas sobre todo con la adaptación de tu interfaz a distintos tamaños y configuraciones de pantalla. Así, por ejemplo, si Android detecta que hay suficiente espacio disponible en la action bar, integrará las pestañas dentro de la propia action bar de forma que no ocupen espacio extra en la pantalla. Si por el contrario no hubiera espacio suficiente colocaría las pestañas bajo la action bar como de costumbre. Más tarde veremos un ejemplo gráfico de esto, pero ahora empecemos.</p>
<p>Lo primero que debemos hacer será crear un nuevo fragment para albergar el contenido de cada una de las pestañas que tendrá nuestra aplicación. Como ya vimos en el <a title="Interfaz de usuario en Android: Fragments" href="http://www.sgoliver.net/blog/?p=3496">artículo dedicado a los fragments</a>, necesitaremos crear como mínimo una pareja de ficheros para cada fragment, el primero para el layout XML y el segundo para su código java asociado. Para el caso de ejemplo de este artículo, que partirá del ya construido en el <a title="Action Bar en Android (I)" href="http://www.sgoliver.net/blog/?p=3620">artículo anterior</a>, incluiré tan sólo dos pestañas, y los ficheros asociados a ellas los llamaré de la siguiente forma:</p>
<p><strong>Pestaña 1:</strong></p>
<ul>
<li>Tab1Fragment.java</li>
<li>fragment1.xml</li>
</ul>
<p><strong>Pestaña 2:</strong></p>
<ul>
<li>Tab2Fragment.java</li>
<li>fragment2.xml</li>
</ul>
<p>La interfaz de los fragmets será mínima para no complicar el ejemplo, y contendrán únicamente una etiqueta de texto &#8220;Tab 1&#8243; o &#8220;Tab 2&#8243; para poder diferenciarlas. Por ejemplo para la pestaña 1 tendríamos un fichero <span style="font-family: 'courier new', courier;">fragment1.xml</span> con el siguiente contenido:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot; &gt;

    &lt;TextView
        android:id=&quot;@+id/textView1&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;@string/tab_1&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>Por su parte, su clase java asociada <span style="font-family: 'courier new', courier;">Tab1Fragment.java</span> no tendrá ninguna funcionalidad, por lo que el código se limita a inflar el layout y poco más:</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.actionbartabs;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab1Fragment extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater,
			ViewGroup container, Bundle savedInstanceState) {
		return inflater.inflate(R.layout.fragment1, container, false);
	}
}
</pre>
<p>Con esto ya tendríamos preparado el contenido de nuestras pestañas, y nos quedaría añadirlas a nuestra aplicación, enlazarlas con la action bar, y asignarles un <em>listener</em> desde el que poder responder a los eventos que se produzcan.</p>
<p>Vamos a empezar por el último paso, crear el <em>listener</em>. Como he comentado, este listener debe contener el código asociado a los eventos clásicos de las pestañas  (normalmente la selección, reselección, o deselección de pestañas) y entre otras cosas tendrá que encargarse de mostrar en cada momento el fragment correspondiente a la pestaña seleccionada. Hay muchas formas de implementar este listener, y sé que la que yo voy a mostrar aquí no es la mejor de todas, pero sí es de las más sencillas para empezar. Vamos a crear nuestro listener creando una nueva clase que extienda de <span style="font-family: 'courier new', courier;">ActionBar.TabListener</span>, en mi caso la llamaré <span style="font-family: 'courier new', courier;">MiTabListener</span> (en un alarde de genialidad). Dentro de esta clase tendremos que sobrescribir los métodos de los eventos <span style="font-family: 'courier new', courier;">onTabSelected()</span>, <span style="font-family: 'courier new', courier;">onTabUnselected()</span> y <span style="font-family: 'courier new', courier;">onTabReselected()</span>. Creo que el nombre de cada uno explica por sí solo lo que hacen. Veamos primero el código:</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.actionbartabs;

import android.app.ActionBar;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
import android.util.Log;

public class MiTabListener implements ActionBar.TabListener {

	private Fragment fragment;

	public MiTabListener(Fragment fg)
	{
		this.fragment = fg;
	}

	@Override
	public void onTabReselected(Tab tab, FragmentTransaction ft) {
		Log.i(&quot;ActionBar&quot;, tab.getText() + &quot; reseleccionada.&quot;);
	}

	@Override
	public void onTabSelected(Tab tab, FragmentTransaction ft) {
		Log.i(&quot;ActionBar&quot;, tab.getText() + &quot; seleccionada.&quot;);
		ft.replace(R.id.contenedor, fragment);
	}

	@Override
	public void onTabUnselected(Tab tab, FragmentTransaction ft) {
		Log.i(&quot;ActionBar&quot;, tab.getText() + &quot; deseleccionada.&quot;);
		ft.remove(fragment);
	}
}
</pre>
<p><script type="text/javascript"><!--
google_ad_client = "ca-pub-4423798007798708";
/* home-728 */
google_ad_slot = "2494167070";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script><br />
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
Como podéis ver, en cada uno de estos métodos lo único que haremos en nuestro caso será mostrar u ocultar nuestros fragments de forma que quede visible el correspondiente a la pestaña seleccionada. Así, en el evento <span style="font-family: 'courier new', courier;">onTabSelected()</span> reemplazaremos el fragment actualmente visible con el de la pestaña seleccionada (que será un atributo de nuestra clase, después veremos dónde y cómo se asigna), y en el método <span style="font-family: 'courier new', courier;">onTabUnselected()</span> ocultamos el fragment asociado a la pestaña ya que está habrá sido deseleccionada. Ambas acciones se realizan llamando al método correspondiente de <span style="font-family: 'courier new', courier;">FragmentTransaction</span>, que nos llega siempre como parámetro y nos permite gestionar los fragments de la actividad. En el primer caso se usará el método <span style="font-family: 'courier new', courier;">replace()</span> y en el segundo el método <span style="font-family: 'courier new', courier;">remove()</span>. Además de todo esto, he añadido mensajes de log en cada caso para poder comprobar que se lanzan los eventos de forma correcta.</p>
<p>Implementado el listener tan sólo nos queda crear las pestañas, asociarle sus fragments correspondientes, y enlazarlas a nuestra action bar. Todo esto lo haremos en el método <span style="font-family: 'courier new', courier;">onCreate()</span> de la actividad principal. Son muchos pasos, pero sencillos todos ellos.</p>
<p>Comenzaremos obteniendo una referencia a la action bar mediante el método <span style="font-family: 'courier new', courier;">getActionBar()</span>. Tras esto estableceremos su método de navegación a <span style="font-family: 'courier new', courier;">NAVIGATION_MODE_TABS</span> para que sepa que debe mostrar las pestañas. A continuació creamos las pestañas el método <span style="font-family: 'courier new', courier;">newTab()</span> de la action bar y estableceremos su texto con <span style="font-family: 'courier new', courier;">setText()</span>. Lo siguiente será instanciar los dos fragments y asociarlos a cada pestaña a través de la clase listener que hemos creado, llamando para ello a <span style="font-family: 'courier new', courier;">setTabListener()</span>. Y por último añadiremos las pestañas a la action bar mediante <span style="font-family: 'courier new', courier;">addTab()</span>. Estoy seguro que con el código se entenderá mucho mejor:</p>
<pre class="brush: java; title: ; notranslate">
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	//Obtenemos una referencia a la actionbar
	ActionBar abar = getActionBar();

	//Establecemos el modo de navegación por pestañas
	abar.setNavigationMode(
  		ActionBar.NAVIGATION_MODE_TABS);

	//Ocultamos si queremos el título de la actividad
	//abar.setDisplayShowTitleEnabled(false);

	//Creamos las pestañas
	ActionBar.Tab tab1 =
		abar.newTab().setText(&quot;Tab 1&quot;);

	ActionBar.Tab tab2 =
		abar.newTab().setText(&quot;Tab 2&quot;);

	//Creamos los fragments de cada pestaña
        Fragment tab1frag = new Tab1Fragment();
        Fragment tab2frag = new Tab2Fragment();

        //Asociamos los listener a las pestañas
        tab1.setTabListener(new MiTabListener(tab1frag));
        tab2.setTabListener(new MiTabListener(tab2frag));

        //Añadimos las pestañas a la action bar
        abar.addTab(tab1);
        abar.addTab(tab2);
}
</pre>
<p>Con esto habríamos acabado. Si ejecutamos la aplicación en el emulador veremos algo como lo siguiente:</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-vertical-tabs.jpg"><img class="alignnone size-medium wp-image-3629" alt="actionbar-vertical-tabs" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-vertical-tabs-300x79.jpg" width="300" height="79" /></a></p>
<p>Como vemos, Android ha colocado nuestras pestañas bajo la action bar porque no ha encontrado sitio suficiente arriba. Pero si probamos con la orientación horizontal (Ctrl+F12) pasa lo siguiente (click para ampliar):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-horizontal-tabs.jpg"><img class="alignnone size-medium wp-image-3631" alt="actionbar-horizontal-tabs" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-horizontal-tabs-300x19.jpg" width="300" height="19" /></a></p>
<p>Dado que ya había espacio suficiente en la propia action bar, Android ha colocado las pestañas directamente sobre ella de forma que hemos ganado espacio para el resto de la interfaz. Esto no lo habríamos podido conseguir utilizando el control <span style="font-family: 'courier new', courier;">TabWidget</span>.</p>
<p>Por último, si cambiamos varias veces de pestaña deberíamos comprobar que se muestra en cada caso el fragment correcto y que el en log aparecen los mensajes de depuración que hemos incluido.</p>
<p>Espero que con estos dos últimos artículos hayáis aprendido al menos a aprovechar la funcionalidad básica de la action bar. Habría por supuesto más cosas que contar, por ejemplo algunas cuestiones de navegación entre actividades, pero eso lo dejaremos para más adelante.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="ActionBar 2 - Tabs - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-actionbar-tabs" target="_blank">curso en GitHub</a>.
<script type="text/javascript"><!--
google_ad_client = "ca-pub-4423798007798708";
/* home-728 */
google_ad_slot = "2494167070";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script><br />
<script type="text/javascript"<br />
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3644</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Action Bar en Android (I)</title>
		<link>http://www.sgoliver.net/blog/?p=3620&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=action-bar-en-android-i</link>
		<comments>http://www.sgoliver.net/blog/?p=3620#comments</comments>
		<pubDate>Tue, 12 Mar 2013 18:37:14 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[actionbar]]></category>
		<category><![CDATA[bar]]></category>
		<category><![CDATA[menu]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3620</guid>
		<description><![CDATA[La action bar de Android es la barra de título y herramientas que aparece en la parte superior de muchas aplicaciones  actuales. Normalmente muestra un icono, el título de la actividad en la que nos encontramos, una serie de botones de acción, y un menú desplegable (menú de overflow) donde se incluyen más acciones que [...]]]></description>
				<content:encoded><![CDATA[<p>La <em>action bar</em> de Android es la barra de título y herramientas que aparece en la parte superior de muchas aplicaciones  actuales. Normalmente muestra un icono, el título de la actividad en la que nos encontramos, una serie de botones de acción, y un menú desplegable (<em>menú de overflow</em>) donde se incluyen más acciones que no tienen espacio para mostrarse como botón o simplemente no se quieren mostrar como tal.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-partes.jpg"><img class="alignnone size-full wp-image-3633" alt="actionbar-partes" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-partes.jpg" width="410" height="131" /></a></p>
<p>La action bar de Android es uno de esos componentes que Google no ha tratado demasiado bien al no incluirla en la librería de compatibilidad <span style="font-family: 'courier new', courier;">android-support</span>. Esto significa que de forma nativa tan sólo es compatible con versiones de Android 3.0 o superiores. En este artículo nos centraremos únicamente en esta versión nativa de la plataforma (configuraremos nuestra aplicación de ejemplo para ejecutarse sobre Android 4).</p>
<p>Tan sólo a modo de referencia diré que existe una librería muy popular llamada <a title="ActionBarSherlock" href="http://actionbarsherlock.com/" target="_blank">ActionBarSherlock </a>que proporciona una implementación alternativa de este componente que es compatible con versiónes de Android a partir de la 2.0. Otra alternativa para compatibilizar nuestras aplicaciones con versiones de Android anteriores a la 3.0 sería utilizar la implementación que Google proporciona como parte de los ejemplos de la plataforma, llamada ActionBarCompat, que puedes encontrar en la siguiente carpeta de tu instalación del SDK: <span style="font-family: 'courier new', courier;">&lt;carpeta-sdk&gt;\samples\android-nn\ActionBarCompat</span></p>
<p>Cuando se crea un proyecto con alguna de las últimas versiones del plugin ADT para Eclipse, la aplicación creada por defecto ya incluye &#8220;de serie&#8221; su action bar correspondiente. De hecho, si creamos un proyecto nuevo y directamente lo ejecutamos sobre un AVD con Android 3.0 o superior veremos como no solo se incluye la action bar sino que también aparece una acción &#8220;<em>Settings</em>&#8221; como muestra la siguiente imagen.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-inicial.jpg"><img class="alignnone size-medium wp-image-3632" alt="actionbar-inicial" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-inicial-300x111.jpg" width="300" height="111" /></a></p>
<p>Vale, ¿pero donde está todo esto definido en nuestro proyecto? La action bar de Android toma su contenido de varios sitios diferentes. En primer lugar muestra el <strong>icono de la aplicación</strong> (definido en el <span style="font-family: 'courier new', courier;">AndroidManifest</span> mediante el atributo <span style="font-family: 'courier new', courier;">android:icon</span> del elemento <span style="font-family: 'courier new', courier;">&lt;application&gt;</span>) y el <strong>título de la actividad</strong> actual (definido en el AndroidManifest mediante el atributo <span style="font-family: 'courier new', courier;">android:label</span> de cada elemento <span style="font-family: 'courier new', courier;">&lt;activity&gt;</span>). El resto de elementos se definen de la misma forma que los &#8220;antiguos&#8221; menús de aplicación que se utilizaban en versiones de Android 2.x e inferiores. De hecho, es exactamente la misma implementación. Nosotros definiremos un menú, y si la aplicación se ejecuta sobre Android 2.x las acciones se mostrarán como elementos del menú como tal (ya que la action bar no se verá al no ser compatible) y si se ejecuta sobre Android 3.0 o superior aparecerán como acciones de la action bar, ya sea en forma de botón de acción o incluidas en el menú de overflow. En definitiva, una bonita forma de mantener cierta compatibilidad con versiones anteriores de Android, aunque en unas ocasiones se muestre la action bar y en otras no.</p>
<p>Y bien, ¿cómo se define un menú de aplicación? Pues en el curso hay <a title="Menús en Android (I): Conceptos básicos" href="http://www.sgoliver.net/blog/?p=1756">un artículo</a> dedicado exclusivamente a ello donde poder profundizar, pero de cualquier forma, en este artículo daré las directrices generales para definir uno sin mucha dificultad.</p>
<p>Un menú se define, como la mayoría de los recursos de Android, mediante un fichero XML, y se colocará en la carpeta <span style="font-family: 'courier new', courier;">/res/menu</span>. El menú se definirá mediante un elemento raiz <span style="font-family: 'courier new', courier;">&lt;menu&gt;</span> y contendrá una serie de elementos <span style="font-family: 'courier new', courier;">&lt;item&gt;</span> que representarán cada una de las opciones. Los elementos <span style="font-family: 'courier new', courier;">&lt;item&gt;</span> por su parte podrán incluir varios atributos que lo definan, entre los que destacan los siguientes:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">android:id</span>. El ID identificativo del elemento, con el que podremos hacer referencia dicha opción.</li>
<li><span style="font-family: 'courier new', courier;">android:title</span>. El texto que se visualizará para la opción.</li>
<li><span style="font-family: 'courier new', courier;">android:icon</span>. El icono asociado a la acción.</li>
<li><span style="font-family: 'courier new', courier;">android:showAsAction</span>. Si se está mostrando una action bar, este atributo indica si la opción de menú se mostrará como botón de acción o como parte del menú de overflow. Puede tomar varios valores:
<ul>
<li><span style="font-family: 'courier new', courier;">ifRoom</span>. Se mostrará como botón de acción sólo si hay espacio disponible.</li>
<li><span style="font-family: 'courier new', courier;">withText</span>. Se mostrará el texto de la opción junto al icono en el caso de que éste se esté mostrando como botón de acción.</li>
<li><span style="font-family: 'courier new', courier;">never</span>. La opción siempre se mostrará como parte del menú de overflow.</li>
<li><span style="font-family: 'courier new', courier;">always</span>. La opción siempre se mostrará como botón de acción. Este valor puede provocar que los elementos se solapen si no hay espacio suficiente para ellos.</li>
</ul>
</li>
</ul>
<p>Así, por ejemplo, si abrimos el menú definido por defecto en el proyecto nuevo (llamado normalmente <span style="font-family: 'courier new', courier;">/res/menu/activity_main.xml</span> si no lo hemos cambiado de nombre durante la creación del proyecto) veremos el siguiente código:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot; &gt;

    &lt;item
        android:id=&quot;@+id/menu_settings&quot;
        android:showAsAction=&quot;never&quot;
        android:title=&quot;@string/menu_settings&quot;/&gt;

&lt;/menu&gt;
</pre>
<p>Como vemos se define un menú con una única opción, con el texto &#8220;Settings&#8221; y con el atributo <span style="font-family: 'courier new', courier;">showAsAction=&#8221;never&#8221;</span> de forma que ésta siempre aparezca en el menú de overflow.</p>
<p>Esta opción por defecto se incluye solo a modo de ejemplo, por lo que podríamos eliminarla sin problemas para incluir las nuestras propias. En mi caso la voy a conservar pero voy a añadir dos más de ejemplo: &#8220;Save&#8221; y &#8220;New&#8221;, la primera de ellas para que se muestre, si hay espacio, como botón con su icono correspondiente, y la segunda igual pero además acompañada de su título de acción:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot; &gt;

    &lt;item
        android:id=&quot;@+id/menu_settings&quot;
        android:showAsAction=&quot;never&quot;
        android:title=&quot;@string/menu_settings&quot;/&gt;

    &lt;item
        android:id=&quot;@+id/menu_save&quot;
        android:showAsAction=&quot;ifRoom&quot;
        android:icon=&quot;@android:drawable/ic_menu_save&quot;
        android:title=&quot;@string/menu_guardar&quot;/&gt;

    &lt;item
        android:id=&quot;@+id/menu_new&quot;
        android:showAsAction=&quot;ifRoom|withText&quot;
        android:icon=&quot;@android:drawable/ic_menu_add&quot;
        android:title=&quot;@string/menu_nuevo&quot;/&gt;
&lt;/menu&gt;
</pre>
<p>Como podéis ver en la segunda opción, se pueden combinar varios valores de <span style="font-family: 'courier new', courier;">showAsAction</span> utilizando el caracter &#8220;<span style="font-family: 'courier new', courier;">|</span>&#8220;.</p>
<p>Una vez definido el menú en su fichero XML correspondiente tan sólo queda asociarlo a nuestra actividad principal. Esto se realiza sobrescribiendo el método <span style="font-family: 'courier new', courier;">OnCreateOptionsMenu()</span> de la actividad, dentro del cual lo único que tenemos que hacer normalmente es <em>inflar</em> el menú llamando al método <span style="font-family: 'courier new', courier;">inflate()</span> pasándole como parámetros el ID del fichero XML donde se ha definido. Este trabajo suele venir hecho ya al crear un proyecto nuevo desde Eclipse:</p>
<pre class="brush: java; title: ; notranslate">
public class MainActivity extends Activity {

	...

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}
}
</pre>
<p>Ejecutemos la aplicación ahora a ver qué ocurre.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-vertical.jpg"><img class="alignnone size-medium wp-image-3634" alt="actionbar-vertical" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-vertical-300x40.jpg" width="300" height="40" /></a></p>
<p>Como podemos observar, la opción &#8220;Settings&#8221; sigue estando dentro del menú de overflow, y ahora aparecen como botones de acción las dos opciones que hemos marcado como <span style="font-family: 'courier new', courier;">showAsAction=&#8221;ifRoom&#8221;</span>, pero para la segunda no aparece el texto. ¿Y por qué? Porque no hay espacio disponible con la pantalla en vertical. Pero si rotamos el emulador para ver qué ocurre con la pantalla en horizontal (pulsando Ctrl + F12) vemos lo siguiente:</p>
<p><img class="alignnone size-medium wp-image-3630" alt="actionbar-horizontal" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/03/actionbar-horizontal-300x18.jpg" width="300" height="18" /></p>
<p>Con la pantalla en horizontal sí se muestra el texto de la segunda opción, tal como habíamos solicitado con el valor <span style="font-family: 'courier new', courier;">withText</span> del atributo <span style="font-family: 'courier new', courier;">showAsAction</span>.</p>
<p>Ahora que ya sabemos definir los elementos de nuestra action bar queda saber cómo responder a las pulsaciones que haga el usuario sobre ellos. Para esto, al igual que se hace con los menús tradicionales (ver <a title="Menús en Android (I): Conceptos básicos" href="http://www.sgoliver.net/blog/?p=1756">artículo sobre menús</a> para más detalles), sobrescribiremos el método <span style="font-family: 'courier new', courier;">OnOptionsItemSelected()</span>, donde consultaremos la opción de menú que se ha pulsado mediante el método <span style="font-family: 'courier new', courier;">getItemId()</span> de la opción de menú recibida como parámetro y actuaremos en consecuencia. En mi caso de ejemplo tan sólo escribiré un mensaje al log.</p>
<pre class="brush: java; title: ; notranslate">
@Override
public boolean onOptionsItemSelected(MenuItem item) {
	switch (item.getItemId()) {
        case R.id.menu_new:
            Log.i(&quot;ActionBar&quot;, &quot;Nuevo!&quot;);
            return true;
        case R.id.menu_save:
            Log.i(&quot;ActionBar&quot;, &quot;Guardar!&quot;);;
            return true;
        case R.id.menu_settings:
            Log.i(&quot;ActionBar&quot;, &quot;Settings!&quot;);;
            return true;
        default:
            return super.onOptionsItemSelected(item);
	}
}
</pre>
<p>Si ejecutamos ahora la aplicación y miramos el log mientras pulsamos las distintas opciones de la action bar veremos como se muestran los mensajes definidos.</p>
<p>Y bien, esto es todo lo que habría que contar a nivel básico sobre la action bar, si no fuera porque los chicos de Android pensaron que también sería interesante &#8220;integrar&#8221; con ella de alguna forma la barra de pestañas, en caso de utilizarse este tipo de interfaz en la aplicación. Para no alargar demasiado este artículo, de momento pararemos aquí y dedicaré una segunda parte a este tema.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="ActionBar 1 - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-actionbar" target="_blank">curso en GitHub</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3620</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Interfaz de usuario en Android: Fragments</title>
		<link>http://www.sgoliver.net/blog/?p=3496&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=fragments-en-android</link>
		<comments>http://www.sgoliver.net/blog/?p=3496#comments</comments>
		<pubDate>Fri, 25 Jan 2013 21:18:09 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[fragment]]></category>
		<category><![CDATA[fragmento]]></category>
		<category><![CDATA[interfaz]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3496</guid>
		<description><![CDATA[Cuando empezaron a aparecer dispositivos de gran tamaño tipo tablet, el equipo de Android tuvo que solucionar el problema de la adaptación de la interfaz gráfica de las aplicaciones a ese nuevo tipo de pantallas. Una interfaz de usuario diseñada para un teléfono móvil no se adaptaba fácilmente a una pantalla 4 o 5 pulgadas [...]]]></description>
				<content:encoded><![CDATA[<p>Cuando empezaron a aparecer dispositivos de gran tamaño tipo tablet, el equipo de Android tuvo que solucionar el problema de la adaptación de la interfaz gráfica de las aplicaciones a ese nuevo tipo de pantallas. Una interfaz de usuario diseñada para un teléfono móvil no se adaptaba fácilmente a una pantalla 4 o 5 pulgadas mayor. La solución a esto vino en forma de un nuevo tipo de componente llamado Fragment.</p>
<p>Un <em>fragment</em> no puede considerarse ni un <em>control</em> ni un <em>contenedor</em>, aunque se parecería más a lo segundo. Un <em>fragment</em> podría definirse como una porción de la interfaz de usuario que puede añadirse o eliminarse de una interfaz de forma independiente al resto de elementos de la actividad, y que por supuesto puede reutilizarse en otras actividades. Esto, aunque en principio puede parecer algo trivial, nos va a permitir poder dividir nuestra interfaz en varias porciones de forma que podamos diseñar diversas configuraciones de pantalla, dependiendo de su tamaño y orientación, sin tener que duplicar código en ningún momento, sino tan sólo utilizando o no los distintos fragmentos para cada una de las posibles configuraciones. Intentemos aclarar esto un poco con un ejemplo.</p>
<p>No quería utilizar el ejemplo típico que aparece por todos lados, pero en este caso creo que es el más ilustrativo. Supongamos una aplicación de correo electrónico, en la que por un lado debemos mostrar la lista de correos disponibles, con sus campos clásicos <em>De</em> y <em>Asunto</em>, y por otro lado debemos mostrar el contenido completo del correo seleccionado. En un teléfono móvil lo habitual será tener una primera actividad que muestre el listado de correos, y cuando el usuario seleccione uno de ellos navegar a una nueva actividad que muestre el contenido de dicho correo. Sin embargo, en un tablet puede existir espacio suficiente para tener ambas partes de la interfaz en la misma pantalla, por ejemplo en un tablet en posición horizontal podríamos tener una columna a la izquierda con el listado de correos y dedicar la zona derecha a mostrar el detalle del correo seleccionado, todo ello sin tener que cambiar de actividad.</p>
<p>Antes de existir los fragments podríamos haber hecho esto implementando diferentes actividades con diferentes layouts para cada configuración de pantalla, pero esto nos habría obligado a duplicar gran parte del código en cada actividad. Tras la aparición de los fragments, colocaríamos el listado de correos en un fragment y la vista de detalle en otro, cada uno de ellos acompañado de su lógica de negocio asociada, y tan sólo nos quedaría definir varios layouts para cada configuración de pantalla incluyendo [o no] cada uno de estos fragments.</p>
<p>A modo de aplicación de ejemplo para este artículo, nosotros vamos a simular la aplicación de correo que hemos comentado antes, adaptándola a tres configuraciones distintas: pantalla normal, pantalla grande horizontal y pantalla grande vertical. Para el primer caso colocaremos el listado de correos en una actividad y el detalle en otra, mientras que para el segundo y el tercero ambos elementos estarán en la misma actividad, a derecha/izquierda para el caso horizontal, y arriba/abajo en el caso vertical.</p>
<p>Definiremos por tanto dos fragments: uno para el listado y otro para la vista de detalles. Ambos serán muy sencillos. Al igual que una actividad, cada fragment se compondrá de un fichero de layout XML para la interfaz (colocado en alguna carpeta /res/layout) y una clase java para la lógica asociada.</p>
<p>El primero de los fragment a definir contendrá tan sólo un control <span style="font-family: 'courier new', courier;">ListView</span>, para el que definiremos un adaptador personalizado para mostrar dos campos por fila (&#8220;De&#8221; y &#8220;Asunto&#8221;). Ya describimos cómo hacer esto en el <a title="Interfaz de usuario en Android: Controles de selección (II)" href="http://www.sgoliver.net/blog/?p=1414">artículo dedicado al control ListView</a>. El layout XML (lo llamaremos <span style="font-family: 'courier new', courier;">fragment_listado.xml</span>) quedaría por tanto de la siguiente forma:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot; &gt;
    &lt;ListView
        android:id=&quot;@+id/LstListado&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot; &gt;
    &lt;/ListView&gt;
&lt;/LinearLayout&gt;
</pre>
<p>Como hemos dicho, todo fragment debe tener asociada,  además del layout, su propia clase java, que en este caso debe extender de la clase <span style="font-family: 'courier new', courier;">Fragment</span>. Y aquí viene el primer contratiempo. Los fragment aparecieron con la versión 3 de Android, por lo que en principio no estarían disponibles para versiones anteriores. Sin embargo, Google pensó en todos y proporcionó esta característica también como parte de la librería de compatibillidad <span style="font-family: 'courier new', courier;">android-support</span>, que en versiones recientes del plugin de Android para Eclipse se añade por defecto a todos los proyectos creados.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-android-support.png"><img class="alignnone size-full wp-image-3498" alt="fragments-android-support" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-android-support.png" width="212" height="255" /></a></p>
<p>Si no fuera así, también puede incluirse manualmente en el proyecto mediante la opción &#8220;<em>Add Support Library&#8230;</em>&#8221; del menú contextual del proyecto.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-add-support.png"><img class="alignnone size-full wp-image-3499" alt="fragments-add-support" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-add-support.png" width="573" height="185" /></a></p>
<p>Hecho esto, ya no habría ningún problema para utilizar la clase <span style="font-family: 'courier new', courier;">Fragment</span>, y otras que comentaremos más adelante, para utilizar fragmentos compatibles con la mayoría de versiones de Android. Veamos cómo quedaría nuestra clase asociada al fragment de listado.</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.fragments;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class FragmentListado extends Fragment {

	private Correo[] datos =
	    	new Correo[]{
	    		new Correo(&quot;Persona 1&quot;, &quot;Asunto del correo 1&quot;, &quot;Texto del correo 1&quot;),
	    		new Correo(&quot;Persona 2&quot;, &quot;Asunto del correo 2&quot;, &quot;Texto del correo 2&quot;),
	    		new Correo(&quot;Persona 3&quot;, &quot;Asunto del correo 3&quot;, &quot;Texto del correo 3&quot;),
	    		new Correo(&quot;Persona 4&quot;, &quot;Asunto del correo 4&quot;, &quot;Texto del correo 4&quot;),
	    		new Correo(&quot;Persona 5&quot;, &quot;Asunto del correo 5&quot;, &quot;Texto del correo 5&quot;)};

	private ListView lstListado;

	@Override
	public View onCreateView(LayoutInflater inflater,
			                 ViewGroup container,
			                 Bundle savedInstanceState) {

		return inflater.inflate(R.layout.fragment_listado, container, false);
	}

	@Override
	public void onActivityCreated(Bundle state) {
		super.onActivityCreated(state);

		lstListado = (ListView)getView().findViewById(R.id.LstListado);

		lstListado.setAdapter(new AdaptadorCorreos(this));
	}

	class AdaptadorCorreos extends ArrayAdapter&lt;Correo&gt; {

    		Activity context;

    		AdaptadorCorreos(Fragment context) {
    			super(context.getActivity(), R.layout.listitem_correo, datos);
    			this.context = context.getActivity();
    		}

    		public View getView(int position, View convertView, ViewGroup parent) {
			LayoutInflater inflater = context.getLayoutInflater();
			View item = inflater.inflate(R.layout.listitem_correo, null);

			TextView lblDe = (TextView)item.findViewById(R.id.LblDe);
			lblDe.setText(datos[position].getDe());

			TextView lblAsunto = (TextView)item.findViewById(R.id.LblAsunto);
			lblAsunto.setText(datos[position].getAsunto());

			return(item);
		}
    	}
}
</pre>
<p>La clase Correo es una clase sencilla, que almacena los campos De, Asunto y Texto de un correo.</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.fragments;

public class Correo
{
	private String de;
	private String asunto;
	private String texto;

	public Correo(String de, String asunto, String texto){
		this.de = de;
		this.asunto = asunto;
		this.texto = texto;
	}

	public String getDe(){
		return de;
	}

	public String getAsunto(){
		return asunto;
	}

	public String getTexto(){
		return texto;
	}
}
</pre>
<p>Si observamos con detenimiento las clases anteriores veremos que no existe casi ninguna diferencia con los temas ya comentados en artículos anteriores del curso sobre utilización de controles de tipo lista y adaptadores personalizados. La única diferencia que encontramos aquí respecto a ejemplos anteriores, donde definíamos actividades en vez de fragments, son los métodos que sobrescribimos. En el caso de los fragment son normalmente dos: <span style="font-family: 'courier new', courier;">onCreateView()</span> y <span style="font-family: 'courier new', courier;">onActivityCreated()</span>.</p>
<p>El primero de ellos, <span style="font-family: 'courier new', courier;">onCreateView()</span>, es el equivalente al <span style="font-family: 'courier new', courier;">onCreate()</span> de las actividades, es decir, que dentro de él es donde normalmente asignaremos un layout determinado al fragment. En este caso tendremos que &#8220;inflarlo&#8221; mediante el método <span style="font-family: 'courier new', courier;">inflate()</span> pasándole como parámetro el ID del layout correspondiente, en nuestro caso <span style="font-family: 'courier new', courier;">fragment_listado</span>.</p>
<p>El segundo de los métodos, <span style="font-family: 'courier new', courier;">onActivityCreated()</span>, se ejecutará cuando la actividad contenedora del fragment esté completamente creada. En nuestro caso, estamos aprovechando este evento para obtener la referencia al control <span style="font-family: 'courier new', courier;">ListView</span> y asociarle su adaptador. Sobre la definición del adaptador personalizado <span style="font-family: 'courier new', courier;">AdaptadorCorreos</span> no comentaremos nada porque es idéntico al ya descrito en el <a title="Interfaz de usuario en Android: Controles de selección (II)" href="http://www.sgoliver.net/blog/?p=1414">artículo sobre listas</a>.</p>
<p>Con esto ya tenemos creado nuestro fragment de listado, por lo que podemos pasar al segundo, que como ya dijimos se encargará de mostrar la vista de detalle. La definición de este fragment será aún más sencilla que la anterior. El layout, que llamaremos <span style="font-family: 'courier new', courier;">fragment_detalle.xml</span>, tan sólo se compondrá de un cuadro de texto:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot;
    android:background=&quot;#FFBBBBBB&quot; &gt;

    &lt;TextView
        android:id=&quot;@+id/TxtDetalle&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>Y por su parte, la clase java asociada, se limitará a inflar el layout de la interfaz. Adicionalmente añadiremos un método público, llamado <span style="font-family: 'courier new', courier;">mostrarDetalle()</span>, que nos ayude posteriormente a asignar el contenido a mostrar en el cuadro de texto.</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.fragments;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class FragmentDetalle extends Fragment {

	@Override
	public View onCreateView(LayoutInflater inflater,
			                 ViewGroup container,
			                 Bundle savedInstanceState) {

		return inflater.inflate(R.layout.fragment_detalle, container, false);
	}

	public void mostrarDetalle(String texto) {
		TextView txtDetalle =
			(TextView)getView().findViewById(R.id.TxtDetalle);

		txtDetalle.setText(texto);
	}
}
</pre>
<p>Una vez definidos los dos fragments, ya tan solo nos queda definir las actividades de nuestra aplicación, con sus respectivos layouts que harán uso de los fragments que acabamos de implementar.</p>
<p>Para la actividad principal definiremos 3 layouts diferentes: el primero de ellos para los casos en los que la aplicación se ejecute en una pantalla normal (por ejemplo un teléfono móvil) y dos para pantallas grandes (uno pensado para orientación horizontal y otro para vertical). Todos se llamará <span style="font-family: 'courier new', courier;">activity_main.xml</span>, y lo que marcará la diferencia será la carpeta en la que colocamos cada uno. Así, el primero de ellos lo colocaremos en la carpeta por defecto <span style="font-family: 'courier new', courier;">/res/layout</span>, y los otros dos en las carpetas <span style="font-family: 'courier new', courier;">/res/layout-large</span> (pantalla grande) y <span style="font-family: 'courier new', courier;">/res/latout-large-port</span> (pantalla grande con orientación vertical) respectivamente. De esta forma, según el tamaño y orientación de la pantalla Android utilizará un layout u otro de forma automática sin que nosotros tengamos que hacer nada.</p>
<p>Para el caso de pantalla normal, la actividad principal mostrará tan sólo el listado de correos, por lo que el layout incluirá tan sólo el fragment <span style="font-family: 'courier new', courier;">FragmentListado</span>.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;fragment xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
	class=&quot;net.sgoliver.android.fragments.FragmentListado&quot;
	android:id=&quot;@+id/FrgListado&quot;
	android:layout_width=&quot;match_parent&quot;
	android:layout_height=&quot;match_parent&quot; /&gt;
</pre>
<p>Como podéis ver, para incluir un fragment en un layout utilizaremos una etiqueta <span style="font-family: 'courier new', courier;">&lt;fragment&gt;</span> con un atributo <span style="font-family: 'courier new', courier;">class</span> que indique la ruta completa de la clase java correspondiente al fragment, en este primer caso &#8220;<span style="font-family: 'courier new', courier;">net.sgoliver.android.fragments.FragmentListado</span>&#8220;. Los demás atributos utilizados son los que ya conocemos de <span style="font-family: 'courier new', courier;">id</span>, <span style="font-family: 'courier new', courier;">layout_width</span> y <span style="font-family: 'courier new', courier;">layout_height</span>.</p>
<p>En este caso de pantalla normal, la vista de detalle se mostrará en una segunda actividad, por lo que también tendremos que crear su layout, que llamaremos <span style="font-family: 'courier new', courier;">activity_detalle.xml</span>. Veamos rápidamente su implementación:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;fragment xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
	class=&quot;net.sgoliver.android.fragments.FragmentDetalle&quot;
	android:id=&quot;@+id/FrgDetalle&quot;
	android:layout_width=&quot;match_parent&quot;
	android:layout_height=&quot;match_parent&quot; /&gt;
</pre>
<p>Como vemos es análoga a la anterior, con la única diferencia de que añadimos el fragment de detalle en vez de el de listado.</p>
<p>Por su parte, el layout para el caso de pantalla grande horizontal, será de la siguiente forma:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout
	xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
	android:orientation=&quot;horizontal&quot;
	android:layout_width=&quot;match_parent&quot;
	android:layout_height=&quot;match_parent&quot;&gt;

	&lt;fragment class=&quot;net.sgoliver.android.fragments.FragmentListado&quot;
		android:id=&quot;@+id/FrgListado&quot;
		android:layout_weight=&quot;30&quot;
		android:layout_width=&quot;0px&quot;
		android:layout_height=&quot;match_parent&quot; /&gt;

	&lt;fragment class=&quot;net.sgoliver.android.fragments.FragmentDetalle&quot;
		android:id=&quot;@+id/FrgDetalle&quot;
		android:layout_weight=&quot;70&quot;
		android:layout_width=&quot;0px&quot;
		android:layout_height=&quot;match_parent&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>Como veis en este caso incluimos los dos fragment en la misma pantalla, ya que tendremos espacio de sobra, ambos dentro de un LinearLayout horizontal, asignando al primero de ellos un peso (propiedad <span style="font-family: 'courier new', courier;">layout_weight</span>) de 30 y al segundo de 70 para que la columna de listado ocupe un 30% de la pantalla a la izquierda y la de detalle ocupe el resto.</p>
<p>Por último, para el caso de pantalla grande vertical será practicamente igual, sólo que usaremos un <span style="font-family: 'courier new', courier;">LinearLayout</span> vertical.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout
	xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
	android:orientation=&quot;vertical&quot;
	android:layout_width=&quot;match_parent&quot;
	android:layout_height=&quot;match_parent&quot;&gt;

	&lt;fragment class=&quot;net.sgoliver.android.fragments.FragmentListado&quot;
		android:id=&quot;@+id/FrgListado&quot;
		android:layout_weight=&quot;40&quot;
		android:layout_width=&quot;match_parent&quot;
		android:layout_height=&quot;0px&quot; /&gt;

	&lt;fragment class=&quot;net.sgoliver.android.fragments.FragmentDetalle&quot;
		android:id=&quot;@+id/FrgDetalle&quot;
		android:layout_weight=&quot;60&quot;
		android:layout_width=&quot;match_parent&quot;
		android:layout_height=&quot;0px&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>Hecho esto, ya podríamos ejecutar la aplicación en el emulador y comprobar si se selecciona automáticamente el layout correcto dependiendo de las características del AVD que estemos utilizando. En mi caso he definido 2 AVD, uno con pantalla normal y otro grande al que durante las pruebas he modificado su orientación (pulsando Ctrl+F12). El resultado fue el siguiente:</p>
<p>Pantalla normal (Galaxy Nexus &#8211; 4.7 pulgadas):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-movil-and4-port.png"><img class="alignnone size-medium wp-image-3512" alt="fragments-movil-and4-port" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-movil-and4-port-168x300.png" width="168" height="300" /></a></p>
<p>Pantalla grande vertical (Nexus 7 &#8211; 7.3 pulgadas):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-tablet-port.png"><img class="alignnone size-medium wp-image-3514" alt="fragments-tablet-port" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-tablet-port-187x300.png" width="187" height="300" /></a></p>
<p>Pantalla grande horizontal (Nexus 7 &#8211; 7.3 pulgadas):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-tablet-land.png"><img class="alignnone size-medium wp-image-3513" alt="fragments-tablet-land" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/fragments-tablet-land-300x188.png" width="300" height="188" /></a></p>
<p>Como vemos en las imágenes anteriores, la interfaz se ha adaptado perfectamente a la pantalla en cada caso, mostrándose uno o ambos fragments, y en caso de mostrarse ambos distribuyéndose horizontal o verticalmente.</p>
<p>Lo que aún no hemos implementado en la lógica de la aplicación es lo que debe ocurrir al pulsarse un elemento de la lista de correos. Para ello, empezaremos asignando el evento <span style="font-family: 'courier new', courier;">onItemClick()</span> a la lista dentro del método <span style="font-family: 'courier new', courier;">onActivityCreated()</span> de la clase <span style="font-family: 'courier new', courier;">FragmentListado</span>. Lo que hagamos al capturar este evento dependerá de si en la pantalla se está viendo el fragment de detalle o no:</p>
<ol>
<li>Si existe el fragment de detalle habría que obtener una referencia a él y llamar a su método <span style="font-family: 'courier new', courier;">mostrarDetalle()</span> con el texto del correo seleccionado.</li>
<li>En caso contrario, tendríamos que navegar a la actividad secundaria <span style="font-family: 'courier new', courier;">DetalleActivity</span> para mostrar el detalle.</li>
</ol>
<p>Sin embargo existe un problema, un fragment no tiene por qué conocer la existencia de ningún otro, es más, deberían diseñarse de tal forma que fueran lo más independientes posible, de forma que puedan reutilizarse en distintas situaciones sin problemas de dependencias con otros elementos de la interfaz. Por este motivo, el patrón utilizado normalmente en estas circunstancias no será tratar el evento en el propio fragment, sino definir y lanzar un evento personalizado al pulsarse el item de la lista y delegar a la actividad contenedora la lógica del evento, ya que ella sí debe conocer qué fragments componen su interfaz. ¿Cómo hacemos esto? Pues de forma análoga a <a title="Interfaz de usuario en Android: Controles personalizados (II)" href="http://www.sgoliver.net/blog/?p=1467">cuando definimos eventos personalizados para un control</a>. Definimos una interfaz con el método asociado al evento, en este caso llamada <span style="font-family: 'courier new', courier;">CorreosListener</span> con un único método llamado <span style="font-family: 'courier new', courier;">onCorreoSeleccionado()</span>, declaramos un atributo de la clase con esta interfaz y definimos un método <span style="font-family: 'courier new', courier;">set&#8230;()</span> para poder asignar el evento desde fuera de la clase. Veamos cómo quedaría:</p>
<pre class="brush: java; title: ; notranslate">
public class FragmentListado extends Fragment {

	//...

	private CorreosListener listener;

	//..

	@Override
	public void onActivityCreated(Bundle state) {
		super.onActivityCreated(state);

		lstListado = (ListView)getView().findViewById(R.id.LstListado);

		lstListado.setAdapter(new AdaptadorCorreos(this));

		lstListado.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView&lt;?&gt; list, View view, int pos, long id) {
				if (listener!=null) {
					listener.onCorreoSeleccionado(
						(Correo)lstListado.getAdapter().getItem(pos));
				}
			}
		});
	}

	public interface CorreosListener {
		void onCorreoSeleccionado(Correo c);
	}

	public void setCorreosListener(CorreosListener listener) {
		this.listener=listener;
	}
}
</pre>
<p>Como vemos, una vez definida toda esta parafernalia, lo único que deberemos hacer en el evento <span style="font-family: 'courier new', courier;">onItemClick()</span> de la lista será lanzar nuestro evento personalizado <span style="font-family: 'courier new', courier;">onCorreoSeleccionado()</span> pasándole como parámetro el contenido del correo, que en este caso obtendremos accediendo al adaptador con <span style="font-family: 'courier new', courier;">getAdapter()</span> y recuperando el elemento con <span style="font-family: 'courier new', courier;">getItem()</span>.</p>
<p>Hecho esto, el siguiente paso será tratar este evento en la clase java de nuestra actividad principal. Para ello, en el <span style="font-family: 'courier new', courier;">onCreate()</span> de nuestra actividad, obtendremos una referencia al fragment mediante el método <span style="font-family: 'courier new', courier;">getFragmentById()</span> del fragment manager (componente encargado de gestionar los fragments) y asignaremos el evento mediante su método <span style="font-family: 'courier new', courier;">setCorreosListener()</span> que acabamos de definir.</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.fragments;

import net.sgoliver.android.fragments.FragmentListado.CorreosListener;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity implements CorreosListener {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		FragmentListado frgListado
			=(FragmentListado)getSupportFragmentManager()
				.findFragmentById(R.id.FrgListado);

		frgListado.setCorreosListener(this);
	}

	@Override
	public void onCorreoSeleccionado(Correo c) {
		boolean hayDetalle =
			(getSupportFragmentManager().findFragmentById(R.id.FrgDetalle) != null);

		if(hayDetalle) {
			((FragmentDetalle)getSupportFragmentManager()
				.findFragmentById(R.id.FrgDetalle)).mostrarDetalle(c.getTexto());
		}
		else {
			Intent i = new Intent(this, DetalleActivity.class);
			i.putExtra(DetalleActivity.EXTRA_TEXTO, c.getTexto());
			startActivity(i);
		}
	}
}
</pre>
<p>Como vemos en el código anterior, en este caso hemos hecho que nuestra actividad herede de nuestra interfaz <span style="font-family: 'courier new', courier;">CorreosListener</span>, por lo que nos basta pasar this al método <span style="font-family: 'courier new', courier;">setCorreosListener()</span>. Adicionalmente, un detalle importante a descatar es que la actividad no hereda de la clase <span style="font-family: 'courier new', courier;">Activity</span> como de costumbre, sino de <span style="font-family: 'courier new', courier;">FragmentActivity</span>. Esto es así porque estamos utilizando la librería de compatibilidad android-support para utilizar fragments conservando la compatibilidad con versiones de Android anteriores a la 3.0. En caso de no necesitar esta compatibilidad podrías seguir heredando de <span style="font-family: 'courier new', courier;">Activity</span> sin problemas.</p>
<p>La mayor parte del interés de la clase anterior está en el método <span style="font-family: 'courier new', courier;">onCorreoSeleccionado()</span>. Éste es el método que se ejecutará cuando el fragment de listado nos avise de que se ha seleccionado un determinado item de la lista. Esta vez sí, la lógica será la ya mencionada, es decir, si en la pantalla existe el fragment de detalle simplemente lo actualizaremos mediante <span style="font-family: 'courier new', courier;">mostrarDetalle()</span> y en caso contrario navegaremos a la actividad <span style="font-family: 'courier new', courier;">DetalleActivity</span>. Para este segundo caso, crearemos un nuevo Intent con la referencia a dicha clase, y le añadiremos como parámetro extra un campo de texto con el contenido del correo seleccionado. Finalmente llamamos a <span style="font-family: 'courier new', courier;">startActivity()</span> para iniciar la nueva actividad.</p>
<p>Y ya sólo nos queda comentar la implementación de esta segunda actividad, <span style="font-family: 'courier new', courier;">DetalleActivity</span>. El código será muy sencillo, y se limitará a recuperar el parámetro extra pasado desde la actividad anterior y mostrarlo en el fragment de detalle mediante su método <span style="font-family: 'courier new', courier;">mostrarDetalle()</span>, todo ello dentro de su método <span style="font-family: 'courier new', courier;">onCreate()</span>.</p>
<pre class="brush: java; title: ; notranslate">
package net.sgoliver.android.fragments;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class DetalleActivity extends FragmentActivity {

	public static final String EXTRA_TEXTO =
		&quot;net.sgoliver.android.fragments.EXTRA_TEXTO&quot;;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_detalle);

		FragmentDetalle detalle =
			(FragmentDetalle)getSupportFragmentManager()
				.findFragmentById(R.id.FrgDetalle);

		detalle.mostrarDetalle(
			getIntent().getStringExtra(EXTRA_TEXTO));
	}
}
</pre>
<p>Y con esto finalizaríamos nuestro ejemplo. Si ahora volvemos a ejecutar la aplicación en el emulador podremos comprobar el funcionamiento de la selección en las distintas configuraciones de pantalla.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="Fragments - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-fragments" target="_blank">curso en GitHub</a>.<br />
<script type="text/javascript"><!--
            google_ad_client = "ca-pub-3420044082155166";
            /* Banner Cabecera Blog */
            google_ad_slot = "3076493448";
            google_ad_width = 250;
            google_ad_height = 250;
            //-->
            </script><br />
            <script type="text/javascript"
            src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
            </script></p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3496</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Código del curso en GitHub</title>
		<link>http://www.sgoliver.net/blog/?p=3462&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=codigo-del-curso-en-github</link>
		<comments>http://www.sgoliver.net/blog/?p=3462#comments</comments>
		<pubDate>Sat, 12 Jan 2013 19:47:08 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[curso]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[programación]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3462</guid>
		<description><![CDATA[Como parte de la actualización del Curso de Programación Android que estoy realizando estos días, he decidido también cambiar la forma de acceder al código de los ejemplos desarrollados en cada artículo. Hasta ahora, lo normal era que al final de cada artículo encontrárais un enlace para descargar el código de ejemplo completo en un [...]]]></description>
				<content:encoded><![CDATA[<p>Como parte de la actualización del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">Curso de Programación Android</a> que estoy realizando estos días, he decidido también cambiar la forma de acceder al código de los ejemplos desarrollados en cada artículo. Hasta ahora, lo normal era que al final de cada artículo encontrárais un enlace para descargar el código de ejemplo completo en un fichero ZIP, un metodo sencillo y directo pero que no permite consultar online el código completo del ejemplo, siempre debíais descargarlo previamente.</p>
<p>Para solucionar esto, y con la intención de facilitar aún más la tarea de seguir los tutoriales, he decidido crear un <a title="Curso de Programación Android en GitHub" href="https://github.com/sgolivernet/curso-android-src" target="_blank">proyecto en GitHub</a> donde iré añadiendo todos los proyectos de ejemplo desarrollados en los diferentes artículos del curso. De esta forma, además de seguir contando con la posibilidad de descargar todo el código en un ZIP, podréis acceder y consultar online cada fichero de cada proyecto del curso.</p>
<p>Al final de cada artículo colocaré el enlace al proyecto de ejemplo correspondiente en GitHub (ojo, aún no están todos, estoy en pleno proceso de actualización). Al pulsarlo, accederéis a una página similar a la de la siguiente imagen (click para ampliar):<br />
<script type="text/javascript"><!--
google_ad_client = "ca-pub-4423798007798708";
/* home-728 */
google_ad_slot = "2494167070";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script><br />
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/github-1.png"><img class="alignnone size-medium wp-image-3463" alt="Curso en GitHub 1" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/github-1-300x123.png" width="300" height="123" /></a></p>
<p>Mediante el botón &#8220;ZIP&#8221; (marcador 2) podréis descargar el código completo del curso en formato ZIP, y para visualizar online el contenido de cualquier fichero o carpeta no tendréis más que pulsar sobre él en el listado de la parte inferior (marcador 1). Si accedéis por ejemplo a algún fichero de código veréis algo como lo siguiente (click para ampliar):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/github-2.png"><img class="alignnone size-medium wp-image-3464" alt="Curso en GitHub 2" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/github-2-300x183.png" width="300" height="183" /></a></p>
<p>Por otro lado, si tenéis usuario de GitHub y accedéis con él también podréis activar las opciones de seguimiento del proyecto (<em>Watch</em>) o  marcarlo como favorito (<em>Star</em>) (marcador 3). Y por supuesto, como cualquier otro proyecto de GitHub, también podréis crear vuestro propio <em>fork</em> para trabajar sobre él (marcador 4).</p>
<p>En fin, espero que esto os ayude a seguir el curso más fácilmente que antes. Saludos.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3462</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Estadísticas del blog en 2012</title>
		<link>http://www.sgoliver.net/blog/?p=3432&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=estadisticas-del-blog-en-2012</link>
		<comments>http://www.sgoliver.net/blog/?p=3432#comments</comments>
		<pubDate>Wed, 02 Jan 2013 19:41:17 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[2012]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[estadísticas]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3432</guid>
		<description><![CDATA[Como cada año, voy a repasar las estadísticas que me ha dejado el blog durante el año que acaba de terminar. Para seguir con la tradición comienzo mostrando en una imagen las estadísticas generales: Me enorgullece comprobar cómo el número de visitas totales se ha duplicado respecto al año pasado, llegando en esta ocasión hasta [...]]]></description>
				<content:encoded><![CDATA[<p>Como cada año, voy a repasar las estadísticas que me ha dejado el blog durante el año que acaba de terminar. Para seguir con la tradición comienzo mostrando en una imagen las estadísticas generales:</p>
<p><a href="http://www.sgoliver.net/blog/?attachment_id=3433" rel="attachment wp-att-3433"><img class="alignnone size-full wp-image-3433" alt="estadisticas-generales-2012" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/estadisticas-generales-2012.png" width="404" height="316" /></a></p>
<p>Me enorgullece comprobar cómo el número de visitas totales se ha duplicado respecto al año pasado, llegando en esta ocasión hasta casi las <strong>900,000 visitas</strong>, con casi <strong>400.000 visitantes únicos</strong>, un 110% superior al 2011. El número de páginas vistas también se duplica respecto al dato anterior, sobrepasando los <strong>2,5 millones de páginas</strong>.</p>
<p>El número de páginas vistas por visita se ha reducido ligeramente, de 2.92 a 2.88, probablemente debido a la reorganización de las páginas del blog dedicadas al <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">Curso de Programación Android</a>, aunque de cualquier forma habrá que trabajar en este dato durante 2013. Sin embargo, el tiempo medio por visita ha aumentado casi un 11,68% llegando casi a los 6 minutos.</p>
<p>Por último, el porcentaje de rebote practicamente se mantiene respecto al año pasado quedando en un 53.37%, un 0.21% mejor que e 2011.</p>
<p>Si miramos ahora la procedencia de los visitantes del blog, el panorama es exactamente el mismo que en 2011, salvo Estados Unidos que ha caido un puesto:</p>
<p><a href="http://www.sgoliver.net/blog/?attachment_id=3434" rel="attachment wp-att-3434"><img class="alignnone size-full wp-image-3434" alt="estadisticas-por-pais-2012" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/estadisticas-por-pais-2012.png" width="583" height="318" /></a></p>
<p>Una vez más, mi más sincero agradecimiento a los lectores de América central y del Sur por seguirme desde el otro lado del charco.</p>
<p>Por otro lado, las páginas más visitadas en el año 2012 han vuelto a ser con gran diferencia las dedicadas al Curso de Programación Android, especialmente el <a title="Curso Programación Android – Indice de Contenidos" href="http://www.sgoliver.net/blog/?page_id=3011">índice de contenidos</a>, la sección dedicada a la <a title="Curso Programación Android – PDF" href="http://www.sgoliver.net/blog/?page_id=2245">obtención del curso en formato PDF</a>, y los artículos iniciales sobre la <a title="Entorno de desarrollo Android" href="http://www.sgoliver.net/blog/?p=1267">instalación y configuración del entorno de desarrollo para Android</a>, la <a title="Estructura de un proyecto Android" href="http://www.sgoliver.net/blog/?p=1278">estructura de un proyecto Android</a>, y la <a title="Desarrollando una aplicación Android sencilla" href="http://www.sgoliver.net/blog/?p=1316">creación de una aplicación Android sencilla de ejemplo</a>.</p>
<p><a href="http://www.sgoliver.net/blog/?attachment_id=3435" rel="attachment wp-att-3435"><img class="alignnone size-full wp-image-3435" alt="paginas-mas-vistas-2012" src="http://www.sgoliver.net/blog/wp-content/uploads/2013/01/paginas-mas-vistas-2012.png" width="487" height="479" /></a></p>
<p>&nbsp;</p>
<p>En definitiva, unos números que jamás hubiera imaginado cuando comencé a escribir este blog, que han mejorado enormemente en los dos últimos años gracias a la gran acogida del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">Curso de Programación Android</a> que continúo publicando y mejorando poco a poco, y que espero que siga creciendo durante este nuevo año 2013 que acabamos de recibir. Muchas gracias a todos por vuestro apoyo y colaboración, y espero seguir dando motivos este 2013 para que sigáis visitando mi humilde morada en la web. Feliz 2013!</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3432</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Mapas en Android (Google Maps Android API v2) – III</title>
		<link>http://www.sgoliver.net/blog/?p=3286&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mapas-en-android-google-maps-android-api-v2-iii</link>
		<comments>http://www.sgoliver.net/blog/?p=3286#comments</comments>
		<pubDate>Mon, 10 Dec 2012 18:41:41 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[dibujo]]></category>
		<category><![CDATA[eventos]]></category>
		<category><![CDATA[GoogleMap]]></category>
		<category><![CDATA[lineas]]></category>
		<category><![CDATA[mapas]]></category>
		<category><![CDATA[MapFragment]]></category>
		<category><![CDATA[maps]]></category>
		<category><![CDATA[marcadores]]></category>
		<category><![CDATA[Marker]]></category>
		<category><![CDATA[MarkerOptions]]></category>
		<category><![CDATA[Polygon]]></category>
		<category><![CDATA[Polyline]]></category>
		<category><![CDATA[v2]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3286</guid>
		<description><![CDATA[En los dos artículos anteriores (I y II) del curso hemos visto cómo crear aplicaciones utilizando la nueva versión de la API v2 de Google Maps para Android y hemos descrito las acciones principales sobre el mapa. En este último artículo de la serie nos vamos a centrar en los eventos del mapa que podemos [...]]]></description>
				<content:encoded><![CDATA[<p>En los dos artículos anteriores (<a title="Mapas en Android (Google Maps Android API v2) – I" href="http://www.sgoliver.net/blog/?p=3244">I</a> y <a title="Mapas en Android (Google Maps Android API v2) – II" href="http://www.sgoliver.net/blog/?p=3271">II</a>) del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">curso</a> hemos visto cómo crear aplicaciones utilizando la nueva versión de la API v2 de Google Maps para Android y hemos descrito las acciones principales sobre el mapa.</p>
<p>En este último artículo de la serie nos vamos a centrar en los eventos del mapa que podemos capturar y tratar, en la creación y gestión de marcadores, y en el dibujo de lineas y polígonos sobre el mapa.</p>
<p>Sin más preámbulos, comencemos con los eventos. Si hacemos un poco de memoria, con la versión anterior de la API debíamos crear una nueva capa (<em>overlay</em>) para capturar los eventos principales de pulsación. Sin embargo, el nuevo componente de mapas soporta directamente los eventos de click, click largo y movimiento de cámara y podemos implementarlos de la forma habitual, mediante su método set correspondiente.</p>
<p>Así por ejemplo, podríamos implementar el evento de <em>onclick</em> llamando al método <span style="font-family: 'courier new', courier;">setOnMapClickListener()</span> con un nuevo listener y sobrescribir el método <span style="font-family: 'courier new', courier;">onMapClick()</span>. Este método recibe como parámetro, en forma de objeto <span style="font-family: 'courier new', courier;">LatLng</span>, las coordenadas de latitud y longitud sobre las que ha pulsado el usuario. Si quisiéramos traducir estas coordenadas física en coordenadas en pantalla podríamos utilizar un objeto <span style="font-family: 'courier new', courier;">Projection</span> (similar al que ya comentamos para la API v1), obteniéndolo a partir del mapa a través del método <span style="font-family: 'courier new', courier;">getProjection()</span> y posteriormente llamando a <span style="font-family: 'courier new', courier;">toScreenLocation()</span> para obtener las coordenadas (x,y) de pantalla donde el usuario pulsó.</p>
<p>Así, por ejemplo, si quisiéramos mostrar un Toast con todos estos datos cada vez que se pulse sobre el mapa podríamos hacer lo siguiente:</p>
<pre class="brush: java; title: ; notranslate">
mapa.setOnMapClickListener(new OnMapClickListener() {
	public void onMapClick(LatLng point) {
		Projection proj = mapa.getProjection();
		Point coord = proj.toScreenLocation(point);

		Toast.makeText(
			MainActivity.this,
			&quot;Click\n&quot; +
			&quot;Lat: &quot; + point.latitude + &quot;\n&quot; +
			&quot;Lng: &quot; + point.longitude + &quot;\n&quot; +
			&quot;X: &quot; + coord.x + &quot; - Y: &quot; + coord.y,
			Toast.LENGTH_SHORT).show();
	}
});
</pre>
<p>De forma similar podríamos implementar el evento de pulsación larga, con la única diferencia de que lo asignaríamos mediante <span style="font-family: 'courier new', courier;">setOnMapLongClickListener()</span> y sobrescribiríamos el método <span style="font-family: 'courier new', courier;">onMapLongClick()</span>.</p>
<pre class="brush: java; title: ; notranslate">
mapa.setOnMapLongClickListener(new OnMapLongClickListener() {
	public void onMapLongClick(LatLng point) {
		Projection proj = mapa.getProjection();
		Point coord = proj.toScreenLocation(point);

		Toast.makeText(
			MainActivity.this,
			&quot;Click Largo\n&quot; +
			&quot;Lat: &quot; + point.latitude + &quot;\n&quot; +
			&quot;Lng: &quot; + point.longitude + &quot;\n&quot; +
			&quot;X: &quot; + coord.x + &quot; - Y: &quot; + coord.y,
			Toast.LENGTH_SHORT).show();
	}
});
</pre>
<p>Así, cuando el usuario hiciera una pulsación larga sobre el mapa veríamos los datos en pantalla de la siguiente forma:<br />
<a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/click-largo1.png"><img class="alignnone size-medium wp-image-3288" title="click-largo" alt="click-largo" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/click-largo1-180x300.png" width="180" height="300" /></a></p>
<p>También podremos capturar el evento de cambio de cámara, de forma que podamos realizar determinadas acciones cada vez que el usuario se mueve manualmente por el mapa, desplazándolo, haciendo zoom, o modificando la orientación o el ángulo de visión. Este evento lo asignaremos al mapa mediante su método <span style="font-family: 'courier new', courier;">setOnCameraChangeListener()</span> y sobrescribiendo el método <span style="font-family: 'courier new', courier;">onCameraChange()</span>. Este método recibe como parámetro un objeto <span style="font-family: 'courier new', courier;">CameraPosition</span>, que ya vimos en el artículo anterior, por lo que podremos recuperar de él todos los datos de la cámara en cualquier momento.</p>
<p>De esta forma, si quisiéramos mostrar un Toast con todos los datos podríamos hacer lo siguiente:</p>
<pre class="brush: java; title: ; notranslate">
mapa.setOnCameraChangeListener(new OnCameraChangeListener() {
	public void onCameraChange(CameraPosition position) {
		Toast.makeText(
			MainActivity.this,
			&quot;Cambio Cámara\n&quot; +
			&quot;Lat: &quot; + position.target.latitude + &quot;\n&quot; +
			&quot;Lng: &quot; + position.target.longitude + &quot;\n&quot; +
			&quot;Zoom: &quot; + position.zoom + &quot;\n&quot; +
			&quot;Orientación: &quot; + position.bearing + &quot;\n&quot; +
			&quot;Ángulo: &quot; + position.tilt,
			Toast.LENGTH_SHORT).show();
	}
});
</pre>
<p>Hecho esto, cada vez que el usuario se mueva por el mapa veríamos lo siguiente:</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/cambio-camara.png"><img class="alignnone size-medium wp-image-3289" title="cambio-camara" alt="cambio-camara" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/cambio-camara-180x300.png" width="180" height="300" /></a></p>
<p>El siguiente tema importante que quería tratar en este artículo es el de los marcadores. Rara es la aplicación Android que hace uso de mapas sin utilizar también este tipo de elementos para resaltar determinados puntos en el mapa. Si recordamos el <a title="Mapas en Android (III): Overlays (Capas)" href="http://www.sgoliver.net/blog/?p=2004">artículo sobre la API v1</a>, vimos cómo podíamos añadir marcadores añadiendo una nueva capa (<em>overlay</em>) al mapa y dibujando nuestro marcador como parte de su evento <span style="font-family: 'courier new', courier;">draw()</span>. En la nueva versión de la API tendemos toda esta funcionalidad integrada en la propia vista de mapa, y agregar un marcador resulta tan sencillo como llamar al método <span style="font-family: 'courier new', courier;">addMarker()</span> pasándole la posición en forma de objeto <span style="font-family: 'courier new', courier;">LatLng</span> y el texto a mostrar en la ventana de información del marcador. En nuestra aplicación de ejemplo añadiremos un menú de forma que cuando lo pulsemos se añada automáticamente un marcador sobre España con el texto &#8220;<em>Pais: España</em>&#8220;. Veamos cómo escribir un método auxiliar que nos ayuda a hacer esto pasándole las coordenadas de latitud y longitud:</p>
<pre class="brush: java; title: ; notranslate">
private void mostrarMarcador(double lat, double lng)
{
    mapa.addMarker(new MarkerOptions()
        .position(new LatLng(lat, lng))
        .title(&quot;Pais: España&quot;));
}
</pre>
<p>Así de sencillo, basta con llamar al método <span style="font-family: 'courier new', courier;">addMarker()</span> pasando como parámetro un nuevo objeto <span style="font-family: 'courier new', courier;">MarkerOptions</span> sobre el que establecemos la posición del marcador (método <span style="font-family: 'courier new', courier;">position()</span>) y el texto a incluir en la ventana de información del marcador (métodos <span style="font-family: 'courier new', courier;">title()</span> para el título y <span style="font-family: 'courier new', courier;">snippet()</span> para el resto del texto). Si ejecutamos la aplicación de ejemplo y pulsamos el menú &#8220;Marcadores&#8221; aparecerá el siguiente marcador sobre el mapa (la ventana de información aparece si además se pulsa sobre el marcador):</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/marcadores.png"><img class="alignnone size-medium wp-image-3292" title="marcadores mapa android" alt="marcadores mapa android" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/marcadores-180x300.png" width="180" height="300" /></a></p>
<p>Además de facilitarnos la vida con la inclusión de marcadores en el mapa también tendremos ayuda a la hora de capturar el evento de pulsación sobre un marcador, ya que podremos asignar al mapa dicho evento como cualquiera de los comentados anteriormente. En este caso el evento se asignará al mapa mediante el método <span style="font-family: 'courier new', courier;">setOnMarkerClickListener()</span> y sobrescribiremos el método <span style="font-family: 'courier new', courier;">onMarkerClick()</span>. Dicho método recibe como parámetro el objeto <span style="font-family: 'courier new', courier;">Marker</span> pulsado, de forma que podamos identificarlo accediendo a su información (posición, título, texto, &#8230;). Veamos un ejemplo donde mostramos un <em>toast</em> con el título del marcador pulsado:</p>
<pre class="brush: java; title: ; notranslate">
mapa.setOnMarkerClickListener(new OnMarkerClickListener() {
	public boolean onMarkerClick(Marker marker) {
		Toast.makeText(
			MainActivity.this,
			&quot;Marcador pulsado:\n&quot; +
			marker.getTitle(),
			Toast.LENGTH_SHORT).show();

		return false;
	}
});
</pre>
<p>Con el código anterior, si pulsamos sobre el marcador de España aparecerá el siguiente mensaje informativo:</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/click-marcadores.png"><img class="alignnone size-medium wp-image-3291" title="click-marcadores" alt="click-marcadores" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/click-marcadores-180x300.png" width="180" height="300" /></a></p>
<p>Todo lo explicado sobre marcadores corresponde al comportamiento por defecto de la API, sin embargo también es posible por supuesto personalizar determinadas cosas, como por ejemplo el aspecto de los marcadores. Esto se sale un poco de este artículo, donde pretendía describir los temas más básicos, pero para quien esté interesado tan sólo decir que mediante los métodos <span style="font-family: 'courier new', courier;">icon()</span> y <span style="font-family: 'courier new', courier;">anchor()</span> del objeto <span style="font-family: 'courier new', courier;">MakerOptions</span> que hemos visto antes es posible utilizar una imagen personalizada para mostrar como marcador en el mapa. En la <a title="Marker personalizado" href="https://developers.google.com/maps/documentation/android/marker" target="_blank">documentación oficial</a> (en inglés) podéis encontrar un ejemplo de cómo hacer esto.</p>
<p>Como último tema, vamos a ver cómo dibujar líneas y polígonos sobre el mapa, elementos muy comunmente utilizados para trazar rutas o delimitar zonas del mapa. Para realizar esto en la versión 2 de la API vamos a actuar una vez más directamente sobre la vista de mapa, sin necesidad de añadir <em>overlays</em> o similares, y ayudándonos de los objetos <span style="font-family: 'courier new', courier;">PolylineOptions</span> y <span style="font-family: 'courier new', courier;">PolygonOptions</span> respectivamente.</p>
<p>Para dibujar una linea lo primero que tendremos que hacer será crear un nuevo objeto <span style="font-family: 'courier new', courier;">PolylineOptions</span> sobre el que añadiremos utilizando su método <span style="font-family: 'courier new', courier;">add()</span> las coordenadas (latitud-longitud) de todos los puntos que conformen la linea. Tras esto estableceremos el grosor y color de la linea llamando a los métodos <span style="font-family: 'courier new', courier;">width()</span> y <span style="font-family: 'courier new', courier;">color()</span> respectivamente, y por último añadiremos la linea al mapa mediante su método <span style="font-family: 'courier new', courier;">addPolyline()</span> pasándole el objeto <span style="font-family: 'courier new', courier;">PolylineOptions</span> recién creado.</p>
<p>En nuestra aplicación de ejemplo he añadido un nuevo menú para dibujar un rectángulo sobre España. Veamos cómo queda:</p>
<pre class="brush: java; title: ; notranslate">
private void mostrarLineas()
{
	//Dibujo con Lineas

	PolylineOptions lineas = new PolylineOptions()
	        .add(new LatLng(45.0, -12.0))
	        .add(new LatLng(45.0, 5.0))
	        .add(new LatLng(34.5, 5.0))
	        .add(new LatLng(34.5, -12.0))
	        .add(new LatLng(45.0, -12.0));

	lineas.width(8);
	lineas.color(Color.RED);

	mapa.addPolyline(lineas);
}
</pre>
<p>Ejecutando esta acción en el emulador veríamos lo siguiente:</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/mapa-lineas.png"><img class="alignnone size-medium wp-image-3293" title="mapa-lineas" alt="mapa-lineas" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/mapa-lineas-180x300.png" width="180" height="300" /></a></p>
<p>Pues bien, esto mismo podríamos haberlo logrado mediante el dibujo de polígonos, cuyo funcionamiento es muy similar. Para ello crearíamos un nuevo objeto <span style="font-family: 'courier new', courier;">PolygonOptions</span> y añadiremos las coordenadas de sus puntos en el sentido de las agujas del reloj. En este caso no es necesario cerrar el circuito (es decir, que la primera coordenada y la última fueran iguales) ya que se hace de forma automática. Otra diferencia es que para polígonos el ancho y color de la linea los estableceríamos mediante los métodos <span style="font-family: 'courier new', courier;">strokeWidth()</span> y <span style="font-family: 'courier new', courier;">strokeColor()</span>. Además, el dibujo final del polígono sobre el mapa lo haríamos mediante <span style="font-family: 'courier new', courier;">addPolygon()</span>. En nuestro caso quedaría como sigue:</p>
<pre class="brush: java; title: ; notranslate">
//Dibujo con polígonos

PolygonOptions rectangulo = new PolygonOptions()
              .add(new LatLng(45.0, -12.0),
            	   new LatLng(45.0, 5.0),
            	   new LatLng(34.5, 5.0),
            	   new LatLng(34.5, -12.0),
            	   new LatLng(45.0, -12.0));

rectangulo.strokeWidth(8);
rectangulo.strokeColor(Color.RED);

mapa.addPolygon(rectangulo);
</pre>
<p>El resultado al ejecutar la acción en el emulador debería ser exactamente igual que el anterior.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="Mapas Android API v2 - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-mapas-api2-p3" target="_blank">curso en GitHub</a>.</p>
<p>Y con esto habríamos concluido la serie de tres artículos destinados a describir el funcionamiento básico de la nueva API v2 de Google Maps para Android. Espero haber aclarado las dudas principales a la hora de comenzar a utilizar la nueva API. Tampoco descarto algún artículo adicional para comentar temas algo más avanzados sobre esta API, pero eso será más adelante.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3286</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>Mapas en Android (Google Maps Android API v2) – II</title>
		<link>http://www.sgoliver.net/blog/?p=3271&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mapas-en-android-google-maps-android-api-v2-ii</link>
		<comments>http://www.sgoliver.net/blog/?p=3271#comments</comments>
		<pubDate>Sun, 09 Dec 2012 12:47:01 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Noticias]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[animateCamera]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[CameraPosition]]></category>
		<category><![CDATA[CameraUpdate]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[GoogleMap]]></category>
		<category><![CDATA[LatLng]]></category>
		<category><![CDATA[mapas]]></category>
		<category><![CDATA[MapFragment]]></category>
		<category><![CDATA[maps]]></category>
		<category><![CDATA[moveCamera]]></category>
		<category><![CDATA[v2]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3271</guid>
		<description><![CDATA[En el artículo anterior del curso vimos cómo realizar todos los preparativos necesarios para comenzar a utilizar la nueva versión de Google Maps para Android (Google Maps Android API v2): descargar las librerías necesarias, obtener la API Key y configurar un nuevo proyecto en Eclipse. En esta segunda entrega vamos a hacer un repaso de las opciones [...]]]></description>
				<content:encoded><![CDATA[<p>En el <a title="Google Maps Android API v2 - Preparativos" href="http://www.sgoliver.net/blog/?p=3244">artículo anterior</a> del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">curso</a> vimos cómo realizar todos los preparativos necesarios para comenzar a utilizar la nueva versión de Google Maps para Android (<a title="Web oficial Google Maps Android API v2" href="https://developers.google.com/maps/documentation/android/"><em>Google Maps Android API v2</em></a>): descargar las librerías necesarias, obtener la <em>API Key</em> y configurar un nuevo proyecto en Eclipse.</p>
<p>En esta segunda entrega vamos a hacer un repaso de las opciones básicas de los nuevos mapas:  elegir el tipo de mapa a mostrar, movernos por él de forma programática, y obtener los datos de la posición actual. Como aplicación de ejemplo (que podéis descargar al final de este artículo), tomaremos como base la ya creada en el artículo anterior, a la que añadiremos varias opciones de menú para demostrar el funcionamiento de algunas funciones del mapa.</p>
<p>Si hacemos un poco de memoria, recordaremos cómo en la antigua versión de la API de Google Maps era bastante poco homogéneo el acceso y modificación de determinados datos del mapa. Por ejemplo, la consulta de la posición actual o la configuración del tipo de mapa se hacían directamente sobre el control <span style="font-family: 'courier new', courier;">MapView</span>, mientras que la manipulación de la posición y el zoom se hacían a través del <em>controlador</em> asociado al mapa (<span style="font-family: 'courier new', courier;">MapController</span>). Además, el tratamiento de las coordenadas y las unidades utilizadas eran algo peculiares, teniendo estar continuamente convirtiendo de grados a microgrados y de estos a objetos <span style="font-family: 'courier new', courier;">GeoPoint</span>, etc.</p>
<p>Con la nueva API, todas las operaciones se realizarán directamente sobre un objeto <span style="font-family: 'courier new', courier;">GoogleMap</span>, el componente base de la API. Accederemos a este componente llamando al método <span style="font-family: 'courier new', courier;">getMap()</span> del fragmento <span style="font-family: 'courier new', courier;">MapFragment</span> que contenga nuestro mapa. Podríamos hacerlo de la siguiente forma:</p>
<pre class="brush: java; title: ; notranslate">
import com.google.android.gms.maps.GoogleMap;
...
GoogleMap mapa = ((SupportMapFragment) getSupportFragmentManager()
                          .findFragmentById(R.id.map)).getMap();
</pre>
<p>Una vez obtenida esta referencia a nuestro objeto <span style="font-family: 'courier new', courier;">GoogleMap</span> podremos realizar sobre él la mayoría de las acciones básicas del mapa.</p>
<p>Así, por ejemplo, para modificar el tipo de mapa mostrado podremos utilizar una llamada a su método <span style="font-family: 'courier new', courier;">setMapType()</span>, pasando como parámetro el tipo de mapa:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">MAP_TYPE_NORMAL</span></li>
<li><span style="font-family: 'courier new', courier;">MAP_TYPE_HYBRID</span></li>
<li><span style="font-family: 'courier new', courier;">MAP_TYPE_SATELLITE</span></li>
<li><span style="font-family: 'courier new', courier;">MAP_TYPE_TERRAIN</span></li>
</ul>
<p>Para nuestro ejemplo voy a utilizar una variable que almacene el tipo de mapa actual (del 0 al 3) y habilitaremos una opción de menú para ir alternando entre las distintas opciones. Quedaría de la siguiente forma:</p>
<pre class="brush: java; title: ; notranslate">
private void alternarVista()
{
	vista = (vista + 1) % 4;

	switch(vista)
	{
		case 0:
			mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL);
			break;
		case 1:
			mapa.setMapType(GoogleMap.MAP_TYPE_HYBRID);
			break;
		case 2:
			mapa.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
			break;
		case 3:
			mapa.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
			break;
	}
}
</pre>
<p>En cuanto al movimiento sobre el mapa, con esta nueva versión de la API vamos a tener mucha más libertad que con la anterior versión, ya que podremos mover libremente nuestro punto de vista (o cámara, como lo han llamado los chicos de Android) por un espacio 3D. De esta forma, ya no sólo podremos hablar de latitud-longitud (<em>target</em>) y zoom, sino también de orientación (<em>bearing</em>) y ángulo de visión (<em>tilt</em>). La manipulación de los 2 últimos parámetros unida a posibilidad actual de ver edificios en 3D de muchas ciudades nos abren un mundo de posibilidades.</p>
<p>El movimiento de la cámara se va a realizar siempre mediante la construcción de un objeto <span style="font-family: 'courier new', courier;">CameraUpdate</span> con los parámetros necesarios. Para los movimientos más básicos como la actualización de la latitud y longitud o el nivel de zoom podremos utilizar la clase <span style="font-family: 'courier new', courier;">CameraUpdateFactory</span> y sus métodos estáticos que nos facilitará un poco el trabajo.</p>
<p>Así por ejemplo, para cambiar sólo el nivel de zoom podremos utilizar los siguientes métodos para crear nuestro <span style="font-family: 'courier new', courier;">CameraUpdate</span>:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.zoomIn()</span>. Aumenta en 1 el nivel de zoom.</li>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.zoomOut()</span>. Disminuye en 1 el nivel de zoom.</li>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.zoomTo(nivel_de_zoom)</span>. Establece el nivel de zoom.</li>
</ul>
<p>Por su parte, para actualizar sólo la latitud-longitud de la cámara podremos utilizar:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.newLatLng(lat, long)</span>. Establece la lat-lng expresadas en grados.</li>
</ul>
<p>Si queremos modificar los dos parámetros anteriores de forma conjunta, también tendremos disponible el método siguiente:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.newLatLngZoom(lat, long, zoom)</span>. Establece la lat-lng y el zoom.</li>
</ul>
<p>Para movernos lateralmente por el mapa (<em>panning</em>) podríamos utilizar los métodos de scroll:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">CameraUpdateFactory.scrollBy(scrollHorizontal, scrollVertical)</span>. Scroll expresado en píxeles.</li>
</ul>
<p>Tras construir el objeto <span style="font-family: 'courier new', courier;">CameraUpdate</span> con los parámetros de posición tendremos que llamar a los métodos <span style="font-family: 'courier new', courier;">moveCamera()</span> o <span style="font-family: 'courier new', courier;">animateCamera()</span> de nuestro objeto <span style="font-family: 'courier new', courier;">GoogleMap</span>, dependiendo de si queremos que la actualización de la vista se muestre directamente o de forma animada.</p>
<p>Con esto en cuenta, si quisiéramos por ejemplo centrar la vista en España con un zoom de 5 podríamos hacer lo siguiente:</p>
<pre class="brush: java; title: ; notranslate">
CameraUpdate camUpd1 =
	CameraUpdateFactory.newLatLng(new LatLng(40.41, -3.69));

mapa.moveCamera(camUpd1);
</pre>
<p>Además de los movimientos básicos que hemos comentado, si queremos modificar los demás parámetros de la cámara o varios de ellos simultaneamente tendremos disponible el método más general <span style="font-family: 'courier new', courier;">CameraUpdateFactory.newCameraPosition()</span> que recibe como parámetro un objeto de tipo <span style="font-family: 'courier new', courier;">CameraPosition</span>. Este objeto los construiremos indicando todos los parámetros de la posición de la cámara a través de su método <span style="font-family: 'courier new', courier;">Builder()</span> de la siguiente forma:</p>
<pre class="brush: java; title: ; notranslate">
LatLng madrid = new LatLng(40.417325, -3.683081);
CameraPosition camPos = new CameraPosition.Builder()
	    .target(madrid)   //Centramos el mapa en Madrid
	    .zoom(19)         //Establecemos el zoom en 19
	    .bearing(45)      //Establecemos la orientación con el noreste arriba
	    .tilt(70)         //Bajamos el punto de vista de la cámara 70 grados
	    .build();

CameraUpdate camUpd3 =
	CameraUpdateFactory.newCameraPosition(camPos);

mapa.animateCamera(camUpd3);
</pre>
<p>Como podemos comprobar, mediante este mecanismo podemos modificar todos los parámetros de posición de la cámara (o sólo algunos de ellos) al mismo tiempo. En nuestro caso de ejemplo hemos centrado la vista del mapa sobre el parque de El Retiro de Madrid, con un nivel de zoom de 19, una orientación de 45 grados para que el noreste esté hacia arriba y un ángulo de visión de 70 grados de forma que veamos en 3D el monumento a Alfonso XII en la vista de mapa NORMAL. En la siguiente imagen vemos el resultado:</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-cameraposition.png"><img class="alignnone size-medium wp-image-3276" title="captura-cameraposition" alt="captura-cameraposition" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-cameraposition-180x300.png" width="180" height="300" /></a></p>
<p>Como podéis ver, en esta nueva versión de la API se facilita bastante el posicionamiento dentro del mapa, y el uso de las clases <span style="font-family: 'courier new', courier;">CameraUpdate</span> y <span style="font-family: 'courier new', courier;">CameraPosition</span> resulta bastante intuitivo.</p>
<p>Bien, pues ya hemos hablado de cómo modificar nuestro punto de vista sobre el mapa, pero si el usuario se mueve de forma manual por él, ¿cómo podemos conocer en un momento dado la posición de la cámara?</p>
<p>Pues igual de fácil, mediante el método <span style="font-family: 'courier new', courier;">getCameraPosition()</span>, que nos devuelve un objeto <span style="font-family: 'courier new', courier;">CameraPosition</span> como el que ya conocíamos. Accediendo a los distintos métodos y propiedades de este objeto podemos conocer con exactitud la posición de la cámara, la orientación y el nivel de zoom.</p>
<pre class="brush: java; title: ; notranslate">
CameraPosition camPos = mapa.getCameraPosition();

LatLng coordenadas = camPos.target;
double latitud = coordenadas.latitude;
double longitud = coordenadas.longitude;

float zoom = camPos.zoom;
float orientacion = camPos.bearing;
float angulo = camPos.titl;
</pre>
<p>En nuestra aplicación de ejemplo, que podéis descargar al final del artículo, he añadido una nueva opción de menú que muestra en un mensaje <em>toast</em> la latitud y longitud actual de la vista de mapa.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-posicion.png"><img class="alignnone size-medium wp-image-3277" title="captura-posicion" alt="captura-posicion" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-posicion-180x300.png" width="180" height="300" /></a></p>
<p>Y con esto habríamos terminado de describir las acciones básicas de configuración y movimiento sobre el mapa. En los próximos artículos veremos más opciones, como la forma de añadir marcadores o dibujar sobre el mapa.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="Mapas Android API v2 - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-mapas-api2-p2" target="_blank">curso en GitHub</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3271</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Mapas en Android (Google Maps Android API v2) &#8211; I</title>
		<link>http://www.sgoliver.net/blog/?p=3244&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mapas-en-android-google-maps-android-api-v2-i</link>
		<comments>http://www.sgoliver.net/blog/?p=3244#comments</comments>
		<pubDate>Wed, 05 Dec 2012 20:16:17 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[mapas]]></category>
		<category><![CDATA[MapFragment]]></category>
		<category><![CDATA[maps]]></category>
		<category><![CDATA[v2]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3244</guid>
		<description><![CDATA[Hace tan sólo 3 días, Google presentaba la segunda versión de su API de Google Maps para Android. Esta nueva versión presenta muchas novedades interesantes, de las que cabe destacar las siguientes: Integración con los Servicios de Google Play (Google Play Services) y la Consola de APIs. Utilización a través de un nuevo tipo específico [...]]]></description>
				<content:encoded><![CDATA[<p>Hace tan sólo 3 días, Google presentaba la segunda versión de su <a title="Google Maps Android API v2" href="https://developers.google.com/maps/documentation/android/" target="_blank">API de Google Maps para Android</a>. Esta nueva versión presenta muchas novedades interesantes, de las que cabe destacar las siguientes:</p>
<ul>
<li>Integración con los Servicios de Google Play (<a title="Google Play Services" href="http://developer.android.com/google/play-services/index.html" target="_blank"><em>Google Play Services</em></a>) y la <a title="Google API Console" href="https://code.google.com/apis/console/" target="_blank">Consola de APIs</a>.</li>
<li>Utilización a través de un nuevo tipo específico de fragment <em>(</em><span style="font-family: 'courier new', courier;"><a title="Android MapFragment" href="https://developers.google.com/maps/documentation/android/reference/com/google/android/gms/maps/MapFragment" target="_blank">MapFragment</a></span><em>)</em>, una mejora muy esperada por muchos.</li>
<li>Utilización de <em>mapas vectoriales</em>, lo que repercute en una mayor velocidad de carga y una mayor eficiencia en cuanto a uso de ancho de banda.</li>
<li>Mejoras en el sistema de caché, lo que reducirá en gran medida las famosas áreas en blanco que tardan en cargar.</li>
<li>Los mapas son ahora 3D, es decir, podremos mover nuestro punto de vista de forma que lo veamos en perspectiva.</li>
</ul>
<p>Al margen de las novedades generales, como desarrolladores ¿qué diferencias nos vamos a encontrar con respecto a la API anterior a la hora de desarrollar nuestras aplicaciones Android?</p>
<p>Pues la principal será el componente que utilizaremos para la inclusión de mapas en nuestra aplicación. Si recordamos la anterior versión de la API, para incluir un mapa en la aplicación debíamos utilizar un control de tipo <span style="font-family: 'courier new', courier;">MapView</span>, que además requería que su actividad contenedora fuera del tipo <span style="font-family: 'courier new', courier;">MapActivity</span>. Con la nueva API nos olvidaremos de estos dos componentes y pasaremos a tener sólo uno, un nuevo tipo específico de <em>fragment</em> llamado <span style="font-family: 'courier new', courier;">MapFragment</span>. Esto nos permitirá entre otras cosas añadir uno [o varios, esto también es una novedad] mapas a cualquier actividad, sea del tipo que sea, y contando por supuesto con todas las ventajas del uso de fragments. <strong>Nota importante</strong>: dado que el nuevo control de mapas se basa en fragments, si queremos mantener la compatibilidad con versiones de Android anteriores a la 3.0 tendremos que utilizar la librería de soporte <em>android-support</em>. Más adelante veremos más detalles sobre esto.</p>
<p>Además de esta novedad, la integración de la API con los <em>Google Play Services</em> y la <em>Consola de APIs</em> de Google, harán que los preparativos del entorno, las librerías utilizadas, y el proceso de obtención de la <em>API Key</em> de Google Maps sean un poco distintos a los que ya vimos en los <a title="Mapas en Android: Preparativos" href="http://www.sgoliver.net/blog/?p=1949" target="_blank">artículos dedicados la primera versión</a>.</p>
<p>Pues bien, en este nuevo artículo del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">Curso de Programación Android</a> vamos a describir los pasos necesarios para hacer uso de la nueva versión de la API de mapas de Google (Google Maps Android API v2).</p>
<p>Como en los artículos previos donde aprendimos a utilizar la API v1, en este caso también será necesario realizar algunos preparativos y tareas previas antes de poder empezar a utilizarlos en nuestras aplicaciones.</p>
<p>En primer lugar, dado que la API v2 se proporciona como parte del SDK de <em>Google Play Services</em>, será necesario incorporar previamente a nuestro entorno de desarrollo dicho paquete. Haremos esto accediendo desde Eclipse al Android SDK Manager y descargando del apartado de extras el paquete llamado &#8220;Google Play Services&#8221;.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/download-google-play-services-sdk.png"><img class="alignnone size-medium wp-image-3245" title="download-google-play-services-sdk" alt="download-google-play-services-sdk" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/download-google-play-services-sdk-300x247.png" width="300" height="247" /></a></p>
<p>Tras pulsar el botón de <em>Install</em> y aceptar la licencia correspondiente el paquete quedará instalado en nuestro sistema, concretamente en la ruta: <span style="font-family: 'courier new', courier;">&lt;carpeta-sdk-android&gt;/extras/google/google_play_services/. </span>Recordemos esto porque nos hará falta más adelante.</p>
<p>El siguiente paso será obtener una <em>API Key</em> para poder utilizar el servicio de mapas de Google en nuestra aplicación. Este paso ya lo comentamos en los artículos sobre la API v1, pero en este caso el procedimiento será algo distinto. La nueva API de mapas de Android se ha integrado por fin en la <a title="Google API Console" href="https://code.google.com/apis/console/" target="_blank">Consola de APIs de Google</a> (una herramienta de la que ya hablamos por ejemplo en los artículos dedicados a <em>Google Cloud Messaging</em>), por lo que el primer paso será acceder a ella. Una vez hemos accedido, tendremos que crear un nuevo proyecto desplegando el menú superior izquierdo y seleccionando la opción &#8220;Create&#8230;&#8221;.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/create-api-project-2.png"><img class="alignnone size-full wp-image-3246" title="create-api-project-2" alt="create-api-project-2" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/create-api-project-2.png" width="198" height="270" /></a></p>
<p>Aparecerá entonces una ventana que nos solicitará el nombre del proyecto. Introducimos algún nombre descriptivo y aceptamos sin más.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/create-new-project.png"><img class="alignnone size-medium wp-image-3247" title="create-new-api-project" alt="create-new-api-project" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/create-new-project-300x112.png" width="300" height="112" /></a></p>
<p>Una vez creado el proyecto, accederemos a la opción &#8220;Services&#8221; del menú izquierdo. Desde esta ventana podemos activar o desactivar cada uno de los servicios de Google que queremos utilizar. En este caso sólo activaremos el servicio llamado &#8220;Google Maps Android API v2&#8243; pulsando sobre el botón ON/OFF situado justo a su derecha.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/mpas-android-api-2.png"><img class="alignnone size-medium wp-image-3248" title="mapas-android-api-2" alt="mapas-android-api-2" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/mpas-android-api-2-300x124.png" width="300" height="124" /></a></p>
<p>Una vez activado aparecerá una nueva opción en el menú izquierdo llamada &#8220;API Access&#8221;. Accediendo a dicha opción tendremos la posibilidad de obtener nuestra nueva API Key que nos permita utilizar el servicio de mapas desde nuestra aplicación particular.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-1.png"><img class="alignnone size-medium wp-image-3249" title="api-access-1" alt="api-access-1" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-1-300x210.png" width="300" height="210" /></a></p>
<p>Para ello, pulsaremos el botón &#8220;Create new Android key&#8230;&#8221;. Esto nos llevará a un cuadro de diálogo donde tendremos que introducir algunos datos identificativos de nuestra aplicación. En concreto necesitaremos dos: la huella digital (SHA1) del certificado con el que firmamos la aplicación, y el paquete java utilizado. El segundo no tiene misterio, pero el primero requiere alguna explicación. Toda aplicación Android debe ir firmada para poder ejecutarse en un dispositivo, tanto físico como emulado. Este proceso de firma es uno de los pasos que tenemos que hacer siempre antes de distribuir públicamente una aplicación.  Adicionalmentes, durante el desarrollo de la misma, para realizar pruebas y la depuración del código, aunque no seamos conscientes de ello también estamos firmado la aplicación con un &#8220;certificado de pruebas&#8221;. Podemos saber en qué carpeta de nuestro sistema está almacenado este certificado accediendo desde Eclipse al menú Window /Preferences y accediendo a la sección Android / Build.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/debug-keystore.png"><img class="alignnone size-medium wp-image-3250" title="debug-keystore" alt="debug-keystore" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/debug-keystore-300x215.png" width="300" height="215" /></a></p>
<p>Como se puede observar, en mi caso el certificado de pruebas está en la ruta &#8220;C:\Users\Salvador\.android\debug.keystore&#8221;. Pues bien, para obtener nuestra huella digital SHA1 deberemos acceder a dicha ruta desde la consola de comando de Windows y ejecutar los siguientes comandos:</p>
<pre class="brush: plain; title: ; notranslate">
C:\&gt;cd C:\Users\Salvador\.android\

C:\Users\Salvador\.android&gt;&quot;C:\Program Files\Java\jdk1.7.0_07\bin\keytool.exe&quot; -list -v -keystore debug.keystore -alias androiddebugkey -storepass android -keypass android
</pre>
<p>Suponiendo que tu instalación de Java está en la ruta &#8220;<span style="font-family: 'courier new', courier;">C:\Program Files\Java\jdk1.7.0_07</span>&#8220;. Si no es así sólo debes sustituir ésta por la correcta. Esto nos deberá devolver varios datos del certificado, entre ellos la huella SHA1.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/sha1.png"><img class="alignnone size-medium wp-image-3251" title="sha1" alt="sha1" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/sha1-300x151.png" width="300" height="151" /></a></p>
<p>Pues bien, nos copiamos este dato y lo añadimos a la ventana de obtención de la API Key donde nos habíamos quedado antes, y a continuación <span style="text-decoration: underline;">separado por un punto y coma</span> añadimos el paquete java que vayamos a utilizar en nuestra aplicación, que en mi caso será <span style="font-family: 'courier new', courier;">&#8220;net.sgoliver.android.mapasapi2&#8243;</span>.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-2.png"><img class="alignnone size-medium wp-image-3252" title="api-access-2" alt="api-access-2" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-2-300x213.png" width="300" height="213" /></a></p>
<p>Pulsamos el botón &#8220;Create&#8221; y ya deberíamos tener nuestra API Key generada, podremos verla en la pantalla siguiente dentro del apartado &#8220;Key for Android Apps (with certificates)&#8221;. Apuntaremos también este dato para utilizarlo más tarde.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-3.png"><img class="alignnone size-medium wp-image-3253" title="api-access-3" alt="api-access-3" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/api-access-3-300x103.png" width="300" height="103" /></a></p>
<p>Con esto ya habríamos concluido los preparativos iniciales necesarios para utilizar el servicio de mapas de Android en nuestras propias aplicaciones, por lo que empecemos a crear un proyecto de ejemplo en Eclipse.</p>
<p>Abriremos Eclipse y crearemos un nuevo proyecto estandar de Android, en mi caso lo he llamado &#8220;android-mapas-api2&#8243;. Recordemos utilizar para el proyecto el mismo paquete java que hemos indicado durante la obtención de la API key.</p>
<p>Tras esto lo primero que haremos será añadir al fichero AndroidManifest.xml la API Key que acabamos de generar. Para ello añadiremos al fichero, dentro de la etiqueta <span style="font-family: 'courier new', courier;">&lt;application&gt;</span>, un nuevo elemento <span style="font-family: 'courier new', courier;">&lt;meta-data&gt;</span> con los siguientes datos:</p>
<pre class="brush: xml; title: ; notranslate">
...
&lt;application&gt;
...
    &lt;meta-data android:name=&quot;com.google.android.maps.v2.API_KEY&quot;
               android:value=&quot;api_key&quot;/&gt;
...
&lt;/application&gt;
</pre>
<p>Como valor del parámetro <span style="font-family: 'courier new', courier;">android:value</span> tendremos que poner nuestra API Key recien generada.</p>
<p>Siguiendo con el AndroidManifest, también tendremos que incluir una serie de permisos que nos permitan hacer uso de los mapas. En primer lugar tendremos que definir y utilizar un permiso llamado &#8220;<span style="color: #ff0000;">tu.paquete.java</span>.permission.MAPS_RECEIVE&#8221;, en mi caso quedaría de la siguiente forma:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;permission
      android:name=&quot;net.sgoliver.android.mapasapi2.permission.MAPS_RECEIVE&quot;
      android:protectionLevel=&quot;signature&quot;/&gt;

&lt;uses-permission android:name=&quot;net.sgoliver.android.mapasapi2.permission.MAPS_RECEIVE&quot;/&gt;
</pre>
<p>Además, tendremos que añadir permisos adicionales que nos permitan acceder a los servicios web de Google, a Internet, y al almacenamiento externo del dispositivo (utilizado para la caché de los mapas):</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot;/&gt;
&lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;/&gt;
&lt;uses-permission android:name=&quot;com.google.android.providers.gsf.permission.READ_GSERVICES&quot;/&gt;
</pre>
<p>Por último, dado que la API v2 de Google Maps Android utiliza OpenGL ES versión 2, deberemos especificar también dicho requisito en nuestro AndroidManifest añadiendo un nuevo elemento <span style="font-family: 'courier new', courier;">&lt;uses-feature&gt;</span>:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-feature android:glEsVersion=&quot;0x00020000&quot;
              android:required=&quot;true&quot;/&gt;
</pre>
<p>Una vez hemos configurado todo lo necesario en el AndroidManifest, y antes de escribir nuestro código, tenemos que seguir añadiendo elementos externos a nuestro proyecto. El primero de ellos será referenciar desde nuestro proyecto la librería con el SDK de Google Play Services que nos descargamos al principio de este tutorial. Para ello, desde Eclipse podemos importar la librería a nuestro conjunto de proyectos mediante la opción de menú &#8220;File / Import&#8230; / Existing Android Code Into Workspace&#8221;. Como ya dijimos este paquete se localiza en la ruta &#8220;<span style="font-family: 'courier new', courier;">&lt;carpeta-sdk-android&gt;/extras/google/google_play_services/libproject/google-play-services_lib</span>&#8220;.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/import-library.png"><img class="alignnone size-medium wp-image-3256" title="import-library" alt="import-library" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/import-library-286x300.png" width="286" height="300" /></a></p>
<p>Tras seleccionar la ruta correcta dejaremos el resto de opciones con sus valores por defecto y pulsaremos Finish para que Eclipse importe esta librería a nuestro conjunto de proyectos.</p>
<p>El siguiente paso será referenciar esta librería desde nuestro proyecto de ejemplo. Para ello iremos a sus propiedades pulsando botón derecho / Properties sobre nuestro proyecto y accediendo a la sección Android de las preferencias. En dicha ventana podemos añadir una nueva librería en la sección inferior llamada Library. Cuando pulsamos el botón &#8220;Add&#8230;&#8221; nos aparecerá la librería recien importada y podremos seleccionarla directamente, añadiéndose a nuestra lista de librerías referenciadas por nuestro proyecto.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/reference-library.png"><img class="alignnone size-medium wp-image-3257" title="reference-library" alt="reference-library" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/reference-library-300x253.png" width="300" height="253" /></a></p>
<p>Como último paso de configuración de nuestro proyecto, si queremos que nuestra aplicación se pueda ejecutar desde versiones &#8220;antiguas&#8221; de Android (concretamente desde la versión de Android 2.2) deberemos asegurarnos de que nuestro proyecto incluye la librería <span style="font-family: 'courier new', courier;">android-support-v4.jar</span>, que debería aparecer si desplegamos las sección &#8220;Android Dependencies&#8221; o la carpeta &#8220;lib&#8221; de nuestro proyecto.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/support-library.png"><img class="alignnone  wp-image-3258" title="support-library" alt="support-library" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/support-library-234x300.png" width="187" height="240" /></a></p>
<p>Las versiones más recientes de ADT incluyen por defecto esta librería en nuestros proyectos, pero si no está incluida podéis hacerlo mediante la opción del menú contextual &#8220;Android Tools / Add Support Library&#8230;&#8221;  sobre el proyecto, o bien de forma manual.</p>
<p>Y con esto hemos terminado de configurar todo lo necesario. Ya podemos escribir nuestro código. Y para este primer artículo sobre el tema nos vamos a limitar a mostrar un mapa en la pantalla principal de la aplicación. En artículos posteriores veremos como añadir otras opciones o elementos al mapa.</p>
<p>Para esto tendremos simplemente que añadir el control correspondiente al layout de nuestra actividad principal. En el caso de la nueva API v2 este &#8220;control&#8221; se añadirá en forma de <em>fragment</em> (de ahí que hayamos tenido que incluir la librería <em>android-support</em> para poder utilizarlos en versiones de Android anteriores a la 3.0) de un determinado tipo (concretamente de la nueva clase <span style="font-family: 'courier new', courier;">com.google.android.gms.maps.SupportMapFragment</span>), quedando por ejemplo de la siguiente forma:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;fragment xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
        android:id=&quot;@+id/map&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        class=&quot;com.google.android.gms.maps.SupportMapFragment&quot;/&gt;
</pre>
<p>Por supuesto, dado que estamos utilizando fragments, la actividad principal también tendrá que extender a <span style="font-family: 'courier new', courier;">FragmentActivity</span> (en vez de simplemente <span style="font-family: 'courier new', courier;">Activity</span> como es lo &#8220;normal&#8221;). Usaremos también la versión de <span style="font-family: 'courier new', courier;">FragmentActivity</span> incluida en la librería <span style="font-family: 'courier new', courier;">android-support</span> para ser compatibles con la mayoría de las versiones Android actuales.</p>
<pre class="brush: java; title: ; notranslate">
public class MainActivity extends android.support.v4.app.FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
    }

...
}
</pre>
<p>Con esto, ya podríamos ejecutar y probar nuestra aplicación. En mi caso las pruebas las he realizado sobre un dispositivo físico con Android 2.2 ya que por el momento parece haber algunos problemas para hacerlo sobre el emulador. Por tanto tendréis que conectar vuestro dispositivo  al PC mediante el cable de datos e indicar a Eclipse que lo utilice para la ejecución de la aplicación.</p>
<p>Si ejecutamos el ejemplo deberíamos ver un mapa en la pantalla principal de la aplicación, sobre el que nos podremos mover y hacer zoom con los gestos habituales o utilizando los controles de zoom incluidos por defecto sobre el mapa.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-ejemplo.png"><img class="alignnone size-medium wp-image-3260" title="captura-ejemplo" alt="captura-ejemplo" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/12/captura-ejemplo-180x300.png" width="180" height="300" /></a></p>
<p>Con este artículo espero haber descrito todos los pasos necesarios para comenzar a utilizar los servicios de mapas de Google utilizando su nueva API Google Maps Android v2. Si tenéis cualquier duda o propuesta de mejora no dudéis en escribirlo en los comentarios.</p>
<p>Como habéis podido comprobar hay muchos preparativos que hacer, aunque ninguno de ellos de excesiva dificultad. En los próximos artículos aprenderemos a utilizar más características de la nueva API.</p>
<p>Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del <a title="Mapas Android API v2 - Android en GitHub" href="https://github.com/sgolivernet/curso-android-src/tree/master/android-mapas-api2" target="_blank">curso en GitHub</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3244</wfw:commentRss>
		<slash:comments>100</slash:comments>
		</item>
		<item>
		<title>Tareas en segundo plano en Android (II): IntentService</title>
		<link>http://www.sgoliver.net/blog/?p=3129&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=tareas-en-segundo-plano-en-android-ii-intentservice</link>
		<comments>http://www.sgoliver.net/blog/?p=3129#comments</comments>
		<pubDate>Sun, 05 Aug 2012 17:54:17 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[BroadcastReceiver]]></category>
		<category><![CDATA[IntentService]]></category>
		<category><![CDATA[segundo plano]]></category>
		<category><![CDATA[tarea]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3129</guid>
		<description><![CDATA[En el artículo anterior del Curso de Programación Android vimos cómo ejecutar tareas en segundo plano haciendo uso de hilos (Thread) y tareas asíncronas (AsyncTask). En este nuevo artículo nos vamos a centrar en una alternativa menos conocida, aunque tanto o más interesante, para conseguir el mismo objetivo: ejecutar una determinada tarea en un hilo independiente [...]]]></description>
				<content:encoded><![CDATA[<p>En el <a title="Tareas en segundo plano en Android (I): Thread y AsyncTask" href="http://www.sgoliver.net/blog/?p=3099">artículo anterior</a> del <a title="Curso de Programación Android" href="http://www.sgoliver.net/blog/?page_id=2935">Curso de Programación Android</a> vimos cómo ejecutar tareas en segundo plano haciendo uso de hilos (<span style="font-family: 'courier new', courier;">Thread</span>) y tareas asíncronas (<span style="font-family: 'courier new', courier;">AsyncTask</span>). En este nuevo artículo nos vamos a centrar en una alternativa menos conocida, aunque tanto o más interesante, para conseguir el mismo objetivo: ejecutar una determinada tarea en un hilo independiente al hilo principal de la aplicación. Esta opción se llama <span style="font-family: 'courier new', courier;">IntentService</span>, y no es más que un tipo particular de servicio Android que se preocupará por nosotros de la creación y gestión del nuevo hilo de ejecución y de detenerse a sí mismo una vez concluida su tarea asociada.</p>
<p>Como en el caso de las <span style="font-family: 'courier new', courier;">AsyncTask</span>, la utilización de un <span style="font-family: 'courier new', courier;">IntentService</span> va a ser tan sencilla como extender una nueva clase de <span style="font-family: 'courier new', courier;">IntentService</span> e implementar su método <span style="font-family: 'courier new', courier;">onHandleIntent()</span>. Este método recibe como parámetro un <span style="font-family: 'courier new', courier;">Intent</span>, que podremos utilizar para pasar al servicio los datos de entrada necesarios.</p>
<p>A diferencia de las <span style="font-family: 'courier new', courier;">AsyncTask</span>, un <span style="font-family: 'courier new', courier;">IntentService</span> no proporciona métodos que se ejecuten en el hilo principal de la aplicación y que podamos aprovechar para &#8220;comunicarnos&#8221; con nuestra interfaz durante la ejecución. Éste es el motivo principal de que los <span style="font-family: 'courier new', courier;">IntentService</span> sean una opción menos utilizada a la hora de ejecutar tareas que requieran cierta vinculación con la interfaz de la aplicación. Sin embargo tampoco es imposible su uso en este tipo de escenarios ya que podremos utilizar por ejemplo mensajes <em>broadcast</em> (y por supuesto su <span style="font-family: 'courier new', courier;">BroadcastReceiver</span> asociado capaz de procesar los mensajes) para comunicar eventos al hilo principal, como por ejemplo la necesidad de actualizar controles de la interfaz o simplemente para comunicar la finalización de la tarea ejecutada. En este artículo veremos cómo implementar este método para conseguir una aplicación de ejemplo similar a la que construimos en el artículo anterior utilizando <span style="font-family: 'courier new', courier;">AsyncTask</span>.</p>
<p>Empezaremos  extendiendo una nueva clase derivada de <span style="font-family: 'courier new', courier;">IntentService</span>, que para ser originales llamaremos <span style="font-family: 'courier new', courier;">MiIntentService</span>. Lo primero que tendremos que hacer será implementar un constructor por defecto. Este constructor lo único que hará será llamar al constructor de la clase padre pasándole el nombre de nuestra nueva clase.</p>
<p>A continuación implementaremos el método <span style="font-family: 'courier new', courier;">onHandleIntent()</span>. Como ya hemos indicado, este método será el que contenga el código de la tarea a ejecutar en segundo plano. Para simular una tarea de larga duración utilizaremos el mismo bucle que ya vimos en el artículo anterior con la novedad de que esta vez el número de iteraciones será variable, de forma que podamos experimentar con cómo pasar datos de entrada a través del <span style="font-family: 'courier new', courier;">Intent</span> recibido como parámetro en <span style="font-family: 'courier new', courier;">onHandleIntent()</span>. En nuestro caso de ejemplo pasaremos un sólo dato de entrada que indique el número de iteraciones. Por tanto, lo primero que haremos será obtener este dato a partir del Intent mediante el método <span style="font-family: 'courier new', courier;">getIntExtra()</span>. Una vez conocemos el número de iteraciones, tan sólo nos queda ejecutar el bucle y comunicar el progreso tras cada iteración.</p>
<p>Como ya hemos comentado, para comunicar este progreso vamos a hacer uso de mensajes <em>broadcast</em>. Para enviar este tipo de mensajes necesitamos construir un <span style="font-family: 'courier new', courier;">Intent</span>, asociarle un nombre de acción determinada que lo identifique mediante <span style="font-family: 'courier new', courier;">setAction()</span>, e incluir los datos que necesitemos comunicar añadiendo tantos <em>extras</em> como sean necesarios mediante el método <span style="font-family: 'courier new', courier;">putExtra()</span>. Los nombres de las acciones se suelen preceder con el paquete java de nuestra aplicación de forma que nos aseguremos que es un identificador único. En nuestro caso lo llamaremos &#8220;<span style="font-family: 'courier new', courier;">net.sgoliver.intent.action.PROGRESO</span>&#8221; y lo definiremos como atributo estático de la clase para mayor comodidad, llamado <span style="font-family: 'courier new', courier;">ACTION_PROGRESO</span>. Por su parte, los datos a comunicar en nuestro ejemplo será solo el nivel de progreso, por lo que sólo añadiremos un extra a nuestro intent con dicho dato. Por último enviaremos el mensaje llamando al método <span style="font-family: 'courier new', courier;">sendBroadcast()</span> pasándole como parámetro el intent recién creado. Veamos cómo quedaría el código completo.</p>
<pre class="brush: java; title: ; notranslate">
public class MiIntentService extends IntentService {

	public static final String ACTION_PROGRESO =
		&quot;net.sgoliver.intent.action.PROGRESO&quot;;
	public static final String ACTION_FIN =
		&quot;net.sgoliver.intent.action.FIN&quot;;

	public MiIntentService() {
	        super(&quot;MiIntentService&quot;);
    	}

	@Override
	protected void onHandleIntent(Intent intent)
	{
		int iter = intent.getIntExtra(&quot;iteraciones&quot;, 0);

		for(int i=1; i&lt;=iter; i++) {
			tareaLarga();

			//Comunicamos el progreso
			Intent bcIntent = new Intent();
			bcIntent.setAction(ACTION_PROGRESO);
			bcIntent.putExtra(&quot;progreso&quot;, i*10);
			sendBroadcast(bcIntent);
		}

		Intent bcIntent = new Intent();
		bcIntent.setAction(ACTION_FIN);
		sendBroadcast(bcIntent);
	}

	private void tareaLarga()
    	{
    		try {
    			Thread.sleep(1000);
    		} catch(InterruptedException e) {}
    	}
}
</pre>
<p>Como podéis comprobar también he añadido un nuevo tipo de mensaje broadcast (<span style="font-family: 'courier new', courier;">ACTION_FIN</span>), esta vez sin datos adicionales, para comunicar a la aplicación principal la finalización de la tarea en segundo plano.</p>
<p>Además de la implementación del servicio, recordemos que también tendremos que declararlo en el <em>AndroidManifest.xml</em>, dentro de la sección <span style="font-family: 'courier new', courier;">&lt;application&gt;</span>:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;service android:name=&quot;.MiIntentService&quot;&gt;&lt;/service&gt;
</pre>
<p>Y con esto ya tendríamos implementado nuestro servicio. El siguiente paso será llamar al servicio para comenzar su ejecución. Esto lo haremos desde una actividad principal de ejemplo en la que tan sólo colocaremos una barra de progreso y un botón para lanzar el servicio. El código del botón para ejecutar el servicio será muy sencillo, tan sólo tendremos que crear un nuevo intent asociado a la clase <span style="font-family: 'courier new', courier;">MiIntentService</span>, añadir los datos de entrada necesarios mediante <span style="font-family: 'courier new', courier;">putExtra()</span> y ejecutar el servicio llamando a <span style="font-family: 'courier new', courier;">startService()</span> pasando como parámetro el intent de entrada. Como ya dijimos, el único dato de entrada que pasaremos será el número de iteraciones a ejecutar.</p>
<pre class="brush: java; title: ; notranslate">
btnEjecutar.setOnClickListener(new OnClickListener() {

	@Override
	public void onClick(View v) {
		Intent msgIntent = new Intent(MainActivity.this, MiIntentService.class);
		msgIntent.putExtra(&quot;iteraciones&quot;, 10);
		startService(msgIntent);
	}
});
</pre>
<p>Con esto ya podríamos ejecutar nuestra aplicación y lanzar la tarea, pero no podríamos ver el progreso de ésta ni saber cuándo ha terminado porque aún no hemos creado el <span style="font-family: 'courier new', courier;">BroadcastReceiver</span> necesario para capturar los mensajes broadcast que envía el servicio durante su ejecución.</p>
<p>Para ello, como clase interna a nuestra actividad principal definiremos una nueva clase que extienda a <span style="font-family: 'courier new', courier;">BroadcastReceiver</span> y que implemente su método <span style="font-family: 'courier new', courier;">onReceive()</span> para gestionar los mensajes <span style="font-family: 'courier new', courier;">ACTION_PROGRESO</span> y <span style="font-family: 'courier new', courier;">ACTION_FIN</span> que definimos en nuestro <span style="font-family: 'courier new', courier;">IntentService</span>. En el caso de recibirse <span style="font-family: 'courier new', courier;">ACTION_PROGRESO</span> extraeremos el nivel de progreso del intent recibido y actualizaremos consecuentemente la barra de progreso mediante <span style="font-family: 'courier new', courier;">setProgress()</span>. En caso de recibirse <span style="font-family: 'courier new', courier;">ACTION_FIN</span> mostraremos un mensaje Toast informando de la finalización de la tarea.</p>
<pre class="brush: java; title: ; notranslate">
public class ProgressReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		if(intent.getAction().equals(MiIntentService.ACTION_PROGRESO)) {
			int prog = intent.getIntExtra(&quot;progreso&quot;, 0);
			pbarProgreso.setProgress(prog);
		}
		else if(intent.getAction().equals(MiIntentService.ACTION_FIN)) {
			Toast.makeText(MainActivity.this, &quot;Tarea finalizada!&quot;, Toast.LENGTH_SHORT).show();
		}
	}
}
</pre>
<p>Pero aún no habríamos terminado dado, ya que aunque hayamos implementado nuestro <span style="font-family: 'courier new', courier;">BroadcastReceiver</span>, éste no tendrá ningún efecto a menos que lo registremos con la aplicación y lo asociemos a los tipos de mensaje que deberá tratar (mediante un <span style="font-family: 'courier new', courier;">IntentFilter</span>). Para hacer esto, al final del método <span style="font-family: 'courier new', courier;">onCreate()</span> de nuestra actividad principal crearemos un <span style="font-family: 'courier new', courier;">IntentFilter</span> al que asociaremos mediante <span style="font-family: 'courier new', courier;">addAction()</span> los dos tipos de mensaje broadcast que queremos capturar, instanciaremos nuestro <span style="font-family: 'courier new', courier;">BroadcastReceiver</span> y lo registraremos mediante <span style="font-family: 'courier new', courier;">registerReceiver()</span>, al que pasaremos la instancia creada y el filtro de mensajes.</p>
<pre class="brush: java; title: ; notranslate">
IntentFilter filter = new IntentFilter();
filter.addAction(MiIntentService.ACTION_PROGRESO);
filter.addAction(MiIntentService.ACTION_FIN);
ProgressReceiver rcv = new ProgressReceiver();
registerReceiver(rcv, filter);
</pre>
<p>Y con esto sí habríamos concluido nuestra aplicación de ejemplo. Si ejecutamos la aplicación en el emulador y pulsamos el botón de comenzar la tarea veremos cómo la barra de progreso comienza a avanzar hasta el final, momento en el que deberá aparecer el mensaje toast indicando la finalización de la tarea.</p>
<div id="attachment_3131" class="wp-caption alignnone" style="width: 349px"><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/08/android_intentservice.png"><img class="size-full wp-image-3131" title="android_intentservice" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/08/android_intentservice.png" alt="android_intentservice" width="339" height="240" /></a><p class="wp-caption-text">Android IntentService</p></div>
<p>Como siempre, podéis descargar el <a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/08/android-intentservice.zip">código completo</a> de la aplicación de ejemplo construida en este artículo.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3129</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Tareas en segundo plano en Android (I): Thread y AsyncTask</title>
		<link>http://www.sgoliver.net/blog/?p=3099&#038;utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=tareas-en-segundo-plano-en-android-i-thread-y-asynctask</link>
		<comments>http://www.sgoliver.net/blog/?p=3099#comments</comments>
		<pubDate>Sun, 29 Jul 2012 09:47:36 +0000</pubDate>
		<dc:creator>sgoliver</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[asynctask]]></category>
		<category><![CDATA[backgroud]]></category>
		<category><![CDATA[hilo]]></category>
		<category><![CDATA[segundo plano]]></category>
		<category><![CDATA[tarea]]></category>
		<category><![CDATA[thread]]></category>

		<guid isPermaLink="false">http://www.sgoliver.net/blog/?p=3099</guid>
		<description><![CDATA[Todos los componentes de una aplicación Android, tanto las actividades, los servicios [sí, también los servicios], o los broadcast receivers se ejecutan en el mismo hilo de ejecución, el llamado hilo principal, main thread o GUI thread, que como éste último nombre indica también es el hilo donde se ejecutan todas las operaciones que gestionan la [...]]]></description>
				<content:encoded><![CDATA[<p>Todos los componentes de una aplicación Android, tanto las actividades, los servicios [sí, también los servicios], o los <em>broadcast receivers</em> se ejecutan en el mismo hilo de ejecución, el llamado <em>hilo principal</em>, <em>main thread</em> o <em>GUI thread</em>, que como éste último nombre indica también es el hilo donde se ejecutan todas las operaciones que gestionan la interfaz de usuario de la aplicación. Es por ello, que cualquier operación larga o costosa que realicemos en este hilo va a bloquear la ejecución del resto de componentes de la aplicación y por supuesto también la interfaz, produciendo al usuario un efecto evidente de lentitud, bloqueo, o mal funcionamiento en general, algo que deberíamos evitar a toda costa. Incluso puede ser peor, dado que Android monitoriza las operaciones realizadas en el hilo principal y detecta aquellas que superen los 5 segundos, en cuyo caso se muestra el famoso mensaje de &#8220;<em>Application Not Responding</em>&#8221; (ANR) y el usuario debe decidir entre forzar el cierre de la aplicación o esperar a que termine.</p>
<p><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/error-anr.png"><img class="alignnone  wp-image-3102" title="Application Not Responding (ANR)" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/error-anr.png" alt="Application Not Responding (ANR)" width="213" height="146" /></a></p>
<p>Obviamente, éstos son el tipo de errores que nadie quiere ver al utilizar su aplicación, y en este artículo y los siguientes vamos a ver varias formas de evitarlo utilizando procesos en segundo plano para ejecutar las operaciones de larga duración. En este primer artículo de la serie nos vamos a centrar en dos de las alternativas más directas a la hora de ejecutar tareas en segundo plano en Android:</p>
<ol>
<li>Crear nosotros mismos de forma explícita un nuevo hilo para ejecutar nuestra tarea.</li>
<li>Utilizar la clase auxiliar <span style="font-family: 'courier new', courier;">AsyncTask</span> proporcionada por Android.</li>
</ol>
<p>Mi idea inicial para este artículo era obviar la primera opción, ya que normalmente la segunda solución nos es más que suficiente, y además es mas sencilla y más limpia de implementar. Sin embargo, si no comentamos al menos de pasada la forma de crear &#8220;a mano&#8221; nuevos hilos y los problemas que surgen, quizá no se viera demasiado claro las ventajas que tiene utilizar las <span style="font-family: 'courier new', courier;">AsyncTask</span>. Por tanto, finalmente voy a pasar muy rápidamente por la primera opción para después centrarnos un poco más en la segunda. Además, aprovechando el tema de la ejecución de tareas en segundo plano, vamos a ver también cómo utilizar un control (el <span style="font-family: 'courier new', courier;">ProgressBar</span>) y un tipo de diálogo (el <span style="font-family: 'courier new', courier;">ProgressDialog</span>) que no vimos en los primeros temas del curso dedicados a la interfaz de usuario.</p>
<p>Y para ir paso a paso, vamso a empezar por crear una aplicación de ejemplo en cuya actividad principal colocaremos un control <span style="font-family: 'courier new', courier;">ProgressBar</span> (en mi caso llamado <span style="font-family: 'courier new', courier;">pbarProgreso</span>) y un botón (<span style="font-family: 'courier new', courier;">btnSinHilos</span>) que ejecute una tarea de larga duración. Para simular una operación de larga duración vamos a ayudarnos de un método auxiliar que lo único que haga sea esperar 1 segundo, mediante una llamada a <span style="font-family: 'courier new', courier;">Thread.sleep()</span>.</p>
<pre class="brush: java; title: ; notranslate">
private void tareaLarga()
{
    try {
        Thread.sleep(1000);
    } catch(InterruptedException e) {}
}
</pre>
<p>Haremos que nuestro botón ejecute este método 10 veces, de forma que nos quedará una ejecución de unos 10 segundos en total:</p>
<pre class="brush: java; title: ; notranslate">
btnSinHilos.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		pbarProgreso.setMax(100);
		pbarProgreso.setProgress(0);

		for(int i=1; i&lt;=10; i++) {
			tareaLarga();
			pbarProgreso.incrementProgressBy(10);
		}

		Toast.makeText(MainHilos.this, &quot;Tarea finalizada!&quot;,
				Toast.LENGTH_SHORT).show();
	}
});
</pre>
<p>Como veis, aquí todavía no estamos utilizando nada especial, por lo que todo el código se ejecutará en el hilo principal de la aplicación. En cuanto a la utilización del control <span style="font-family: 'courier new', courier;">ProgressBar</span> vemos que es muy sencilla y no requiere apenas configuración. En nuestro caso tan sólo hemos establecido el valor máximo que alcanzará (el valor en el que la barra de progreso estará rellena al máximo) mediante el método <span style="font-family: 'courier new', courier;">setMax(100)</span>, posteriormente la hemos inicializado al valor mínimo mediante una llamada a <span style="font-family: 'courier new', courier;">setProgress(0)</span> de forma que inicialmente aparezca completamente vacía, y por último en cada iteración del bucle incrementamos su valor en 10 unidades llamando a <span style="font-family: 'courier new', courier;">incrementProgressBy(10)</span>, de tal forma que tras la décima iteración la barra llegue al valor máximo de 100 que establecimos antes. Finalmente mostramos un mensaje <span style="font-family: 'courier new', courier;">Toast</span> para informar de la finalización de la tarea. Pues bien, ejecutemos la aplicación, pulsemos el botón, y veamos qué ocurre.</p>
<div class="woo-sc-box normal  rounded full" style="padding-left:15px;background-image:none;">He colocado un pequeño vídeo al final del artículo donde puede verse el resultado final de todas las pruebas que haremos durante este tutorial. En concreto esta primera prueba puede verse entre los segundos 00:00 &#8211; 00:12</div>
<p>No era eso lo que esperábamos ¿verdad? Lo que ha ocurrido es que desde el momento que hemos pulsado el botón para ejecutar la tarea, hemos bloqueado completamente el resto de la aplicación, incluida la actualización de la interfaz de usuario, que ha debido esperar a que ésta termine mostrando directamente la barra de progreso completamente llena. En definitiva, no hemos sido capaces de ver el progreso de la tarea. Pero como decíamos, este efecto puede empeorar. Probemos ahora a pulsar el botón de la tarea y mientras ésta se ejecuta realicemos cualquier acción sobre la pantalla, un simple click sobre el fondo nos basta. Veamos qué ocurre ahora.</p>
<div class="woo-sc-box normal  rounded full" style="padding-left:15px;background-image:none;">Puedes verlo en el vídeo entre los segundos 00:13 &#8211; 00:28</div>
<p>Vemos cómo al intentar hacer cualquier acción sobre la aplicación Android nos ha advertido con un mensaje de error que la aplicación no responde debido a que se está ejecutando una operación de larga duración en el hilo principal. El usuario debe elegir entre esperar a que termine de ejecutarla o forzar el cierre de la aplicación. Pues bien, estos son los efectos que vamos a intentar evitar. La opción más inmediata que nos proporciona Android, al igual que otras plataformas, es crear directamente hilos secundarios dentro de los cuales ejecutar nuestras operaciones costosas. Esto lo conseguimos en Android instanciando un objeto de la clase <span style="font-family: 'courier new', courier;">Thread</span>. El constructor de la clase <span style="font-family: 'courier new', courier;">Thread</span> recibe como parámetro un nuevo objeto <span style="font-family: 'courier new', courier;">Runnable</span> que debemos construir implementando su método <span style="font-family: 'courier new', courier;">run()</span>, dentro del cual vamos a realizar nuestra tarea de larga duración. Hecho esto, debemos llamar al método <span style="font-family: 'courier new', courier;">start()</span> del objeto <span style="font-family: 'courier new', courier;">Thread</span>definido para comenzar la ejecución de la tarea en segundo plano.</p>
<pre class="brush: java; title: ; notranslate">
new Thread(new Runnable() {
    public void run() {
        //Aquí ejecutamos nuestras tareas costosas
    }
}).start();
</pre>
<p>Hasta aquí todo sencillo y relativamente limpio. Los problemas aparecen cuando nos damos cuenta que desde este hilo secundario que hemos creado no podemos hacer referencia directa a componentes que se ejecuten en el hilo principal, entre ellos los controles que forman nuestra interfaz de usuario, es decir, que desde el método <span style="font-family: 'courier new', courier;">run()</span> no podríamos ir actualizando directamente nuestra barra de progreso de la misma forma que lo hacíamos antes. Para solucionar esto, Android proporciona varias alternativas, entre ellas la utilización del método <span style="font-family: 'courier new', courier;">post()</span> para actuar sobre cada control de la interfaz, o la llamada al método <span style="font-family: 'courier new', courier;">runOnUiThread()</span> para &#8220;enviar&#8221; operaciones al hilo principal desde el hilo secundario [Nota: Sí, vale, sé que no he nombrado la opción de los <a title="Android Handler" href="http://developer.android.com/reference/android/os/Handler.html" target="_blank">Handler</a>, pero no quería complicar más el tema por el momento]. Ambas opciones requieren como parámetro un nuevo objeto <span style="font-family: 'courier new', courier;">Runnable</span> del que nuevamente habrá que implementar su método <span style="font-family: 'courier new', courier;">run()</span> donde se actúe sobre los elementos de la interfaz. Por ver algún ejemplo, en nuestro caso hemos utilizado el método <span style="font-family: 'courier new', courier;">post()</span> para actuar sobre el control <span style="font-family: 'courier new', courier;">ProgressBar</span>, y el método <span style="font-family: 'courier new', courier;">runOnUiThread()</span>para mostrar el mensaje toast.</p>
<pre class="brush: java; title: ; notranslate">
new Thread(new Runnable() {
    public void run() {
    	pbarProgreso.post(new Runnable() {
		public void run() {
			pbarProgreso.setProgress(0);
		}
	});

	for(int i=1; i&lt;=10; i++) {
		tareaLarga();
		pbarProgreso.post(new Runnable() {
	   		public void run() {
	   			pbarProgreso.incrementProgressBy(10);
	    		}
	    	});
	}

	runOnUiThread(new Runnable() {
	    public void run() {
	        Toast.makeText(MainHilos.this, &quot;Tarea finalizada!&quot;,
				Toast.LENGTH_SHORT).show();
	    }
	});
    }
}).start();
</pre>
<p>Utilicemos este código dentro de un nuevo botón de nuestra aplicación de ejemplo y vamos a probarlo en el emulador.</p>
<div class="woo-sc-box normal  rounded full" style="padding-left:15px;background-image:none;">Puedes verlo en el vídeo entre los segundos 00:29 &#8211; 00:43</div>
<p>Ahora sí podemos ver el progreso de nuestra tarea reflejado en la barra de progreso. La creación de un hilo secundario nos ha permitido mantener el hilo principal libre de forma que nuestra interfaz de usuario de actualiza sin problemas durante la ejecución de la tarea en segundo plano. Sin embargo miremos de nuevo el código anterior. Complicado de leer, ¿verdad? Y eso considerando que tan sólo estamos actualizando un control de nuestra interfaz. Si el número de controles fuera mayor, o necesitáramos una mayor interacción con la interfaz el código empezaría a ser inmanejable, difícil de leer y mantener, y por tanto también más propenso a errores. Pues bien, aquí es donde Android llega en nuestra ayuda y nos ofrece la clase <span style="font-family: 'courier new', courier;">AsyncTask</span>, que nos va a permitir realizar esto mismo pero con la ventaja de no tener que utilizar artefactos del tipo <span style="font-family: 'courier new', courier;">runOnUiThread()</span> y de una forma mucho más organizada y legible. La forma básica de utilizar la clase <span style="font-family: 'courier new', courier;">AsyncTask</span>consiste en crear una nueva clase que extienda de ella y sobrescribir varios de sus métodos entre los que repartiremos la funcionalidad de nuestra tarea. Estos métodos son los siguientes:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">onPreExecute()</span>. Se ejecutará antes del código principal de nuestra tarea. Se suele utilizar para preparar la ejecución de la tarea, inicializar la interfaz, etc.</li>
<li><span style="font-family: 'courier new', courier;">doInBackground()</span>. Contendrá el código principal de nuestra tarea.</li>
<li><span style="font-family: 'courier new', courier;">onProgressUpdate()</span>. Se ejecutará cada vez que llamemos al método <span style="font-family: 'courier new', courier;">publishProgress()</span> desde el método <span style="font-family: 'courier new', courier;">doInBackground()</span>.</li>
<li><span style="font-family: 'courier new', courier;">onPostExecute()</span>. Se ejecutará cuando finalice nuestra tarea, o dicho de otra forma, tras la finalización del método <span style="font-family: 'courier new', courier;">doInBackground()</span>.</li>
<li><span style="font-family: 'courier new', courier;">onCancelled()</span>. Se ejecutará cuando se cancele la ejecución de la tarea antes de su finalización normal.</li>
</ul>
<p>Estos métodos tienen una particularidad esencial para nuestros intereses. El método <span style="font-family: 'courier new', courier;">doInBackground()</span> se ejecuta en un hilo secundario (por tanto no podremos interactuar con la interfaz), pero sin embargo todos los demás se ejecutan en el hilo principal, lo que quiere decir que dentro de ellos podremos hacer referencia directa a nuestros controles de usuario para actualizar la interfaz. Por su parte, dentro de <span style="font-family: 'courier new', courier;">doInBackground()</span> tendremos la posibilidad de llamar periódicamente al método <span style="font-family: 'courier new', courier;">publishProgress()</span> para que automáticamente desde el método <span style="font-family: 'courier new', courier;">onProgressUpdate()</span> se actualice la interfaz si es necesario. Al extender una nueva clase de <span style="font-family: 'courier new', courier;">AsyncTask</span>indicaremos tres parámetros de tipo:</p>
<ol>
<li>El tipo de datos que recibiremos como <strong>entrada</strong> de la tarea en el método <span style="font-family: 'courier new', courier;">doInBackground()</span>.</li>
<li>El tipo de datos con el que actualizaremos el <strong>progreso</strong> de la tarea, y que recibiremos como parámetro del método <span style="font-family: 'courier new', courier;">onProgressUpdate()</span> y que a su vez tendremos que incluir como parámetro del método <span style="font-family: 'courier new', courier;">publishProgress()</span>.</li>
<li>El tipo de datos que devolveremos como <strong>resultado</strong> de nuestra tarea, que será el tipo de retorno del método <span style="font-family: 'courier new', courier;">doInBackground()</span> y el tipo del parámetro recibido en el método <span style="font-family: 'courier new', courier;">onPostExecute()</span>.</li>
</ol>
<p>En nuestro caso de ejemplo, extenderemos de <span style="font-family: 'courier new', courier;">AsyncTask</span> indicando los tipos <span style="font-family: 'courier new', courier;">Void</span>, <span style="font-family: 'courier new', courier;">Integer</span> y <span style="font-family: 'courier new', courier;">Boolean</span>respectivamente, lo que se traducirá en que:</p>
<ul>
<li><span style="font-family: 'courier new', courier;">doInBackground()</span> no recibirá ningún parámetro de entrada (<span style="font-family: 'courier new', courier;">Void</span>).</li>
<li><span style="font-family: 'courier new', courier;">publishProgress()</span> y <span style="font-family: 'courier new', courier;">onProgressUpdate()</span> recibirán como parámetros datos de tipo entero (<span style="font-family: 'courier new', courier;">Integer</span>).</li>
<li><span style="font-family: 'courier new', courier;">doInBackground()</span> devolverá como retorno un dato de tipo booleano y <span style="font-family: 'courier new', courier;">onPostExecute()</span> también recibirá como parámetro un dato del dicho tipo (<span style="font-family: 'courier new', courier;">Boolean</span>).</li>
</ul>
<p>Dicho esto, cómo repartiremos la funcionalidad de nuestra tarea entre los distintos métodos. Pues sencillo, en <span style="font-family: 'courier new', courier;">onPreExecute()</span> inicializaremos la barra de progreso estableciendo su valor máximo y poniéndola a cero para comenzar. En <span style="font-family: 'courier new', courier;">doInBackground()</span> ejecutaremos nuestro bucle habitual llamando a <span style="font-family: 'courier new', courier;">publishProgress()</span> tras cada iteración indicando el progreso actual. En <span style="font-family: 'courier new', courier;">onProgressUpdate()</span> actualizaremos el estado de la barra de progreso con el valor recibido como parámetro. y por último en <span style="font-family: 'courier new', courier;">onPostExecute()</span> mostraremos el mensaje Toast de finalización de la tarea. Veamos el código completo:</p>
<pre class="brush: java; title: ; notranslate">
private class MiTareaAsincrona extends AsyncTask {

	@Override
	protected Boolean doInBackground(Void... params) {

		for(int i=1; i&lt;=10; i++) {
			tareaLarga();

			publishProgress(i*10);

			if(isCancelled())
				break;
		}

		return true;
	}

	@Override
	protected void onProgressUpdate(Integer... values) {
		int progreso = values[0].intValue();

		pbarProgreso.setProgress(progreso);
	}

	@Override
	protected void onPreExecute() {
		pbarProgreso.setMax(100);
		pbarProgreso.setProgress(0);
	}

	@Override
	protected void onPostExecute(Boolean result) {
		if(result)
			Toast.makeText(MainHilos.this, &quot;Tarea finalizada!&quot;,
					Toast.LENGTH_SHORT).show();
	}

	@Override
	protected void onCancelled() {
		Toast.makeText(MainHilos.this, &quot;Tarea cancelada!&quot;,
				Toast.LENGTH_SHORT).show();
	}
}
</pre>
<p>Si observamos con detenimiento el código, la única novedad que hemos introducido es la posibilidad de cancelar la tarea en medio de su ejecución. Esto se realiza llamando al método <span style="font-family: 'courier new', courier;">cancel()</span> de nuestra <span style="font-family: 'courier new', courier;">AsyncTask</span> (para lo cual añadiremos un botón más a nuestra aplicación de ejemplo, además del nuevo que añadiremos para comenzar la tarea). Dentro de la ejecución de nuestra tarea en <span style="font-family: 'courier new', courier;">doInBackground()</span> tendremos además que consultar periodicamente el resultado del método <span style="font-family: 'courier new', courier;">isCancelled()</span> que nos dirá si el usuario ha cancelado la tarea (es decir, si se ha llamado al método <span style="font-family: 'courier new', courier;">cancel()</span>), en cuyo caso deberemos de terminar la ejecución lo antes posible, en nuestro caso de ejemplo simplemente saldremos del bucle con la instrucción <span style="font-family: 'courier new', courier;">break</span>. Además, tendremos en cuenta que en los casos que se cancela la tarea, tras el método <span style="font-family: 'courier new', courier;">doInBackground()</span> no se llamará a <span style="font-family: 'courier new', courier;">onPostExecute()</span> sino al método <span style="font-family: 'courier new', courier;">onCancelled()</span>, dentro del cual podremos realizar cualquier acción para confirma la cancelación de la tarea. En nuestro caso mostraremos un mensaje Toast informando de ello.</p>
<div class="woo-sc-box normal  rounded full" style="padding-left:15px;background-image:none;">Puedes verlo en el vídeo entre los segundos 00:44 &#8211; 01:06</div>
<p>Mucho mejor que las alternativas anteriores, verdad? Pero vamos a mostrar una opción más. Si queremos que el usuario pueda ver el progreso de nuestra tarea en segundo plano, pero no queremos que interactúe mientras tanto con la aplicación tenemos la opción de mostrar la barra de progreso dentro de un diálogo. Android nos proporciona directamente un componente de este tipo en forma de un tipo especial de diálogo llamado <span style="font-family: 'courier new', courier;">ProgressDialog</span>.</p>
<p>Configurar un cuadro de diálogo de este tipo es muy sencillo. Para ello vamos a añadir un botón más a nuestra aplicación de ejemplo, donde inicializaremos el diálogo y lanzaremos la tarea en segundo plano. Para inicializar el diálogo comenzaremos por crear un nuevo objeto <span style="font-family: 'courier new', courier;">ProgressDialog</span> pasándole como parámetro el contexto actual. Tras esto estableceremos su estilo: <span style="font-family: 'courier new', courier;">STYLE_HORIZONTAL</span> para una barra de progreso tradicional, o <span style="font-family: 'courier new', courier;">STYLE_SPINNER</span> para un indicador de progreso de tipo <em>indeterminado</em>.</p>
<div id="attachment_3116" class="wp-caption alignnone" style="width: 269px"><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/progressdialog_horizontal.png"><img class="size-full wp-image-3116" title="ProgressDialog horizontal" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/progressdialog_horizontal.png" alt="ProgressDialog horizontal" width="259" height="119" /></a><p class="wp-caption-text">ProgressDialog horizontal</p></div>
<div id="attachment_3117" class="wp-caption alignnone" style="width: 248px"><a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/progressdialog_spinner.png"><img class="size-full wp-image-3117" title="ProgressDialog spinner" src="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/progressdialog_spinner.png" alt="ProgressDialog spinner" width="238" height="77" /></a><p class="wp-caption-text">ProgressDialog spinner</p></div>
<p>Lo siguiente será especificar el texto a mostrar en el diálogo, en nuestro caso el mensaje &#8220;<em>Procesando&#8230;</em>&#8220;, y el valor máximo de nuestro progreso, que lo mantendremos en 100. Por último indicaremos si deseamos que el diálogo sea <em>cancelable</em>, es decir, que el usuario pueda cerrarlo pulsando el botón <em>Atrás</em> del teléfono. Para nuestro ejemplo activaremos esta propiedad para ver cómo podemos cancelar también nuestra tarea en segundo plano cuando el usuario cierra el diálogo. Tras la configuración del diálogo lanzaremos la <span style="font-family: 'courier new', courier;">AsyncTask</span> del ejemplo anterior, que tendremos que modificar ligeramente para adaptarla al nuevo diálogo. Veamos el código por ahora:</p>
<pre class="brush: java; title: ; notranslate">
btnAsyncDialog.setOnClickListener(new OnClickListener() {

	@Override
	public void onClick(View v) {

		pDialog = new ProgressDialog(MainHilos.this);
		pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		pDialog.setMessage(&quot;Procesando...&quot;);
		pDialog.setCancelable(true);
		pDialog.setMax(100);

		tarea2 = new MiTareaAsincronaDialog();
		tarea2.execute();
	}
});
</pre>
<p>La <span style="font-family: 'courier new', courier;">AsyncTask</span> será muy similar a la que ya implementamos. De hecho el método <span style="font-family: 'courier new', courier;">doInBackground()</span> no sufrirá cambios.</p>
<p>En <span style="font-family: 'courier new', courier;">onProgressUpdate()</span> la única diferencia será que actualizaremos el progreso llamando al método <span style="font-family: 'courier new', courier;">setProgress()</span> del <span style="font-family: 'courier new', courier;">ProgressDialog</span> en vez de la <span style="font-family: 'courier new', courier;">ProgressBar</span>.</p>
<p>El código de <span style="font-family: 'courier new', courier;">onPreExecute()</span> sí tendrá algún cambio más. Aprovecharemos este método para implementar el evento <span style="font-family: 'courier new', courier;">onCancel</span> del diálogo, dentro del cual cancelaremos también la tarea en segundo plano llamando al método <span style="font-family: 'courier new', courier;">cancel()</span>. Además, inicializaremos el progreso del diálogo a 0 y lo mostraremos al usuario mediante el método <span style="font-family: 'courier new', courier;">show()</span>.</p>
<p>Por último, en el método <span style="font-family: 'courier new', courier;">onPostExecute()</span> además de mostrar el Toast de finalización, tendremos que cerrar previamente el diálogo llamando a su método <span style="font-family: 'courier new', courier;">dismiss()</span>.</p>
<p>Veamos el código completo de la <span style="font-family: 'courier new', courier;">AsyncTask</span> modificada para usar el nuevo <span style="font-family: 'courier new', courier;">ProgressDialog</span>.</p>
<pre class="brush: java; title: ; notranslate">
private class MiTareaAsincronaDialog extends AsyncTask {

    	@Override
    	protected Boolean doInBackground(Void... params) {

    		for(int i=1; i&lt;=10; i++) {
			tareaLarga();

			publishProgress(i*10);

			if(isCancelled())
				break;
		}

    		return true;
    	}

    	@Override
    	protected void onProgressUpdate(Integer... values) {
    		int progreso = values[0].intValue();

    		pDialog.setProgress(progreso);
    	}

    	@Override
    	protected void onPreExecute() {

    		pDialog.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				MiTareaAsincronaDialog.this.cancel(true);
			}
		});

    		pDialog.setProgress(0);
    		pDialog.show();
    	}

    	@Override
    	protected void onPostExecute(Boolean result) {
    		if(result)
    		{
    			pDialog.dismiss();
    			Toast.makeText(MainHilos.this, &quot;Tarea finalizada!&quot;,
					Toast.LENGTH_SHORT).show();
    		}
    	}

    	@Override
    	protected void onCancelled() {
    		Toast.makeText(MainHilos.this, &quot;Tarea cancelada!&quot;,
				Toast.LENGTH_SHORT).show();
    	}
    }
</pre>
<p>Si ahora ejecutamos nuestro proyecto y pulsamos sobre el último botón incluido veremos cómo el diálogo aparece por encima de nuestra actividad mostrando el progreso de la tarea asíncrona. Tras finalizar, el diálogo desaparece y se muestra el mensaje toast de finalización. Si en cambio, se pulsa el botón Atrás del dispositivo antes de que la tarea termine el diálogo se cerrará y se mostrará el mensaje de cancelación.</p>
<div class="woo-sc-box normal  rounded full" style="padding-left:15px;background-image:none;">Puedes verlo en el vídeo entre los segundos 01:07 &#8211; 01:35</div>
<p>Y con esto habríamos concluido este primer artículo sobre hilos y tareas en segundo plano. Os dejo a continuación el vídeo de demostración de la aplicación de ejemplo construida durante el tema, y como siempre el <a href="http://www.sgoliver.net/blog/wp-content/uploads/2012/07/android-hilos-asynctask.zip">código fuente completo</a> del ejemplo.</p>
<p><a href="http://www.youtube.com/watch?v=tSia91rGaCo">Demo &#8211; Hilos y AsyncTask en Android</a></p>
<p><iframe src="http://www.youtube.com/embed/tSia91rGaCo?feature=player_detailpage" frameborder="0" width="640" height="360"></iframe></p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgoliver.net/blog/?feed=rss2&#038;p=3099</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
	</channel>
</rss>
