Páginas

Grabar vídeo de la pantalla en Android
Recording the screen on Android

Hasta ahora, si queríamos grabar un vídeo de la pantalla de nuestro móvil, teníamos que recurrir a aplicaciones del market. He cacharreado con algunas, y no es un proceso sencillo, ya que la mayoría requieren tener el móvil rooteado o instalar programas auxiliares en el ordenador.
Por suerte, a partir de la versión 4.4 de Android (API level 19), se incluye la utilidad screenrecord al adb. Con ella, grabar la pantalla es tan sencillo como conectar el móvil e invocar el siguiente comando:

$ adb shell screenrecord /sdcard/record.mp4

De esa forma comienza la grabación. Para pararla pulsamos ctrl + C. El resultado estará en la ruta de nuestro teléfono que hayamos indicado.

El comando permite muchas opciones, como fijar el tiempo de grabación, el bit rate, etc. Aquí puedes encontrar más información.

En algunas ocasiones el comando me ha mostrado el error:

unable to create video/avc codec instance

No he conseguido averiguar a qué se debe, pero aplicando la solución universal del informático, se resuelve: reinicia tu móvil.
Until now, if you wanted to record a video of the screen of our mobile, we had to use market applications. I have tried some of them, and it´s not a simple process, as most require you to have the phone rooted or auxiliary programs installed on your computer.
Fortunately, since version 4.4 of Android (API level 19), it includes the adb utility screenrecord. With it, recording the display is as simple as plugging the phone and calling the following command:

$ adb shell screenrecord /sdcard/record.mp4

In this way the recording begins. To stop it press ctrl + C. The result is in the path of our phone that we have stated.

The command allows many options, such as setting the recording time, bit rate, etc. Here you can find more information.

Sometimes the command has shown me the error:

unable to create video/avc codec instance

I have not managed to find out why that is, but applying the universal solution to the computer, it solves: reboot your phone.

Asignar id a una vista en ejecución
Assign id to a view programmatically

Cuando se declara una vista en un fichero layout de Android, lo habitual es asignarle automáticamente un id.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/boardLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

Sin embargo, puede haber casos en que no podamos hacerlo. Por ejemplo, si queremos reutilizar una vista por medio de la etiqueta include, y necesitamos que cada instancia de la vista tenga un identificador distinto.

En estos casos tenemos la posibilidad de definir identificadores en un fichero de recursos.

<resources>
    <item name="id_1" type="id"/>
</resoruces>

Estos identificadores serán creados en la clase R, por lo que podremos recuperarlos y asignarlos a una vista con el siguiente código.

String name = "id" + i;   
view.setId(getResources().getIdentifier(name, "id", getPackageName()));

A partir de ese momento podremos recuperar la vista buscando por el id.
Views are usually declared in Android layout files with and id.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/boardLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

However, sometimes we can´t assign the id. For example, if we want to reuse a view with include tag, and we need each instance to have a different id.

In this situation, we have the option to define ids in a resource file.

<resources>
    <item name="id_1" type="id"/>
</resoruces>

This identifiers are created in R class, so you can access and assign them to a view in code.

String name = "id" + i;   
view.setId(getResources().getIdentifier(name, "id", getPackageName()));

From this point, we can find the view searching by id.

Evitar el giro de pantalla en Android
Avoid screen rotation in Android

Por defecto, las actividades Android se adaptan automáticamente a la orientación del dispositivo. Si queremos evitar este comportamiento no tenemos más que definir en el fichero AndroidManifest.xml de nuestra aplicación cuál es la orientación que debe tener cada actividad (portrait para vertical, y landscape para horizontal).

<activity android:name="package.YourActivity"
          android:screenOrientation="portrait" >

By default, Android activities adapt to device orientation. If we want to avoid this behaviour, we just have to define and orientation for any activity on AndroidManifest.xml file (portrait or landscape).

<activity android:name="package.YourActivity"
          android:screenOrientation="portrait" >

Evitar que se apague la pantalla
Keep the screen on

Cuando se está programando un juego, podemos requerir que la pantalla del dispositivo permanezca encendida incluso si el usuario pasa un tiempo sin interactuar. Es habitual encontrar soluciones que proponen el uso de wake locks, pero estos tienen dos grandes inconvenientes: requieren permisos especiales, y hacen que el programador tenga que ocuparse de liberarlos en el momento oportuno.

