viernes, 27 de junio de 2014

Corona SDK - Juego de naves (II) : Objetos de visualización y Gestión de eventos

  En este blog veremos como podemos crear un sencillo juego de naves para nuestro dispositivo de forma rápida gracias a las funcionalidades que nos ofrece Corona SDK.

  El juego consiste en una nave que podremos mover desplazando el dedo por la pantalla. La nave deberá esquivar las bombas que irán apareciendo para no explotar y perder la partida. Como podemos ver es un juego muy sencillo pero que nos permitirá ver algunas de las principales funciones de Corona como son los objetos de visualización, gestión de eventos, colisiones, gestión de escenas y el uso de spritee para la animación de la nave.

  A continuación se muestra una imagen del resultado final:



Objetos de Visualización


  Lo primero que pasaremos a crear será el fondo de nuestro juego. Para ello disponemos de la clase Display, con ella podemos crear cualquier objeto de visualización de nuestras escenas conocidos en Corona como DisplayObject. Para la creación y visualización de imágenes en la pantalla del dispositivo disponemos de dos funciones:

  display.newImage()
  display.newImageRect()

  La diferencia entre estas dos funciones es que si usamos la segunda cuando ejecutemos el juego en pantallas de diferente resolución Corona se encargará de re-dimensionarlas y ajustarlas a la nueva resolución, por tanto lo recomendable es esta. Una vez visto está diferencia pasamos a definir nuestros diferentes fondos:

local localGroup = display.newGroup( )
local backgroundGroup = display.newGroup( )

background = display.newImageRect( imgDir.."mainbackground.png", display.contentWidth, display.contentHeight )
background.x = display.contentCenterX ; background.y = display.contentCenterY
background:addEventListener( "touch", playerControl )
localGroup:insert(background)

middleBackground = display.newImageRect( imgDir.."bgLayer1.png", display.contentWidth+10, display.contentHeight )
middleBackground.x = display.contentCenterX ; middleBackground.y = display.contentCenterY
middleBackground.name = "middle"
backgroundGroup:insert( middleBackground )

beforeBackground = display.newImageRect( imgDir.."bgLayer2.png", display.contentWidth+10, display.contentHeight )
beforeBackground.x = display.contentCenterX ; beforeBackground.y = display.contentCenterY
beforeBackground.name = "before"

backgroundGroup:insert( beforeBackground )


 Hemos definido tres fondos para crear el cielo de la escena. El orden de creación de los objetos Display marcará el orden de visualización en pantalla, de tal forma que el último objeto creado se superpondrá a cualquier objeto que haya sido creado con anterioridad y ocupe el mismo espacio en la pantalla.

Cabe destacar en este código el uso de lo que se conoce en Corona como "Grupos". Un grupo es un contenedor de objetos DisplayObject con el que podremos gestionar de mejor forma nuestra escena. La clase Group hereda de DisplayObject y con esto podríamos destacar tres cosas muy importantes:

-  Un grupo puede contener objetos de visualización y/o a otros grupos.
-  A un grupo se le pueden aplicar las mismas transformaciones que a un objeto DisplayObject de tal forma que dichas transformaciones se le aplicará a todos los elementos que contiene.
-  Un objeto DisplayObject no puede estar contenido en dos grupos distintos.

  Con estas implicaciones en mente podemos por tanto plantearnos la gestión de una escena mediante el uso de un "árbol de escena". Un árbol de escena se podría ver como el árbol de directorios de un SO donde dicho árbol contendrá distintos nodos hoja que serán los diferentes objetos de visualización de nuestra escena y los nodos padre serán los objetos Group que los contendrán a los cuales les aplicaremos las distintas transformaciones.

  Otro de los objetos Display que más usaremos en un juego con animaciones 2D serán los sprite. En nuestro caso hemos hecho uso de estos sprites para crear la animación de la nave moviendo las alas:

 local options = {
    width = 115,
    height = 69,
    numFrames = 8
}
local playerSheet = graphics.newImageSheet( imgDir.."playerAnimation.png", options )
local sequenceData = { name = "player", start=1, count=8, time=1000, loopCount=0, loopDirection = "forward" }

