Drag And Drop de una listview

PantallaDragDrop001Tras ver cómo acceder a una base de datos desde Android, hoy voy a empezar a implementar la pantalla para procesar la bandeja de entrada.

La pantalla se dividirá en 4 partes.

  1. En la parte superior se mostrará el título de la pantalla.
  2. A la derecha se mostrará una lista con las posibles opciones que se tiene para procesar una cosa.
  3. A la izquierda se verá la lista de cosas que hay pendiente de procesar en la bandeja de entrada.
  4. En la parte inferior se muestra el elemento para introducir nuevas cosas a la bandeja de entrada.

Hoy veremos como crear un adapter para procesar la lista de la bandeja de entrada y como seleccionar un elemento y arrastrarlo a la opción que deseemos, acción que se conoce comúnmente como drag&drop.

No pretendo explicar la implementación de lo que sucederá al dejar un elemento de la lista en cada una de las opciones ni que pasa al insertar un nuevo elemento en la bandeja de entrada. Sólo me centraré en como mostrar la lista de la bandeja de entrada y como seleccionar cada elemento de la bandeja de entrada para dejarlo en la opción deseada.

Para empezar, sigo trabajando con la versión 0.8.4 de AndroidStudio, con la base de datos TareasDB que creamos en la entrada cómo acceder a base de datos desde aplicación Android,

Nos descargamos el icono ic_action_send_now.png en los distintos formatos, en mi caso descargue el fichero comprimido Android_Design_Icons_20131106.zip, que contiene varios iconos de la pagina de desarrollo de android y copiamos los distintos tamaños en los directorios correspondientes.

Seguiremos con los xml de los resources de la carpeta res/values:

Fichero de recursos para los colores colors.xml


<?xml version="1.0" encoding="utf-8"?>

<resources>

<color name="title_background">#ff355689</color>
<color name="title_text">#ffffffff</color>
<color name="title_separator">#40ffffff</color>
</resources>

Fichero de recursos para las dimensiones dimens.xml

<pre><resources>
 <!-- Default screen margins, per the Android Design guidelines. -->
 <dimen name="activity_horizontal_margin">16dp</dimen>
 <dimen name="activity_vertical_margin">16dp</dimen>
 <dimen name="title_height">45dip</dimen>
 <dimen name="text_size_small">14sp</dimen>
 <dimen name="text_size_medium">18sp</dimen>
</resources></pre>

Fichero de recursos para los textos strings.xml

</pre>
<pre><?xml version="1.0" encoding="utf-8"?>
<resources>
 <string name="app_name">GTDList</string>
 <string name="hello_world">Hello world!</string>
 <string name="action_settings">Settings</string>
 <string name="lista_vacia">No hay elementos en la lista.</string>
 <string name="tarea_vacia">La tarea debe informarse</string>
 <string name="inbox">Inbox</string>
 <string name="next_actions">Next actions</string>
 <string name="calendar">Calendar</string>
 <string name="doIt">Do</string>
 <string name="delegate">Delegate</string>
 <string name="trash">Trash</string>
 <string name="someday">Someday</string>
 <string name="reference">Reference</string>
</resources></pre>

Fichero de recursos para los estilos styles.xml

<pre><resources>

 <!-- Base application theme. -->
 <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
 <!-- Customize your theme here. -->
 </style>
 <style name="TitleBar">
 <item name="android:layout_width">fill_parent</item>
 <item name="android:layout_height">@dimen/title_height</item>
 <item name="android:orientation">horizontal</item>
 <item name="android:background">@color/title_background</item>
 </style>

 <style name="processButton">
 <item name="android:gravity">center|left</item>
 <item name="android:layout_height">30dp</item>
 <item name="android:layout_width">85dp</item>
 <item name="android:layout_weight">1</item>
 <item name="android:padding">5dp</item>
 </style>

</resources></pre>
<pre>

Luego se dibujan las pantallas. En este caso se tendrán dos pantallas una que contendrá toda la información relativa la pantalla para procesar la bandeja de entrada (main_tareas.xml) y una segunda que se encargará de gestionar el contenido de la lista (lista_inbox.xml).
En el fichero main_tareas.xml se definirán 3 layouts, uno para cada zona de la pantalla.
Un RelativeLayout que contiene la cabecera, que estará alineado en la cabecera “layout_alignParentTop=’true'”
Otro RelativeLayout que contendrá el EditText y el ImageButton para añadir elementos en la bandeja de entrada situado en el pie “layout_alignParentBottom=’true'”
El último layout servirá para tratar la parte central. Se tratará de un LinearLayout de orientación horizontal situado entre la cabecera y el pie “android:layout_above=’@id/footer’ y android:layout_below=’@id/header'”