Existe una solución mucho más simple, que de hecho es recomendada en la página de desarrolladores de Android: usar el flag keep_screen_on. Este flag puede activarse para actividades individuales, tanto desde el manifest de la aplicación, como desde código. Esta segunda opción tiene la ventaja de que el flag puede igualmente desactivarse por código si en algún momento ya no es necesario.

En el manifest, declararíamos una actividad así:

<activity android:name="jvel.android.games.saveme.MainActivity" 
          android:keepScreenOn="true" />

En código, añadiríamos las siguientes líneas:

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

Si optamos por la opción de código, y más adelante queremos eliminar el flag:

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

When we are programming a game, we may want the screen of the device to keep on. There´s a solution based on wake locks, but it has two big advantages: it requieres special permission, and the developer needs to worry about releasing unused locks.

There is a much simpler solution, that is recommended in Android developers page: using flag_keep_screen_on. This flag can be activated only in an activity, from manifest or from code. This scond option has the adventage that the flag can be cleared if you don´t need it, allowing the screen to turn off again.

In manifest, we can declare an activity with:

<activity android:name="jvel.android.games.saveme.MainActivity" 
          android:keepScreenOn="true" />

In code, we have to add this line:

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

If we choose code option, and we want to clear the flag:

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

Añadir scroll a una actividad
Making an activity scrollable

Una duda habitual cuando se empieza a trabajar con Android es cómo añadir scroll a una actividad, o a algún componente de la interfaz. Y la verdad es que la solución es muy simple. Basta con añadir un ScrollView por encima del elemento al que queramos añadir el scroll.

Es importante tener en cuenta que la etiqueta ScrollView admite un único hijo. Lo habitual es que ese hijo sea un layout que a su vez contenga los elementos sobre los que se quiere hacer scroll.

Por tanto, si queremos que se pueda hacer scroll sobre la pantalla completa de una actividad, tendríamos algo así:

<ScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
        <!-- your components -->
    </RelativeLayout>
</ScrollView>

Si queremos aplicar el scroll a un grupo concreto de componentes de nuestra actividad, también podemos hacerlo. Por ejemplo, esta es la estructura de la actividad de configuración del juego en Batalla de Memoria, que deja la publicidad fija en la parte superior, y el botón en la inferior, haciendo que la barra de desplazamiento sólo afecte a las opciones en sí.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/adLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="horizontal" >
    </LinearLayout>

    <ScrollView
        android:id="@+id/options_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/adLayout"
        android:layout_above="@+id/playButton" >
        
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" >
            <!-- your components -->
        </RelativeLayout>
    </ScrollView>

    <Button
        android:id="@+id/playButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
         />

</RelativeLayout>
A typical question when someone is beginning with Android is how to add scroll to an activity, or a component of the interface. It has a really simple solution. You just have to add a ScrollView tag above the element you want to scroll.

It´s important to note that ScrollView tag supports just one child. It uses to be a layout that contains the rest of the components.

Therefore, if we want to make scroll over the entire interface of an activity, we have to write something like this:

<ScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
        <!-- your components -->
    </RelativeLayout>
</ScrollView>

If we want to apply scroll to a concrete group of components, we also can do it. This is the structure of configuration activity in Match Pairs Battle, which lets ad at the top, a button at the bottom, and allows scroll over the options.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/adLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="horizontal" >
    </LinearLayout>

    <ScrollView
        android:id="@+id/options_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/adLayout"
        android:layout_above="@+id/playButton" >
        
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" >
            <!-- your components -->
        </RelativeLayout>
    </ScrollView>

    <Button
        android:id="@+id/playButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
         />

</RelativeLayout>

Entradas multilenguaje en blogger
Multilingual posts in blogger

Una de las grandes carencias de Blogger es el multilenguaje, ya que hasta la fecha no aporta una solución para poder publicar las entradas en diferentes idiomas.

