18/07/2013
En el tip anterior vimos cómo utilizar el widget Gallery para mostrar una galería de imágenes «scrollable» en sentido horizontal. En esta ocasión vamos a hacer la galería con el widget GridView que permite mostrar una tabla scrollable en formato vertical. Tendremos que volver a implementar un Adapter, aunque en esta ocasión será equivalente a las implementaciones que hacemos para los ListView.
El proyecto de ejemplo consistirá en una activity que mostrará en pantalla un GridView con algunos fondos de pantallas de Ubuntu y un texto asociado. El layout es el siguiente.
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" > <GridView android:id="@+id/gridView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:numColumns="2" android:stretchMode="columnWidth" android:verticalSpacing="10dp" android:horizontalSpacing="5dp"> </GridView> </merge>
Algunas propiedades interesantes:
- numColumns: número de columnas a mostrar, puede ser un valor fijo o la constante auto_fit que intentará mostrará el máximo número de columnas posible. Como luego veremos, no se usará el valor de esta propiedad sino que el número de columnas se establecerá programáticamente según el dispositivo.
- stretchMode: ajuste del espacio libre. Usaremos columnWidth para que sea repartido proporcionalmente entre las columnas o spacingWidth para repartirlo entre el espaciado de las mismas
- verticalSpacing,horizontalSpacing: espaciado entre celdas.
El contenido de la celda es el siguiente layout con la imagen y su texto asociado.
<?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:gravity="center"> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
En la Activity hay que establecer el Adapter del GridView. Asimismo, para nuestro ejemplo vamos a mostrar un Toast cada vez que se seleccione una imagen. Se va a calcular programáticamente el número de columnas a mostrar en función de la orientación del dispositivo y de su densidad de pantalla, distinguiendo entre teléfonos y tablets. Esto también se podría hacer definiendo múltiples layouts, o incluso una variable en unos ficheros integers.xml con el número de columnas, ubicándolos en los directorios adecuados con sus correspondientes cualificadores (res/values/integers.xml, res/values-sw600dp-land/integers.xml, res/values-sw600dp-port/integers.xml), pero para este caso creo que es más sencillo hacerlo así.
package com.danielme.tipsandroid.gridview; import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.danieme.tipsandroid.gridview.R; /** * * @author danielme.com * */ public class MainActivity extends Activity { ImageView imagenSeleccionada; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Integer[] imagenes = { R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,R.drawable.f,R.drawable.g,R.drawable.h, R.drawable.i,R.drawable.j,R.drawable.k,R.drawable.l,R.drawable.m,R.drawable.n,R.drawable.o}; GridView gridView = (GridView) findViewById(R.id.gridView); //el número de columnas se calculará en función del tamaño de pantalla y la posición boolean bigScreen = (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { if (bigScreen) { gridView.setNumColumns(4); } else { gridView.setNumColumns(3); } } else { if (bigScreen) { gridView.setNumColumns(3); } else { gridView.setNumColumns(2); } } gridView.setAdapter(new GalleryAdapter(this, imagenes)); gridView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view,int position, long id) { Toast.makeText(MainActivity.this, ((TextView) view.findViewById(R.id.textView1)).getText() + " seleccionada", Toast.LENGTH_SHORT).show(); } }); } }
El Adapter como ya se ha comentado es equivalente al que se suele implementar para un ListView siguiendo el ViewHolderPattern. La única particularidad de este caso es que, tal y como vimos en el tip anterior, debido a la alta resolución de las imágenes es imprescindible reescalarlas y, por eficiencia, vamos a guardar las imágenes reescaladas en memoría (hay que tener precaución con esto). Para más detalles, ver el tip anterior.
package com.danielme.tipsandroid.gridview; import com.danieme.tipsandroid.gridview.R; import android.content.Context; import android.graphics.Bitmap; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class GalleryAdapter extends BaseAdapter { Context context; Integer[] imagenes; //guardamos las imágenes reescaladas para mejorar el rendimiento ya que estas operaciones son costosas //se usa SparseArray siguiendo la recomendación de Android Lint static SparseArray<Bitmap> imagenesEscaladas = new SparseArray<Bitmap>(7); public GalleryAdapter(Context context, Integer[] imagenes) { super(); this.imagenes = imagenes; this.context = context; } @Override public int getCount() { return imagenes.length; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder = null; if (convertView == null) { holder = new Holder(); LayoutInflater ltInflate = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE ); convertView = ltInflate.inflate(R.layout.gallery_item, null); holder.setTextView((TextView) convertView.findViewById(R.id.textView1)); holder.setImage((ImageView) convertView.findViewById(R.id.imageView1)); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); } if (imagenesEscaladas.get(position) == null) { Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromResource(context.getResources(), imagenes[position], 100, 0); imagenesEscaladas.put(position, bitmap); } holder.getImage().setImageBitmap(imagenesEscaladas.get(position)); holder.getTextView().setText(position + ""); return convertView; } class Holder { ImageView image; TextView textView; public ImageView getImage() { return image; } public void setImage(ImageView image) { this.image = image; } public TextView getTextView() { return textView; } public void setTextView(TextView textView) { this.textView = textView; } } }
Con esto ya lo tenemos todo (los ficheros que he obviado están todos en el proyecto de ejemplo). El resultado final en JellyBean para una densidad xhdpi es el siguiente
En una Nexus 7 en formato horizontal veremos cuatro columnas tal y como cabría esperar:
El proyecto completo para Eclipse ADT se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Por favor seria tan amable de informarme si yo puedo convertir una imagen en un widget ya que tengo un huawei p6 y quiero poner una foto en la pantalla inicial donde se encuentras dos fotos ya preestablecidas. Gracias
muy buen tutorial, pero una consulta mas, como le hago para colocar en vez de eso imagenes alojadas en sqllite o un bd mysql? gracias
Muy buen tuto me ha sacado de un gran apuro tu proyecto. Una duda, como puedo hacer para que no me reescale las imagenes?