Este layout tendrá dos partes una situada a la izquierda con todos las opciones posibles que se tienen al procesar la bandeja de entrada y otro situado a la derecha que contendrá un listview.

<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"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 android:paddingBottom="@dimen/activity_vertical_margin"
 tools:context=".MainTareasActivity">

 <!-- Header aligned to top -->
 <RelativeLayout
 android:id="@+id/header"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_alignParentTop="true"
 android:background="#FA5858"
 android:gravity="center" >

 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_margin="5dp"
 android:text="@string/inbox"
 android:textColor="#000"
 android:textSize="20sp" />

 </RelativeLayout>

 <!-- Footer aligned to bottom -->

 <RelativeLayout
 android:id="@+id/footer"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_alignParentBottom="true"
 android:background="#D8D8D8"
 android:gravity="center"
 >
 <LinearLayout
 android:layout_width="wrap_content"
 android:layout_height="wrap_content">

 <EditText android:id="@+id/textInbox"
 android:layout_width="300dp"
 android:layout_height="wrap_content"/>

 <ImageButton
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:id="@+id/btn_addInbox"
 android:layout_gravity="left|top"
 android:onClick="onRegisterInbox"
 android:src="@drawable/ic_action_send_now"/>
 </LinearLayout>
 </RelativeLayout>
 <LinearLayout
 android:orientation="horizontal"
 android:layout_height="fill_parent"
 android:layout_width="fill_parent"
 android:layout_above="@id/footer"
 android:layout_below="@id/header">

 <LinearLayout
 android:orientation="vertical"
 android:layout_height="fill_parent"
 android:layout_width="100dp"
 android:layout_gravity="left"
 >
 <TextView android:id="@+id/inbox"
 android:text="@string/inbox"
 style="@style/processButton"
 />

 <TextView android:id="@+id/nextAction"
 android:text="@string/next_actions"
 style="@style/processButton"
 />

 <TextView android:id="@+id/calendar"
 android:text="@string/calendar"
 style="@style/processButton"
 />

 <TextView android:id="@+id/doIt"
 android:text="@string/doIt"
 style="@style/processButton"
 />

 <TextView android:id="@+id/delegate"
 android:text="@string/delegate"
 style="@style/processButton"
 />

 <TextView android:id="@+id/someday"
 android:text="@string/someday"
 style="@style/processButton"
 />

 <TextView android:id="@+id/reference"
 android:text="@string/reference"
 style="@style/processButton"
 />

 <TextView android:id="@+id/trash"
 android:text="@string/trash"
 style="@style/processButton"
 />
 </LinearLayout>
 <LinearLayout
 android:id="@+id/listLayout"
 android:orientation="vertical"
 android:layout_height="fill_parent"
 android:layout_width="fill_parent"
 >
 <ListView
 android:id="@+id/list"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"/>
 </LinearLayout>
 </LinearLayout>
</RelativeLayout></pre>
<pre>
El segundo layout lista_inbox.xml tiene la estructura de datos a mostrar en cada elemento de la lista. En este caso se tratará sólo de un TextView.
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 android:padding="10dp"
 android:id="@+id/layoutlistaInbox">

 <TextView
 android:id="@+id/title"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:paddingLeft="25dp"/>
</LinearLayout>
Ahora que ya se tiene la estructura de los recursos necesarios para la aplicación veamos como se implementará el código java.
Se sigue utilizando la clase BaseDatosHelper.java que ya se definió en la entrada anterior. Es importante fijarse si se ha cambiado los paquetes de la aplicación para modificar la ruta de la base de datos
En este caso será la siguiente ruta:
</pre>
<pre>private static String DB_PATH = "/data/data/com.dcrespi.gtdlist/databases/";</pre>
<pre>

A parte de la clase de apoyo para acceder a la base de datos, también se debe contar con una clase para gestionar el contenido del ListView, en este caso la clase se llamará ListaAdapter.java. La creación del Adapter se debe a la complejidad del Activity. Si se contará con un activity que mostrará únicamente el contenido de una Array de Strings se podría hacer sin problemas haciendo extender la clase activity de ListActivity, pero al tener la lista de opciones y el pie de pantalla para introducir nuevas cosas a la bandeja de entrada no es posible.


package com.dcrespi.gtdlist.model;