Tras bastante tiempo probando soluciones encontradas por la web, he conseguido llegar a una opción bastante sencilla y satisfactoria, basada en jQuery. Su principal ventaja respecto a la mayoría de las que he visto, es que permite que la entrada y su título puedan verse en distintos idiomas, de forma que las visitas, comentarios, etc., son únicos. También intenta mostrar al visitante el idioma más adecuado, de acuerdo a la configuración del navegador.

La solución se basa en crear un panel con el contenido del texto en cada uno de los idiomas deseados, y añadir botones que muestran un idioma determinado, y ocultan el resto.

La solución, paso a paso, sería:

1. Añadir la librería jQuery a la plantilla de nuestro blog. Para ello, vamos a Plantilla - Editar HTML, y en el código búscamos la etiqueta </head>. Justo detrás copiamos la siguiente línea:

<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js' type='text/javascript'/>

2. Añadir el script que selecciona el idioma por defecto, y controla las pulsaciones de los botones. Lo pegamos detrás de la línea anterior:

<script type='text/javascript'>
   $(document).ready(function() {
   <!-- Select the initial language -->
   var userLang = navigator.language || navigator.userLanguage; 
   if (userLang.indexOf(&quot;es&quot;) &gt;= 0) {
  $(&quot;div.english&quot;).hide();
   }
   else {
  $(&quot;div.spanish&quot;).hide();
   }
  <!-- If spanish button is clicked, hide english text, and show spanish text -->
  $(&quot;#spanish_button&quot;).click(function () {
    $(&quot;div.english&quot;).hide();
    $(&quot;div.spanish&quot;).fadeIn(300);
  });
  
  <!-- If english button is clicked, hide spanish text, and show english text -->
  $(&quot;#english_button&quot;).click(function () {
    $(&quot;div.english&quot;).fadeIn(300);
    $(&quot;div.spanish&quot;).hide();
  });
   });
 </script>

3. Añadir los botones para cambiar de idioma. Podemos ponerlos en cada una de las entradas, pegando siempre el código al comienzo de las mismas, en la vista HTML, o en algún lugar fijo del blog. En mi caso los he colocado en la cabecera. Como fuente de las imágenes debéis poner la ruta a aquellas que queráis utilizar.

<img align='right' height='50' id='english_button' src=%your_image% width='50'/>
<img align='right' height='50' id='spanish_button' src=%your_image% width='50'/>

4. Para cada entrada, escribir el texto en cada uno de los idiomas, dentro de una etiqueta div, indicando el idioma en el atributo class:

<div class="english">
English Text.
</div>
<div class="espanish">
Spanish Text.
</div>

Y ya está. Podéis ver el resultado en este mismo blog. Puedes añadir tantos idiomas como quieras.
One of the major shortcomings of Blogger is multilingual and, to date, it does not provide a solution to publish entries in different languages.

After some time testing solutions found in the web, I got a fairly simple and satisfactory option, based on jQuery. Its main advantage over most I've seen, is that it allows one entry with different languages, so that the views, comments, etc., are unique. It also attempts to show visitors the most appropriate language, according to the browser settings.

The solution is to create a panel with the contents of the entry in each of the desired languages, and add buttons for each language, showing the corresponding panel when it´s pressed, and hidding the others.

The solution, step by step:

1. Add the jQuery library to our blog template. To do this, go to Template - Edit HTML, and in code we search for </head>. Just behind, copy the following line:

<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js' type='text/javascript'/>

2. Add the script to select default language, and control clicks on the buttons. Paste it below the import line.

<script type='text/javascript'>
   $(document).ready(function() {
   <!-- Select the initial language -->
   var userLang = navigator.language || navigator.userLanguage; 
   if (userLang.indexOf(&quot;es&quot;) &gt;= 0) {
  $(&quot;div.english&quot;).hide();
   }
   else {
  $(&quot;div.spanish&quot;).hide();
   }
  <!-- If spanish button is clicked, hide english text, and show spanish text -->
  $(&quot;#spanish_button&quot;).click(function () {
    $(&quot;div.english&quot;).hide();
    $(&quot;div.spanish&quot;).fadeIn(300);
  });
  
  <!-- If english button is clicked, hide spanish text, and show english text -->
  $(&quot;#english_button&quot;).click(function () {
    $(&quot;div.english&quot;).fadeIn(300);
    $(&quot;div.spanish&quot;).hide();
  });
   });
 </script>

3. Add buttons to change language. We can place them in each post; to do that, we have to paste the code in the beginning of each one. The other option is to place the buttons in a fixed location. (Change %your_image% by the url of the image you want to use for the button).

<img align='right' height='50' id='english_button' src=%your_image% width='50'/>
<img align='right' height='50' id='spanish_button' src=%your_image% width='50'/>

4. For each post, write the text for each language in a div tag, indicating the language in class attribute. Do the same for the title.

<div class="english">
English Text.
</div>
<div class="espanish">
Spanish Text.
</div>

And that's it. You can see the result in the entries of this blog. You can add as many languages ​​as you want.

Now, I just have to improve my english...

Texto alternativo para listas vacías
Alternative text for empty lists

La clase ListView de Android ofrece el método setEmptyView, que permite mostrar un mensaje al usuario si una lista está vacía. Para utilizarlo, es necesario definir en el layout de la página un elemento con visibilidad gone, que será el que se le pase a dicho método.

Como ejemplo podemos ver cómo se ha implementado la lista de jugadores de Memory Battle. Este es el fragmento del fichero de layout.

    <ListView
        android:id="@+id/players_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" /> 

    <TextView
        android:id="@+id/no_players_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/playersList"
        android:text="@string/no_players"
        android:visibility="gone" />

Y este el código que indica a la lista que debe usar el TextView como vista a mostrar cuando no tiene elementos.

playersList = (ListView) findViewById(R.id.playersList);
playersList.setEmptyView(findViewById(R.id.no_players_text));

Dado que lo que espera setEmptyView es un objeto View, se podría cambiar el texto por cualquier otro componente, como una imagen.
ListView class in Android provides setEmptyView method, which allows us to show a message to the user if the list is empty. To use it, we have to define in the layout file an element with gone visibility. That will be the parameter for setEmptyView.

This example shows how I´ve implemented the players list on Memory Battle. This is the fragment in the layout file:

    <ListView
        android:id="@+id/players_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" /> 

    <TextView
        android:id="@+id/no_players_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/playersList"
        android:text="@string/no_players"
        android:visibility="gone" />

And this is the code to tell the list that it has to show the TextView when it has no elements.

playersList = (ListView) findViewById(R.id.playersList);
playersList.setEmptyView(findViewById(R.id.no_players_text));

You can use any View object as parameter for setEmptyView, so you could change the text for an image, for example.

Cadenas con parámetros
Parameters in strings

Cuando obtenemos cadenas de texto del fichero string.xml, tenemos una serie de opciones avanzadas, que incluyen la utilización de parámetros.

Un ejemplo clásico es aquél en que la cadena va a mostrar un dato de negocio, como podría ser el nombre del usuario. Puede que alguna vez hayas solucionado este problema sencillamente concatenando la cadena obtenida del fichero xml, con el nombre:

getString(R.string.hello) + Model.user_name;

Esta solución es perfectamente válida, pero no nos valdría si queremos insertar el valor en medio de la cadena. En este caso podemos aprovechar la posibilidad de añadir parámetros. En el fichero string.xml tendríamos una entrada como esta.


<string name="hello">Hola %1$s, bienvenido</string>

Cada parámetro contiene un dígito que sirve como identificador secuencial, y otro que indica su tipo: s - string, d - decimal...

En código podemos completar la cadena con la siguiente línea.

String.format(getString(R.string.hello), Model.user_name);

El método format de la clase String admite tantos parámetros como sean necesarios, por lo que podemos añadir tantos como queramos en la definición del texto.
When we get string from string.xml file, we can use some advanzed options, including using parameters.

For example, if we want to show a business data, like username. A simple solution is to concatenate it with a string from xml file:

getString(R.string.hello) + Model.user_name;

This is a correct solution, but not enough if we want to insert the value in the middle of the chain. In this case we can use the possibility to add parameters. We need an entry like this in the file string.xml.


<string name="hello">Hello %1$s, wellcome</string>

Each parameter has one digit that is used as a sequential identifier, and another one to tell the type: s - string, d - decimal...

In our code, we can complete the string with the next line.

String.format(getString(R.string.hello), Model.user_name);

Format method on class String can receive as many parameters as needed, so we can add all we need.

Personalizar la fuente de un texto
Customize text fonts

El tipo de letra a utilizar en un TextView se puede personalizar fácilmente en su definición xml, mediante la etiqueta android:typeface, que admite los valores sans, serif, monospace y normal.

<TextView android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"      
        android:typeface="sans" />

Si queremos utilizar otro tipo de letra, necesitaremos el correspondiente fichero ttf. En equipos Windows podemos encontrar todos los que tengamos instalados en la carpeta Windows/Fonts. Para incorporarlo a nuestro aplicación Android simplemente tenemos que copiarlo en la carpeta assets, que está directamente en la raíz del proyecto.


En la imagen se puede ver la estructura para el proyecto en el que estoy trabajando actualmente, con la fuente Bubble Bath, que Rocco Pissani me ha cedido amablemente (¡Gracias!).

Una vez situada la fuente en su lugar, podemos utilizarla desde el código de cualquier actividad, y aplicarla a un TextView.

Typeface tf = Typeface.createFromAsset(this.getAssets(),
    "bubblebath.ttf");
textView.setTypeface(tf);
The font used in TextView elements can be customized in its xml definition, with the android:typeface tag, which can take sans, serif, monospace and normal values.

<TextView android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"      
        android:typeface="sans" />

If we want to use another font, we need its ttf file. Under Windows we can find all that we have installed in our computer in the folder Windows/Fonts. To add it to our Android application, we just have to put it in assets folder, that is in the project root folder.


The previous picture shows the structure of the project I´m working actually, with the font Bubble Bath, that Rocco Pissani has yielded to me, nicely (Thanks!).

Once the font is placed in its location, we can use it in the code of any activity, and apply it to a TextView.

Typeface tf = Typeface.createFromAsset(this.getAssets(),
    "bubblebath.ttf");
textView.setTypeface(tf);

Eliminar la animación entre actividades
Remove animation between activities


Por defecto, siempre que comenzamos o terminamos una actividad en Android, ésta se presenta con una animación. En general este comportamiento pasa desapercibido, pero puede haber casos en que no sea deseable. Por ejemplo, si queremos navegar entre actividades sin que el usuario se percate de ello.

Para desactivar la animación de una actividad tendremos que crear un nuevo Theme en nuestro fichero de estilos, ubicado en values/styles.xml.

<style name="noAnimTheme" parent="android:Theme">
        <item name="android:windowAnimationStyle">@null</item>
</style>

A continuación, sólo nos queda indicar en el manifest de la aplicación qué actividades usarán ese estilo.

<activity android:name="jvel.android.kidsgames.MainActivity"
          android:theme="@style/noAnimTheme" >
</activity>

Si en lugar de eliminar la animación, lo que queremos es utilizar una específica, sólo tenemos que indicar cuál en el elemento android:windowAnimationStyle del fichero de estilos.

By default, when we throw or we finish an activity on Android, it is begun with an animation. Most times this behaviour is good, but we can remove it if we need.

To remove the animation, we have to create a new Theme in styles, placed on values/styles.xml.

<style name="noAnimTheme" parent="android:Theme">
        <item name="android:windowAnimationStyle">@null</item>
</style>

Next, we have to change manifest file to tell what activities must use our new style.

<activity android:name="jvel.android.kidsgames.MainActivity"
          android:theme="@style/noAnimTheme" >
</activity>

If we don´t want to remove the animation, but change it, we just have to write the wanted one in android:windowAnimationStyle.

Redimensionar mesas de trabajo en Illustrator

Una de las tareas más tediosas cuando se desarrolla para Android es la de crear los recursos gráficos (iconos, imágenes, etc.) para las distintas densidades de pantalla.

He hecho algunas pruebas con Adobe Illustrator CS6, y el uso de las mesas de trabajo o artboards puede ayudar mucho, ya que a la hora de exportar el trabajo a png, cada mesa de trabajo genera un archivo independiente.

El gran problema viene cuando tengo todo lo que necesito para la aplicación, y toca generar las versiones para cada densidad de pantalla. La idea es muy simple, selecciono todos los elementos del ficheros, y aplico el factor de escala necesario. El primer problema que surge es que esta transformación no afecta a las mesas de trabajo, y no existe (que yo conozca), un método para redimensionarlas por grupos. Así que toca ir una a una dándoles el tamaño correcto. Esto, cuando se trabaja con montones de imágenes, y con al menos 3 densidades de pantalla, resulta inasumible.

Por suerte Illustrator permite realizar scripts (secuencias de comandos) con los que automatizar estas tareas. Así, podríamos usar el siguiente script para redimensionar varias mesas de trabajo de una vez.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
if (app.documents.length > 0) {
 var idoc = app.activeDocument;

 var fromArtBoard = idoc.artboards.length + 1;
 var toArtBoard = idoc.artboards.length + 1;

 while (fromArtBoard < 0 || toArtBoard < 0 || fromArtBoard > idoc.artboards.length || toArtBoard > idoc.artboards.length) {
  fromArtBoard = Number(Window.prompt ("Enter the first artboard to resize", 1)) - 1;
  toArtBoard = Number(Window.prompt ("Enter the last artboard to resize", idoc.artboards.length)) - 1;
 }

 var width = Number(Window.prompt ("Enter new Artboard width", "new width"));
 var height = Number(Window.prompt ("Enter new Artboard height", "new height"));
 
    for (i=fromArtBoard; i<=toArtBoard; i++) {
        var abBounds = idoc.artboards[i].artboardRect;// left, top, right, bottom
 
        var ableft = abBounds[0];
        var abtop = abBounds[1];
        var abwidth = abBounds[2] - ableft;
        var abheight = abtop - abBounds[3];
        
        var abctrx = abwidth/2+ableft;
        var abctry = abtop-abheight/2;
             
        var ableft = abctrx-width/2; 
        var abtop = abctry+height/2;
        var abright = abctrx+width/2; 
        var abbottom = abctry-height/2;
        
        idoc.artboards[i].artboardRect = [ableft, abtop, abright, abbottom];
    }
}

Pero aquí no acaban los problemas. Si usamos este script y redimensionamos nuestras imágenes con la transformación de escala, seguramente nos encontraremos que la posición de las imágenes ya no coincide con la de sus respectivas mesas de trabajo. De nuevo tendríamos que ir recolocándolas una a una en su posición. Para evitarlo, tendríamos que generar un script que se encargase de redimensionar tanto las mesas de trabajo como las imágenes, de forma que se mantuviesen en la misma posición. Podría ser algo así.


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
if (app.documents.length > 0) {
 var idoc = app.activeDocument;

 var factor = Number(Window.prompt ("Enter scale factor as percentage", 100));
 
 var ilayer = idoc.activeLayer;
 
 /* Recorremos todos los grupos de objetos escalando */
 for(i = 0; i < idoc.groupItems.length; i++) {
  if (idoc.groupItems[i].parent == ilayer) {
   idoc.groupItems[i].resize(factor,factor,true,true,true,true, factor, Transformation.CENTER);
  }
    }
 
 /* Recorremos todas las mesas de trabajo escalando*/
    for (i=0; i<idoc.artboards.length; i++) {
        var abBounds = idoc.artboards[i].artboardRect;
 
        var ableft = abBounds[0];
        var abtop = abBounds[1];
        var abwidth = abBounds[2] - ableft;
        var abheight = abtop - abBounds[3];
        
        var abctrx = abwidth/2+ableft;
        var abctry = abtop-abheight/2;
             
  var width = abwidth * factor / 100;
  var height = abheight * factor / 100;
        var ableft = abctrx-width/2; 
        var abtop = abctry+height/2;
        var abright = abctrx+width/2; 
        var abbottom = abctry-height/2;
        
        idoc.artboards[i].artboardRect = [ableft, abtop, abright, abbottom];
    }
}
 else  {
        alert ("there are no open documents");
}

Hay un tema importante a tener en cuenta. Lo que hace el script es redimensionar todas las agrupaciones de objetos que se encuentran directamente en la capa activa del documento. Lo he hecho así porque en mis pruebas trabajé con una única capa, con todos los elementos de una mesa de trabajo en un único grupo, por lo que es el funcionamiento que necesitaba. Si trabajas con varias capas puedes modificar el script para recorrerlas todas, o para que te pida qué capas procesar. Si no agrupas los elementos tendrás que modificar el script para recorrer todos los elementos del documento, en lugar de los grupos.

