viernes, 26 de abril de 2013

Ejemplo con View Pager super Extendido (Animación en Pager)

Hola que tal, en esta ocasión les quiero compartir otro ejemplo con ViewPager. Voy a extender un poco mas los ejemplos mostrados anteriormente en otros posts para actualizar y explicar un poco mas acerca del funcionamiento del ViewPager y los Fragmentos. Gracias a todos aquellos que escribieron comentarios y preguntas por tomarse el tiempo de leer el blog y practicar con estos sencillos ejemplos.

Aun y cuando los Fragmentos fueron introducidos a Android desde API 11 (Android 3.0 HoneyComb), han evolucionado y cambiado para hacerse mas dinámicos y permitir mucha mas flexibilidad en la interacción con el usuario. Un fragmento es básicamente una mini ejecución  que toma parte de la pantalla de nuestra actividad y cuenta con su propio ciclo de vida, eventos, etc. Inicio con una pequeña explicación sobre fragmentos por que si recuerdan en ejemplos anteriores comenzamos creando un fragmento el cual sera un poco distinto para cada una de las paginas de nuestro ViewPager. El código que prepare es el siguiente:


public class PlainColorFragment extends Fragment {
  private static final String PLAIN_COLOR_FRAG_ARG_COLOR = "color";
  int color = Color.GREEN;
  View view = null;
  AnalogClock clock = null;
  public static PlainColorFragment newInstance(int color) {
    PlainColorFragment frag = new PlainColorFragment();
    Bundle frag1Args = new Bundle();
    frag1Args.putInt(PLAIN_COLOR_FRAG_ARG_COLOR, color);
    frag.setArguments(frag1Args);
    frag.setRetainInstance(true);
    return frag;
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.color = (getArguments() != null) ? getArguments().getInt(PLAIN_COLOR_FRAG_ARG_COLOR) : Color.GRAY;
  }
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    view = inflater.inflate(R.layout.color_layout, container, false);
    clock = (AnalogClock) view.findViewById(R.id.analogClock1);
    clock.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        vanishClock();
      }
    });
    view.setBackgroundColor(color);
    return view;
  }
  public void vanishClock() {
    if (clock.getAlpha() < 0.4f)
      clock.setAlpha(1.0f);
    else
      clock.setAlpha(clock.getAlpha() - .2f);
  }
}

A partir de Android 4.0 y la revisión de la librería de soporte 12. Es necesario que nuestros fragmentos tengan un constructor por defecto (sin parámetros) para que puedan ser destruidos y reconstruidos por el FragmentManager. Por lo tanto en nuestro código creamos un método estático (newInstance) que nos ayudara a crear instancias de nuestro fragmento basado en el color que pasemos como parámetro. Y ya que cuando un fragmento es creado su método onCreate es ejecutado podremos retraer el color de nuestro fragmento por el método getArguments(). Pueden notar que en nuestro método newInstance asignamos estos parámetros a nuestra nueva instancia; el FragmentManager conservara estos parámetros por nosotros y si es necesario destruir nuestro fragmento por baja memoria o algún otro factor, los parámetros serán guardados para estar disponibles cuando nuestro fragmento se reconstruya. En el código también se puede observar que en el método onCreateView; ademas de asignar nuestro color de fondo; inflamos un layout contenido en una definición XML el cual le dará cuerpo a nuestro fragmento. Es importante notar que el metodo onCreateView es ejecutado no solo una vez si no varias veces, en ocasiones es necesario por cuestiones de recurso destruir vistas y reconstruirlas mas tarde cuando estos recursos esten disponibles; pero eso lo explicare mas delante. Ya con nuestra vista extendida, lo que hacemos es asignar nuestro identificador clock al componente reloj que se encuentra dentro de nuestro xml y asignarle un pequeño método (vanishClock) a su evento click. Esto ira desvaneciendo el reloj por cada clic que el usuario haga sobre el. Aquí abajo les dejo la definicion XML de la vista que inflamos para nuestro fragmento.



Muy bien ahora iremos con nuestra actividad principal, el codigo se ve como el que sigue:
Lo que podemos ver aquí es que tenemos dos propiedades, nuestro ViewPager (pager) y nuestro PageChangeListener (pageChangeListener). View pager es el encargado de cambiar de pagina cada vez que el usuario deslice el dedo en nuestra actividad y la clase PageChangeListener, la utilizaremos para algunas operaciones auxiliares las cuales explicare un poco mas adelante. Lo importante viene en el método onCreate de nuestra actividad; primero establecemos nuestra vista (R.layout.main), la cual contiene en su definición un ViewPager con el id pager. La definicion de este contenido lo pondre un poco mas abajo. Luego creamos un objeto MyFragmentPagerAdapter (una clase definida por nosotros) y agregamos 3 fragmentos de creamos manualmente también. Noten que para nuestros fragmentos mandamos llamar al método estático que preparamos (newInstance) con 3 distintos colores como parámetros. Luego asignamos a nuestro ViewPager el pageChangeListener, el adaptador que acabamos de preparar y un nuevo transformador que también explicare un poco mas tarde. Y eso es todo por el momento para nuestra actividad principal. Ahora les dejo el layout que asignamos para esta actividad
Como pueden observar no es algo muy complejo solo un Text View para mantener un titulo en nuestra actividad en todo momento y nuestra clase importante, nuestro ViewPager. Pero como vimos en el código nuestro ViewPager solo es asignado con varios recursos, en realidad el ViewPager es el conector entre nuestros distintas clases las cuales en realidad son las que hacen el trabajo pesado. Arriba pudimos ver que tenemos una clase llamada MyFragmentPageAdapter y a la cual se agregamos nuestros 3 fragmentos que creamos con el método addFragment; por lo tanto vamos a revisar esta clase.
Esta clase extiende de la clase abstracta FragmentPageAdapter, la cual esta cargo de manejar los fragmentos que estarán disponibles para nuestro ViewPager. Cada vez que el usuario cambie de pagina deslizando el dedo el ViewPager solicitara al adaptador que le regrese el fragmento correspondiente a la pagina que el usuario esta cambiando hasta que lleguemos a nuestro getCount(), cuando ya no haya mas paginas disponibles el ViewPager no permitirá al usuario seguir cambiando de pagina hacia adelante. Como ven la implementacion es sencilla. Solo tenemos una lista de fragmentos y cada vez que vayamos agregando un nuevo fragmento al adaptador este se agregara a nuestra lista. Cuando el Pager solicite una pagina en especifico con el método getItem, nosotros regresaremos el fragmento correspondiente de la lista y el getCount sera igual al tamaño de nuestra lista.

Luego tenemos nuestro PageChangeListener
Esta clase es muy sencilla, cuando un evento de cambio de pagina suceda en nuestro ViewPager, esta clase sera llamada y en la cual podemos interceptar información valiosa. En nuestro caso solo tenemos una propiedad currentIndex, la cual sera asignada cada vez que una pagina sea seleccionada (onPageSelected) y la cual expondremos por medio de un método publico getCurrentIndex. La finalidad de esta clase la podrán ver mas delante. Lo ultimo que asignamos a nuestro ViewPager es un nuevo objeto PageTransformer con el método setPageTransformer. Esta clase lo que hará sera dar una buena animación al cambio de pagina. Una nota personal aquí es que yo no soy muy bueno para diseñar animaciones y esta clase la tome de uno de los ejemplos de Google solo la modifique un poco así que no pondré el código, este lo pueden obtener en mi github junto con el código fuente.  Así que con esto ya tenemos un muy buen ejemplo casi igual al que teníamos anteriormente, pueden ejecutar la aplicación y podrán ver como al deslizar de pagina los distintos fragmentos de colores van apareciendo y al dar clic en el reloj este se va desvaneciendo poco a poco.




Pero les daré una pista, ya habíamos dicho que el método onCreateView de los fragmentos es llamado en varias ocasiones. Así que que es lo que tal vez ya habrán notado que si desvanecen el reloj hasta cierto punto y cambian de paginas varias veces, el reloj que anteriormente estaba desvanecido ha vuelto a la normalidad. Esto sucede ya que cuando cambiamos de pagina el sistema reclama la memoria de las vistas que están ocultas y estas son destruidas. El cuando esto sucederá depende del sistema, de los recursos disponibles, etc., tal vez en ciertos dispositivos poderosos como el S4 esto no pase, pero es mejor estar precavidos y dar a nuestros usuarios la mejor experiencia. Así que lo que haremos sera sobre escribir el método onViewDestroyed para salvar el estado de nuestro reloj y así poder re-establecerlo al momento de que esta vista sea creada de nuevo. Lo que haremos sera agregar un poco de código a nuestro fragmento como el que sigue.

Lo que hicimos es crear dos propiedades un Bundle (viewRecreateState) que sera donde guardaremos nuestro estado. Recuerden que lo que es destruido es nuestra vista pero las propiedades del fragmento se quedan intactas hasta que el fragmento sea destruido también si es que esto ocurre. Entonces crearemos una nombre estático el cual utilizaremos como indice en nuestro bundle y el método restoreClockState que sera en cargado de establecer el alpha del reloj si es que tenemos este valor guardado en nuestras preferencias. Creo que el método onDestroyView es muy fácil de entender, solo guardaremos el alpha de nuestro reloj antes de que la vista sea destruida. Por lo tanto lo único que tenemos que hacer ahora es llamar a nuestro método restoreClockState dentro de onCreateView para que si la vista fue destruida anteriormente esta vuelva al estado anterior cuando es recreada. Ahora nuestro método se vera así:

Y ya para terminar agregaremos los métodos necesarios para seguir el mismo funcionamiento de el articulo anterior. Esto es crear un menu para lanzar una animación en la pagina actual cuando el usuario haga clic en el item. Esto muestra de manera sencilla como interactuar con los fragmentos desde un agente externo. Recuerdan que teníamos un listener (PageChangeListener) en nuestro pager que guardaba la pagina actual? Y agregaremos a cada fragmento tiene un método publico startAnimationClock que animara el reloj y el cual sera llamado para animar el reloj interno. El codigo se ve asi.

Y en nuestra actividad principal agregamos:


Así que al final tendremos nuestra aplicación terminada y sin importar si cambiamos de pagina mil veces, mientras nuestro fragmento este en el sistema nuestra aplicación siempre presentaran una interfaz consistente a nuestros usuarios. Bueno como siempre, el código completo de este ejemplo lo pueden encontrar en mi perfil de github en esta tag del repositorio. Y ya saben si tienen alguna duda, sugerencia y/o comentario no duden en comentar o escribir un correo. Una disculpa por el post tan largo pero trate de explicarme lo mejor posible.
Saludos a todos.


 

No hay comentarios:

Publicar un comentario