/**
* Created by dcrespi on 11/09/2014.
*/
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.dcrespi.gtdlist.R;

import java.util.ArrayList;

public class ListaAdapter extends BaseAdapter {

private Activity activity;
private ArrayList<String> results;
private ListView listView;
private static LayoutInflater inflater=null;

public ListaAdapter(Activity a, ArrayList<String>info, ListView list) {
activity = a;
results = info;
listView = list;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public int getCount() {
return results.size();
}

public Object getItem(int position) {
return position;
}

public long getItemId(int position) {
return position;
}

public static class ViewHolder{
public TextView name;
}

public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder holder;
if(convertView==null){
vi = inflater.inflate(R.layout.lista_inbox, null);
holder=new ViewHolder();
holder.name=(TextView)vi.findViewById(R.id.title);
vi.setTag(holder);
}
else
holder=(ViewHolder)vi.getTag();

holder.name.setText(results.get(position));
return vi;
}
}

La clase MainTareasActivity.java contiene toda la lógica de la pantalla.
Hay una serie de métodos que se encargan de gestionar la acción seleccionada en la base de datos.

  • completarTarea
  • getIddb
  • deleteToInboxByName
  • updateFolder
  • getItems
  • onRegisterInbox
  • refrescarLista. En este método se utilizará la clase ListaAdapter para rellenar la widget ListView.

Se define la clase MyDragEventListener que estará escuchando las acciones relativas a las acciones de Drag&Drop.  Para el caso que nos ocupa se implementarán las acciones DragEvent.ACTION_DRAG_STARTEDDragEvent.ACTION_DROP.

Se define el método listSourceItemLongClickListener que servirá para prestar atención cuando se mantiene pulsado durante un tiempo el botón sobre un elemento. Es decir cuando se realiza el Drag. En el método se almacenará en el portapapeles el elemento seleccionado de la lista mediante el objeto ClipData. Importante tener en cuenta que al instanciar el objeto, normalmente se hace un charsequence de view.getTag(), pero esto no ha estado funcionando y finalmente he conseguido que funcione haciendo un view.getTag().toString(). No es muy elegante pero funciona.

Como siempre en el método onCreate inicializaremos todas las variables. Especialmente importante identificar los objetos que Listeners tendrán.

package com.dcrespi.gtdlist;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuItem;

import com.dcrespi.gtdlist.bbdd.BaseDatosHelper;
import com.dcrespi.gtdlist.model.ListaAdapter;
import com.dcrespi.gtdlist.model.Tarea;

import android.view.View;

import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;