spritePlayer = display.newSprite( playerSheet, sequenceData )
spritePlayer.x = display.contentCenterX/2 
spritePlayer.y = display.contentCenterY
spritePlayer.name = "player"
spritePlayer:play( )
backgroundGroup:insert( spritePlayer )

  Con la función "newImageSheet" de la clase Graphics nos creamos una imagen que Corona será capaz de manejar para obtener las diferentes imágenes o fotogramas que contiene para nuestra animación. La tabla sequenceData almacena las diferentes animaciones que podrá contener nuestro objeto Sprite, por tanto como podremos observar podemos definir distintas animaciones para una misma imagen dentro de esta tabla simplemente indicando el nombre "único" que identifique a la animación, el fotograma de inicio, el fotograma final y la duración de la animación. Una vez creado todos estos datos se los pasamos a la funcion "newSprite" para que nos cree nuestro objeto Sprite, lo posicionamos y iniciamos su animación.

  Por defecto se reproduce la primera animación que hayamos declarado en la tabla "sequenceData" pero si quisiéramos  iniciar otra simplemente debemos indicarlo mediante la función "setSequence("nombreAnimacion")" de la clase SpriteObject antes de ejecutar la llamada a la función "play()".



Gestión de eventos


  Los diferentes tipos de eventos que define Corona podrán ser capturados por los objetos DisplayObject y por el oyente global Runtime. La forma de añadir una captura de evento a un objeto se realiza de la siguiente forma:

  object : addEvenListener("nombreDelEvento", funcion_para_manejarlo)

  En Corona podemos distinguir dos tipos de eventos genéricos: los eventos en tiempo de ejecución y los eventos locales.

 Eventos en tiempo de ejecución


  Los eventos en tiempo de ejecución son capturados por el oyente Runtime ya que no van dirigidos a ningun objeto oyente en concreto. En este tipo de eventos podemos destacar los siguientes:

 -  EnterFrame: que se disparará en cada frame por segundo en el intervalo de nuestra aplicación (30 o 60).
 -  System: este tipo de evento se disparará cuando se produce algún evento externo a la aplicación, por ejemplo cuando el dispositivo se suspende.
 -  Orientation: se produce cuando la orientación del dispositivo cambia.

  Eventos locales


  Los eventos locales solo pueden ser capturados por los objectos Display ya que estos si van dirigidos a uno o varios objetos concretos. Entre todos los eventos locales podemos destacar los siguientes:

  - Hit: este tipo de evento ocurre cuando el usuario toca la pantalla del dispositivo.

  - Collision : se dispara cuando el motor de física detecta una colisión entre dos objetos.

  - Timer: este tipo de evento se dispara cuando transcurre X tiempo desde que se declaró, es el equivalente a un trigger.

  Cabe destacar y entrar en mayor profundidad en los eventos del tipo "hit" ya que serán unos de los más usados . Cuando el usuario pulsa la pantalla del dispositivo se dispararán dos tipos de eventos: "tap" y "touch". La diferencia entre ambos es que el evento tap solo se dispara cuando se pulsa la pantalla mientra que el evento "touch" será disparado en todo el ciclo de pulsación es decir cuando se pulsa, cuando se desplaza el dedo por la pantalla y cuando se suelta. Estos dos eventos serán lanzados para todos aquellos objetos que interceptan con la pulsación en pantalla. El primer objeto en recibir el evento será aquel que este por "encima" o con "menor profundidad" y se irá lanzando sucesivamente hasta el ultimo objeto con mayor profundidad. Esta cadena sucesiva de lanzamiento se podrá detener si la función donde se maneja el evento de cualquier objeto de la cadena devuelve "true".

  La forma de eliminar la captura de un evento de un objeto se realiza mediante la función :

   object : removeEvenListener("nombreDelEvento", funcion_para_manejarlo)

  Con respecto a dicha eliminación existe una gran diferencia entre los eventos locales y los de tiempo de ejecución. Cuando se destruye el objeto Display mediante la llamada a la función "removeSelf()"también se elimina el "listener" de este objeto para los distintos eventos que se haya asignado, por tanto si borramos un objeto Display para cambiar de escena no tenemos que preocuparnos de eliminar sus "listeners" asignados previamente. Sin embargo los "listeners" declarados para los eventos en tiempo de ejecución  permanecen entre el cambio de escena ya que el escuchador global Runtime no puede ser borrado y por tanto debemos asegurarnos de borrar los "listeners" cuando dejemos de necesitarlos para evitar errores indeseados.

  Una vez explicado todo esto pasamos a introducir la gestión de eventos en nuestra escena principal del juego:

local function playerControl( event )
-- body
local dx 
local dy

if (event.phase == "began") then
display.getCurrentStage():setFocus(event.target)

posInic.X = event.x
posInic.Y = event.y
elseif (event.phase == "moved") then

dx = event.x - posInic.X
dy = event.y - posInic.Y

spritePlayer:translate( dx, dy )

checkCollision()

posInic.X = event.x
posInic.Y = event.y
elseif (event.phase=='ended' or event.phase == "cancelled" ) then  
  display.getCurrentStage():setFocus(nil)
  end

return true
end

background:addEventListener( "touch", playerControl )

  En este código lo que hacemos es asignarle al fondo trasero la captura del evento "touch" para la pulsación sobre la pantalla por parte del usuario. La función encargada de manejar este evento será "playerControl" en la cual lo que hacemos es mover la nave a la posición en la que se encuentra el dedo del usuario, dicha posición nos la da las variables event,x y event.y. Como podemos observar en esta función hacemos una distinción de la "fase" en la que nos encontramos cuando se dispara el evento:

  - Si es la fase inicial asignamos el "focus" del programa a nuestra imagen del fondo trasero que fue la que lanzó este evento al ser pulsada (la podemos obtener mediante la variable event.target). Con esto lo que pretendemos es asegurarnos de que mientras el usuario mueva su dedo por la pantalla ningún otro listener sea disparado para este evento y por tanto solo se realice la funcionalidad de mover la nave mientras el usuario toca la pantalla y así evitar errores no deseados.
  - Si la fase es "moved" significa que el usuario no ha soltado el dedo de la pantalla y lo está moviendo por la pantalla por tanto cambiamos la posición de la nave y comprobamos si se ha detectado alguna colisión con alguna bomba.
  - Si la fase es final o cancelado significa que el usuario a dejado de pulsar sobre la pantalla o que por alguna razón externa se debe abortar la captura de este evento y por tanto lo que hacemos es quitar el "focus" del objeto fondo trasero.

  Por ultimo devolvemos "true" para indicar que hemos manejado este evento y que no se necesita seguir lanzandolo a objetos que se encuentre en un nivel inferior de la jerarquía de manejo para este evento.



El código completo lo podrás encontrar en la siguiente dirección:

https://github.com/Muffin-Apps/ShipGame-CoronaSDK

  

domingo, 22 de junio de 2014

Cómo monetizar nuestra aplicacion

Introducción


 Si bien es cierto que la mayoría de la gente a la hora de descargar una aplicación o un juego de la tienda de su dispositivo prefiere utilizar aplicaciones gratuitas y es muy reacia a gastarse el dinero en comprar aplicaciones para su dispositivo, podemos rentabilizar nuestra aplicación de diferentes formas como puede ser mediante el uso de un sistema de micro-pagos o de publicidad dentro de nuestra aplicación. Sin embargo en este blog nos centraremos en el primero.

 Desde hace unos pocos de años a surgido un concepto nuevo y que cada vez cobra más fuerza, especialmente en los videojuegos, y es el concepto de "freemiun":

  El concepto "freemiun" consiste en publicar una app de forma gratuita, de modo que el usuario pueda descargarla sin ningún coste, pero algunos de sus contenidos serán de pago. El usuario podrá realizar ciertas tareas y acceder a ciertas funcionalidades de forma gratuita pero para el resto de contenidos deberá pagar.

  Está es una buena forma de poder rentabilizar los costes y mantenimiento de nuestra aplicación a la vez de conseguir un amplio número de descargas, con lo que ello supone, debido a que la aplicación en sí es gratuita. Para poder implementar un sistema de transacciones dependerá de la plataforma en la que estemos desarrollando:

-  Plataforma iOS: en iOS el sistema de micro-pagos se conoce como "In-App Purchase". Este recurso te permite vender de forma segura recursos virtuales o no, manejar un sistema de suscripciones y contenidos premium. Para introducir este sistema en nuestra aplicación debemos hacer uso del framework "Store Kit framework" el cual nos permitirá conectar nuestra aplicación con la App Store para realizar los pagos de forma segura  y nos devolverá los elementos comprados por el usuario. Un posible ejemplo para la utilización de este sistema sería el de crear un juego con niveles extra, para los cuales el usuario deberá pagar por ellos para poder jugarlos. Para más información puede consultar el siguiente enlace: https://developer.apple.com/in-app-purchase/

-  Plataforma Android: si lo que queremos es introducir un sistema de transacciones en nuestra aplicación podemos hacer uso de "Google Wallet". Sin embargo podemos dejar la gestión y procesamiento de los pagos a Google, utilizando el " Google Play In-App Billing" para poder acceder a los servicios de Google Play Store y realizar las ventas de forma segura. Para poder hacer uso de estos servicios debemos obtener un "client-ID" para OAuth 2.0 en la consola de desarrollador de google para que así el servicio de Google Play pueda identificar de forma única a nuestra aplicación. Para obtener más información acerca de este proceso puede consultar el siguiente enlace : https://developers.google.com/wallet/instant-buy/android/tutorial

  El utilizar las bibliotecas oficiales de estas plataformas implica también el cobro de una comisión por parte de iOS o Android por cada compra realizada por el usuario.


Añadiendo un sistema de micro-pagos a nuestro juego


Una buena forma de rentabilizar nuestro juego y sacar beneficios sin que el usuario deba pagar por él, podría ser el introducir un sistema de monedas. Este sistema de monedas le proporciona al jugador el poder adquirir elementos del juego para poder personalizar su avatar, obtener elementos consumibles, obtener distintas mejoras y poder subir de nivel , etc. En dicho sistema podríamos diferenciar entre dos tipos de monedas:

-  Monedas virtuales: las cuales el jugador obtendrá durante el desarrollo del juego. El uso de este tipo de moneda también propicia una buena forma de "enganchar" al jugador al juego a corto plazo.
-  Monedas reales: las cuales podrán adquirirse mediante el pago con dinero real. Con estas monedas el jugador podrá tener acceso anticipado a elementos que se desbloquearán más tarde en el juego o a contenidos extras solo accesibles mediante el uso de estas monedas.

  El utilizar este sistema para introducir los micro-pagos en nuestro sistema tiene además una ventaja con respecto a comprar directamente los elementos con dinero real. Esto es debido a que si por ejemplo la forma de pago es mediante tarjeta de crédito el uso de la misma conlleva el cobro de unos intereses o comisión extra. Por tanto el uso continuado del pago con tarjeta implicará un mayor coste para el usuario en la compra de varios artículos, en lugar de utilizar un sistema de monedas virtuales donde el usuario solo deberá hacer uso de su tarjeta para comprar "X" monedas con las que podrá comprar diferentes elementos y las monedas restantes quedarán almacenadas para su posterior uso.

  Sin embargo el implementar de forma manual un sistema de micro-pagos en nuestro juego puede ser una tarea muy laboriosa ya que deberemos de implementar un servicio "backend" para dicho sistema, preferiblemente un servicio web ya que esto le proporcionará al usuario la ventaja de poder consultar sus compras o comprar artículos sin la necesidad de entrar en la aplicación. Este servicio web se deberá encargar del almacenamiento y gestión de los distintos elementos consumibles del juego, de la gestión del monedero del usuario y la realización de los pagos de forma segura, como por ejemplo mediante el uso de la API SOAP de PayPal (https://developer.paypal.com/webapps/developer/docs/classic/api/PayPalSOAPAPIArchitecture/).

  Por tanto podemos utilizar diferentes pluggins o servicios que han sido ya creados por otros desarrolladores y que ya están testeados y comprobados en diferentes aplicaciones. El elegir un servicio u otro dependerá de la tecnología que estemos utilizando para desarrollar nuestro juego.

  Por ejemplo si estamos utilizando la plataforma de Unity3D podemos encontrar diferentes pluggins para implementar este servcio de micro-pagos:

  -  Prime31 : nos proporciona diferentes pluggin para la monetización de nuestra aplicación para las plataforma iOS, Android, Windows. El precio de los diferentes pluggin varía entre los 40$ y 140$. Este es el enlace con su página web: https://prime31.com/plugins
  -  SOOMLA: es un framework de código abierto para la gestión de la economía del juego. Este framework permite de forma sencilla integrar una tienda ahorrando al desarrollador el esfuerzo de implementar la funcionalidad del almacén, dependencias de bienes virtuales, interfaces de facturación y beneficios. En enlace a su página web es el siguiente: http://project.soom.la/#unity_anchor.

Conclusiones


  Este nuevo concepto sobre cómo generar beneficios mediante el lanzamiento de aplicaciones gratuitas está cobrando cada vez más fuerza y las principales compañías y desarrolladoras de videojuegos lo están comenzando a adoptar ya que es una buena forma de captar nuevos usuarios debido a la preferencia de la gente a descargar aplicaciones gratuitas antes que tener que pagar por ellas.

  En mi opinión el uso de publicidad para generar beneficios y pagar por la aplicación para eliminarla es una mala forma ayudar al crecimiento de tu aplicación ya que la gente siempre preferirá desinstalar la aplicación y descargarse otra gratuita sin publicidad, a pagar 1€ por eliminar la publicidad. Sin embargo el utilizar un sistema de micro-pagos si se inserta de forma correcta en el juego, puede generar muchos beneficios ya que si el jugador se siente cada vez más atraído por el juego comenzará a pagar por él.

Principales referencias usadas









sábado, 21 de junio de 2014

Añadir el patrón Pull to Refresh a nuestra App


Uno de los patrones de interacción que vemos cada vez más es el de Pull To Refresh. Consiste en que el usuario realiza una acción de pull (tirar) vertical hacia abajo para ejecutar una actualización del contenido que esta viendo. Podemos verlo en aplicaciones móviles como gestores de correo, Google+ o Facebook, es decir, en aplicaciones que muestran contenido obtenido de forma remota y que por lo tanto necesita ser actualizado. Es una forma sencilla e intuitiva de que el usuario realice una actualización manual.

Aunque siempre puedes implementar tu mismo este patrón, existen varias bibliotecas que nos simplificarán la vida bastante. Yo he elegido ActionBar-PullToRefresh. Es muy fácil de usar y de personalizar pero, como se puede ver en su página de GitHub, ya no esta bajo mantenimiento. Otro aspecto a tener en cuenta es que solo funciona para versiones de Android igual o superiores a 4.0 (API 14).


Un ejemplo sencillo:

Vamos a aplicar el patrón sobre un Activity que despliega una lista, dejando la
personalización para mas adelante y trabajando con los valores por defecto. En el caso de los Fragment la inicialización es un poco diferente pero los cambios son mínimos.

Primero debemos incluir un PullToRefreshLayout como layout raíz de la interfaz de nuestra Activity (MainActivity):
<uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/my_ptr_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

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

</uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout>

PullToRefreshLayout solo funciona por defecto con:
  • Clases que heredan de AbsListView (como ListView o GridView)
  • ScrollView
  • WebView

Para cualquier otro tendremos que implementar nuestro propio ViewDelegate.

Ahora inicializaremos PullToRefreshLayout en el código de MainActivity:
public class MainActivity extends Activity{
  private View txt;
  private PullToRefreshLayout pullLayout;
  private final OnRefreshListener onRefreshListener =
    new OnRefreshListener(){
      @Override
      public void onRefreshStarted(View v){
        // Aqui realizaremos la actualización
      }
    };

  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    pullLayout = (PullToRefreshLayout) findViewById(R.id.my_ptr_layout);
    // Inicializamos el PullToRefreshLayout
    ActionBarPullToRefresh.from(this)
      // Marcamos todos los hijos para que puedan recibir la accion de pull
      .allChildrenArePullable()
      // Le indicamos el OnRefreshlistener
      .listener(onRefreshListener)
      // Terminamos la inicializacion
      .setup(mPullToRefreshLayout);
    //...
  }
  //...
}


Hay que tener en cuenta que el método onRefreshStarted del listener se ejecutará sobre la hebra principal así que lo mejor es que lancemos algún tipo de AsyncTask o Loader. Para parar la animación de actualización llamaremos al método setRefreshComplete de nuestro PullToRefreshLayout:
@Override
public void onRefreshStarted(View view) {
  new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
    //Aqui realizamos la actualización del contenido
      return null;
    }

    @Override
    protected void onPostExecute(Void result) {
      super.onPostExecute(result);
      //...
      pullLayout.setRefreshComplete();
    }
  }.execute();
}


Con esto ya tenemos completo un ejemplo sencillo de uso de esta biblioteca.


Personalización

Ya hemos visto lo fácil que es usar la biblioteca. Ahora veremos lo fácil que es cambiar el comportamiento de la barra de actualización.

En la biblioteca tenemos la clase Options. Durante la inicialización podemos darle una instancia de esta clase para indicarle distintos elementos de configuración como:
  • headerLayout: Layout (indicado por el identificador del recurso) que se usará para la barra de actualización.
  • headerTransformer: El HeaderTransformer que se encargará de cambiar la barra de actualización según su estado (lo veremos mas adelante).
  • headerInAnimation: Animación que se usará al mostrar la barra de actualización.
  • headerOutAnimation: Animación que se usará al esconder la barra de actualización.
  • refreshScrollDistance: Distancia de pulling necesaria para que se dispare la actualización. Se especifica en porcentaje (de 0.0 a 1.0) con respecto a la altura de la vista a la que se hará pull. Por ejemplo 0.5f significa que solo tenemos que recorrer la mitad de la altura para disparar la actualización.
  • refreshOnUp: Por defecto la actualización se dispara cuando se recorre la distancia de pulling. Si activamos este elemento el usuario tendrá que levantar el dedo después de recorrerla, lo que le da la posibilidad de "arrepentirse" y volver atrás.
  • refreshMinimize: Esconder la barra de actualización durante esta. Es útil cuando la actualización es muy larga y no queremos tapar indefinidamente el ActionBar.
  • refreshMinimizeDelay: Es el tiempo que se esperará antes de esconder la barra de actualización.

Para aplicar nuestras opciones tendremos que actualizar el código de inicialización que hemos visto antes:
@Override
protected void onCreate(Bundle savedInstanceState){
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  pullLayout = (PullToRefreshLayout) findViewById(R.id.my_ptr_layout);
  Options options = Options.create()
    .scrollDistance(.5f)
    .headerLayout(R.layout.customised_header)
    .headerTransformer(new CustomisedHeaderTransformer())
    .build();

  // Inicializamos el PullToRefreshLayout
  ActionBarPullToRefresh.from(this)
    // Le indicamos el objeto options a usar
    .options(options)
    .allChildrenArePullable()
    .listener(onRefreshListener)
    .setup(mPullToRefreshLayout);
  //...
}


La instancia de la clase CustomizedHeaderTransformer es la encargada de actualizar la barra según el estado de la actualización. Hereda de la clase abstracta HeaderTransformer y debe implementar los siguientes métodos:
  • boolean showHeaderView(): Se llama cuando la barra debe mostrarse.
  • boolean hideHeaderView(): Se llama cuando la barra debe esconderse.

Además de esos dos métodos se pueden implementar los siguientes:
  • onViewCreated(Activity activity, View headerView): Se llama cuando se ha creado el View de la barra de actualización.
    • activity: Activity a la que esta asociada la barra.
    • headerView: Barra de actualización creada.
  • onReset(): Se llama cuando la barra tiene que ser reiniciada.
  • onPulled(float perc): Se llama cuando el usuario realiza la acción de pulling.
    • perc: Porcentaje de distancia de pulling.
  • onRefreshStarted(): Se llama cuando se dispara la actualización.
  • onReleaseToRefresh(): Se llama cuando el usuario ha recorrido toda la distancia de pulling pero aún necesita levantar el dedo.
  • onRefreshMinimized(): Se llama cuando la actualización está llevando más tiempo que el especificado en refreshMinimizeDelay.
  • onConfigurationChanged(Activity activity, Configuration conf): Se llama cuando la configuración de la Activity a la que está asociada la barra cambia.
    • activity: Activity a la que esta asociada la barra.
    • conf: Nueva configuración.

Como hemos visto usar esta biblioteca es muy sencillo y nos da una gran libertad a la hora de personalizar el comportamiento de la barra de actualización. Con esto podremos implantar el patrón Pull to Refresh a nuestras aplicaciones.