Activar opciones de desarrollo

Una de las decisiones que ha tomado la gente de Android en las últimas versiones (creo que desde la 4.2), es la de ocultar por defecto las opciones de desarrollo.

Acceder a estas opciones es imprescindible si queremos empezar a utilizar nuestro teléfono para probar nuestras aplicaciones porque, entre otras muchas cosas, nos permiten habilitar la depuración por usb.

La activación es muy simple. Sólo tenemos que acceder al menú ajustes, meternos en información del teléfono, y pulsar 7 veces sobre el número de compilación. Sí, es un poco extraño. Estos de Android...

Si todo ha ido bien, verás un mensaje en el que tu teléfono te nombra oficialmente desarrollador, y podrás acceder a las opciones bajo el menú de ajustes.

Ya podemos empezar a cacharrear.

Splash Screen

Un clásico de cualquier aplicación móvil son las splash screen, o pantallas de bienvenida. No son más que pantallas de transición que se muestran a los usuarios antes de darles acceso a la aplicación. Algunas veces se aprovechan para hacer cosas en segundo plano, como cargar recursos, y otras simplemente sirven para mostrar tu logo y meterle a los usuarios tu marca.

Hacer una splash screen en Android es muy simple. Primero necesitamos una activity cuyo layout muestre tu logo. Por ejemplo:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    tools:context=".JVelActivity" 
    android:onClick="onClick"
    android:background="#0C416F">

<ImageView android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:contentDescription="@string/jvel"
    android:src="@drawable/jvel"/>

</LinearLayout>

Y en el código de la misma podemos hacer algo como esto:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class JVelActivity extends FullScreenActivity {
 
 private boolean gone = false;
 
 private Thread waitThread;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_jvel);
  
  waitThread = new Thread()
     {
         public void run()
         {
             try
             {
                 //Mostramos la pantalla durante tres segundos, una vez terminados, vamos a la pantalla principal
                 sleep(3000);
             }
             catch (InterruptedException e) 
             {}
             finally
             {   
                 toMainActivity();
             }
         }
     };
     waitThread.start();
 }
 
 private void toMainActivity() {
  if (!gone) {
  Intent intent = new Intent(this, MenuActivity.class);
         startActivity(intent);
         gone = true;
         finish();
  }
 }
 
 public void onClick(View view) {
  toMainActivity();
 }
}

Lo único que hace la actividad es crear un hilo que se duerme durante 3 segundos y, pasado ese tiempo, envía la aplicación a la verdadera pantalla principal (MenuActivity en este caso). También se contempla la posibilidad de que el usuario se harte de esperar, y toque la pantalla. En ese caso pasará inmediatamente al menú, gracias al método onClick, que se ha añadido como oyente para el LinearLayout de fondo.

Lógicamente la actividad debe ser la inicial de la aplicación. Ya sabéis que podéis configurarla como tal en el manifest:

1
2
3
4
5
6
7
8
<activity
            android:name="jvel.android.game.calculator.JVelActivity"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

Puedes ver una implementación de esta pantalla (con el espectacular logo de jVel) en cualquiera de mis juegos. En algunos casos se complementa con una carga de recursos en segundo plano, y un mensaje informativo. Eso para otro día.

Librería de juegos: FullScreenActivity

Uno de los objetivos que me marco conforme voy haciendo nuevas cosas en Android, es crear una librería con las utilidades que suelo acabar utilizando en la mayoría de mis programas. Así es como nace mi GameLibrary.


Creo que lo primero que añadí a la librería fue la clase FullScreenActivity. Una clase tremendamente sencilla, pero que me ahorra muchas líneas de código. También es verdad que a mí me gusta que todas mis actividades se presenten en pantalla completa, eliminando la barra de título. La clase en cuestión queda así:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class FullScreenActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  requestWindowFeature(Window.FEATURE_NO_TITLE);
  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN);
 }
}

Sólo tienes que hacer que una actividad herede de ella para tener una actividad de pantalla completa. Si sólo quieres aplicarlo a alguna clase concreta, mejor copiar las dos líneas de código directamente en ella.