import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainTareasActivity extends Activity {

 BaseDatosHelper miBBDDHelper;

 ArrayList<Tarea> tareas;

 ArrayList<String> listaInbox = new ArrayList<String>();

 TextView nextAction;
 TextView calendar;
 TextView doIt;
 TextView delegate;
 TextView someday;
 TextView reference;
 TextView trash;

 ListView listView;

 MyDragEventListener myDragEventListener = new MyDragEventListener();

 public void crearBBDD() {
 miBBDDHelper = new BaseDatosHelper(this);
 try {
 miBBDDHelper.crearDataBase();
 } catch (IOException ioe) {
 throw new Error("Unable to create database");
 }
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main_tareas);
 crearBBDD();

 nextAction = (TextView)findViewById(R.id.nextAction);
 calendar = (TextView)findViewById(R.id.calendar);
 doIt = (TextView)findViewById(R.id.doIt);
 delegate = (TextView)findViewById(R.id.delegate);
 someday = (TextView)findViewById(R.id.someday);
 reference = (TextView)findViewById(R.id.reference);
 trash = (TextView)findViewById(R.id.trash);

 this.refrescarLista();
 listView = (ListView) findViewById(R.id.list);

 listView.setOnItemLongClickListener(listSourceItemLongClickListener);

 nextAction.setOnDragListener(myDragEventListener);
 calendar.setOnDragListener(myDragEventListener);
 doIt.setOnDragListener(myDragEventListener);
 delegate.setOnDragListener(myDragEventListener);
 someday.setOnDragListener(myDragEventListener);
 reference.setOnDragListener(myDragEventListener);
 trash.setOnDragListener(myDragEventListener);

 }

 public void refrescarLista (){
 // Obtener la lista de tareas
 listaInbox = new ArrayList<String>();
 tareas = getItems();

 if (tareas.isEmpty()){
 listaInbox.add("Inbox empty");
 } else {
 for (Tarea tarea:tareas){
 listaInbox.add(tarea.getTitle());
 List<String> tareaList = new ArrayList<String>();
 tareaList.add(tarea.getIddb());
 }
 }
 listView = (ListView) findViewById(R.id.list);
 ListaAdapter adapter = new ListaAdapter (this, listaInbox, listView);
 listView.setAdapter(adapter);

 }

 public void onRegisterInbox(View v)
 {
 // TODO controlar que la tarea exista en el inbox
 TextView cajaTarea= (TextView) findViewById(R.id.textInbox);

 String tarea = cajaTarea.getText().toString();

 if (tarea.isEmpty()) {
 Toast toast1 =
 Toast.makeText(getApplicationContext(),
 R.string.tarea_vacia, Toast.LENGTH_SHORT);
 toast1.show();
 } else {
 // Abrimos una conexion
 miBBDDHelper.abrirBaseDatos();
 // Consultamos los datos
 boolean bienHecho = miBBDDHelper.InsertTarea("", tarea, "#inbox");

 if (bienHecho) {
 Toast toast1 =
 Toast.makeText(getApplicationContext(),
 "Success", Toast.LENGTH_SHORT);

 toast1.show();
 } else {
 Toast toast1 =
 Toast.makeText(getApplicationContext(),
 "Fail insert", Toast.LENGTH_SHORT);

 toast1.show();
 }
 // Cerramos la conexion
 miBBDDHelper.close();
 }
 this.refrescarLista();
 cajaTarea.setText("");
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.main_tareas, menu);
 return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 if (id == R.id.action_settings) {
 return true;
 }
 return super.onOptionsItemSelected(item);
 }

 /*
 * Obtiene una lista del inbox
 *
 * @returns ArrayList<Tarea> listaTareas
 */
 public ArrayList<Tarea> getItems() {
 //Abrimos una conexion
 miBBDDHelper.abrirBaseDatos();
 //Consultamos los datos
 ArrayList<Tarea> listaTareas = miBBDDHelper.getTareasByTag("#inbox");
 //Cerramos la conexion
 miBBDDHelper.close();
 //Devolvemos los datos
 return listaTareas;
 }

 /*
 * Modifica el buzón de la terea
 *
 * @returns ArrayList<Tarea> listaTareas
 */
 public boolean updateFolder(String tarea, String newFolder) {
 //Abrimos una conexion
 miBBDDHelper.abrirBaseDatos();
 //Consultamos los datos
 boolean result = miBBDDHelper.updateFolderByTitle(tarea, "#inbox", newFolder );
 //Cerramos la conexion
 miBBDDHelper.close();
 //Devolvemos el resultado
 return result;
 }

 public boolean deleteToInboxByName(String tarea) {
 //Abrimos una conexion
 miBBDDHelper.abrirBaseDatos();
 //Consultamos los datos

 boolean result = miBBDDHelper.deleteTarea(getIddb(tarea));
 //Cerramos la conexion
 miBBDDHelper.close();
 //Devolvemos el resultado
 return result;
 }

 private String getIddb(String text){
 String iddb="";
 for (Tarea tarea: tareas){
 if (tarea.getTitle().equals(text)){
 iddb = tarea.getIddb();
 break;
 }
 }
 return iddb;
 }

 private boolean completarTarea(String title){
 //Abrimos una conexion
 miBBDDHelper.abrirBaseDatos();
 //Consultamos los datos

 Tarea tarea = getTarea(title);
 boolean result = miBBDDHelper.taskUpdateTag(tarea.getIddb(), tarea.getEtag().replaceAll("#inbox", ""));
 if (result) {
 result = miBBDDHelper.taskCompleted(tarea.getIddb());
 }
 //Cerramos la conexion
 miBBDDHelper.close();
 //Devolvemos el resultado
 return result;
 }

 private Tarea getTarea(String text){
 Tarea tarea = new Tarea();
 for (Tarea auxTarea: tareas){
 if (auxTarea.getTitle().equals(text)){
 tarea = auxTarea;
 break;
 }
 }
 return tarea;
 }

 protected class MyDragEventListener implements View.OnDragListener {

 @Override
 public boolean onDrag(View v, DragEvent event) {
 final int action = event.getAction();

 switch(action) {
 case DragEvent.ACTION_DRAG_STARTED:
 //All involved view accept ACTION_DRAG_STARTED for MIMETYPE_TEXT_PLAIN
 if (event.getClipDescription()
 .hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN))
 {
 return true; //Accept
 }else{
 return false; //reject
 }
 case DragEvent.ACTION_DRAG_ENTERED:
 return true;
 case DragEvent.ACTION_DRAG_LOCATION:
 return true;
 case DragEvent.ACTION_DRAG_EXITED:
 return true;
 case DragEvent.ACTION_DROP:
 // Gets the item containing the dragged data
 ClipData.Item item = event.getClipData().getItemAt(0);
 //If apply only if drop on buttonTarget
 if(v == nextAction){
 String text = item.getText().toString();
 listaInbox.remove(text);
 boolean result = updateFolder(text, "#nextAction");
 refrescarLista();
 return true;
 } else if (v == calendar){
 listaInbox.remove(item.getText().toString());
 boolean result = updateFolder(item.getText().toString(), "#calendar");
 refrescarLista();
 return true;
 } else if (v == doIt){
 listaInbox.remove(item.getText().toString());
 boolean result = completarTarea(item.getText().toString());
 refrescarLista();
 return true;
 } else if (v == delegate) {
 listaInbox.remove(item.getText().toString());
 boolean result = updateFolder(item.getText().toString(), "#delegate");
 refrescarLista();
 return true;
 } else if (v == someday) {
 listaInbox.remove(item.getText().toString());
 boolean result = updateFolder(item.getText().toString(), "#someday");
 refrescarLista();
 return true;
 } else if (v == reference) {
 listaInbox.remove(item.getText().toString());
 boolean result = updateFolder(item.getText().toString(), "#reference");
 refrescarLista();
 return true;
 } else if (v == trash) {
 listaInbox.remove(item.getText().toString());
 boolean result = deleteToInboxByName(item.getText().toString());
 refrescarLista();
 return true;
 } else{
 return false;
 }

 case DragEvent.ACTION_DRAG_ENDED:
 if (event.getResult()){
 } else {
 };
 return true;
 default: //unknown case
 return false;

 }
 }
 }

 public int getDipsFromPixel(float pixels) {
 // Get the screen's density scale
 final float scale = getResources().getDisplayMetrics().density;
 // Convert the dps to pixels, based on density scale
 return (int) (pixels * scale + 0.5f);
 }

 OnItemLongClickListener listSourceItemLongClickListener
 = new OnItemLongClickListener(){

 @Override
 public boolean onItemLongClick(AdapterView<?> l, View v,
 int position, long id) {
 try {
 //Selected item is passed as item in dragData
 ClipData.Item item = new ClipData.Item(listaInbox.get(position));

 String[] clipDescription = {ClipDescription.MIMETYPE_TEXT_PLAIN};
 ClipData dragData = new ClipData(v.getTag().toString(),
 clipDescription,
 item);
 View.DragShadowBuilder myShadow = new MyDragShadowBuilder(v);

 v.startDrag(dragData, //ClipData
 myShadow, //View.DragShadowBuilder
 listaInbox.get(position), //Object myLocalState
 0); //flags

 } catch (Exception e) {
 e.printStackTrace();
 }
 return true;
 }};

 private static class MyDragShadowBuilder extends View.DragShadowBuilder {
 private static Drawable shadow;

 public MyDragShadowBuilder(View v) {
 super(v);
 shadow = new ColorDrawable(Color.LTGRAY);
 }

 @Override
 public void onProvideShadowMetrics (Point size, Point touch){
 int width = getView().getWidth();
 int height = getView().getHeight();

 shadow.setBounds(0, 0, width, height);
 size.set(width, height);
 touch.set(width / 2, height / 2);
 }

 @Override
 public void onDrawShadow(Canvas canvas) {
 shadow.draw(canvas);
 }

 }
}

En las siguientes imágenes se puede ver el comportamiento de la pantalla:

Se accede a la pantalla con cuatro elementos en la bandeja de entrada.

PantallaDragDrop002

Seleccionar el elemento Factura Luz y mientras se mantiene el botón pulsado, arrastrarlo hasta la opción DO, se aprecia en el cuadro grís (DRAG):

PantallaDragDrop004

Por último se deja de tener pulsado el botón (DROP):

PantallaDragDrop005

Espero que os sirva y os sea útil.

img_ccTodo el material publicado en este Blog, salvo las obras que no pertenecen a su autor, se difunden bajo licencia CC by-SA de Creative Commons, por lo que eres libre de copiar, distribuir y comunicar este contenido de forma publica, hacer un uso comercial del mismo, etc., siempre que lo hagas bajo las condiciones de la licencia indicada, y que reconozcas a su autor e indiques un enlace al contenido original o en su defecto a la pagina principal de este blog.

Anuncios
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: