Fuente: http://www.maestrosdelweb.com Autor: Adrian Catalán
El lector de feed que desarrollaremos en este capítulo se diferencia del capítulo anterior porque permitiremos al usuario decidir si al presionar sobre un elemento quiere verlo en una vista previa dentro de la aplicación o en el navegador, para esta elección agregaremos un menú y personalizaremos el View de cada fila del resultado.
El ejemplo se verá de la siguiente manera:
Empecemos descargando el código que debe ser importado hacia un nuevo proyecto es muy similar al del capítulo anterior, pero revisemos las diferencias:
src/com/android/mdw/demo/Element.java)@android:id/listAdemás, tiene las especificaciones de la guía anterior.
@android:id/list y tener una forma de reconocer XML.Trabajamos con Views del capítulo anterior como: LinearLayout, ListView, TextView, Button y agregamos nuevas Views.
Para el layout principal, repetimos el procedimiento del capítulo anterior de nuevo recordando que el identificador (android:id) del ListView debe ser @android:id/list.
Además, necesitaremos 3 Views adicionales:
menu.xmlshowelement.xmlrow.xmlPara agregar estos Views, hacemos click derecho sobre la carpeta /res/layout luego New > (nuevo) y other… (otro):
En el diálogo que aparece seleccionamos archivo XML de Android y hacemos click en Next > (siguiente):
Escribimos el nombre del archivo (es importante incluir la extensión XML), en el ejemplo el nombre es menu.xml y hacemos click en el botón Finish (finalizar):
Este procedimiento es necesario cada vez que necesitemos un Layout nuevo, para esta aplicación serán necesarios 3: menu.xml, row.xml y showelement.xml describiremos a detalle cada uno de ellos.
View para el menú: menu.xml
Utiliza un elemento menú y dos elementos ítem para cada opción, le pondremos a cada ítem texto y el icono que trae por defecto la aplicación, el archivo XML debe quedar así:
El menú debería verse de la siguiente forma:

View para preview: showelement.xml
En esta vista previa mostraremos el título del artículo, el autor y la descripción, necesitamos varios TextView dentro de un LinearLayout con orientación vertical, el archivo XML debe quedar así:
La vista previa debería verse de la siguiente forma:
View para fila: row.xml
En este capítulo en cada una de las filas de la lista no mostraremos dos líneas como en el anterior, ahora vamos a tener una imagen del elemento y su título agrupados por un LinearLayout con orientación horizontal, el archivo XML debe quedar de la siguiente forma:
En esta versión del lector de feeds tenemos varias clases que apoyan para facilitar el desarrollo:
Para la vista previa del artículo necesitamos otra Activity que es otra clase que llamaremos ShowElement.java para agregar una clase es necesario hacer click derecho sobre com.android.mdw.demo (bajo src) luego New (nuevo) y por último Class (clase).
Después colocamos el nombre “MyApp” para el ejemplo y la superclase (clase de la que hereda) android.app.Application para el ejemplo y hacemos click en finalizar.
El proceso es necesario para MyApp, MyAdapter y ShowElement la clase Element va incluida, ahora veamos el contenido de cada una de estas clases:
Element
Haremos un objeto Element, para aumentar el orden porque ahora guardaremos más información de cada elemento del listado obtenido del feed. Esta clase está incluida en los archivos de prueba y no es muy complicada, maneja métodos para colocar y obtener los atributos de cada elemento como: Título, Enlace, Autor, Descripción, Fecha de publicación, Imagen.
SetImage es el método diferente y se debe a la forma en que obtenemos las imágenes. El parser XML buscará dentro del contenido del artículo la primera ocurrencia de la etiqueta img de HTML y en algunos casos la única es la foto del autor del artículo. En ese caso, tomamos prestada la imagen del avatar de Twitter de @maestros el URL es:
http://a1.twimg.com/profile_images/82885809/mdw_hr_reasonably_small.png
La URL se recibe como parámetro en este método, si al tratar de obtener la imagen algo fallara entonces también se establece la imagen del avatar de twitter. Para obtener la imagen en base a un URL se utiliza la ayuda de la función loadFromUrl que devuelve un Bitmap.
Este método abre una conexión hacia el URL especificado, luego decodifica el flujo (stream) de bytes recibido y en base a ellos construye un objeto Bitmap. El código de la clase Element (ya incluido en el código base de la guía) es el siguiente:
package com.android.mdw.demo;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class Element {
static SimpleDateFormat FORMATTER = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
private String title;
private String author;
private String link;
private Bitmap image;
private String description;
private Date date;
public String getTitle() {
return this.title;
}
public String getLink() {
return this.link;
}
public String getDescription() {
return this.description;
}
public String getDate() {
return FORMATTER.format(this.date);
}
public Bitmap getImage(){
return this.image;
}
public String getAuthor(){
return this.author;
}
public void setTitle(String title) {
this.title = title.trim();
}
public void setLink(String link) {
this.link = link;
}
public void setDescription(String description) {
this.description = description.trim();
}
public void setDate(String date) {
try {
this.date = FORMATTER.parse(date);
} catch (java.text.ParseException e) {
e.printStackTrace();
}
}
public void setImage(String image){
if (image.contains("autor")) {
image = "http://a1.twimg.com/profile_images/82885809/mdw_hr_reasonably_small.png";
}
try {
this.image = loadFromUrl(new URL(image));
} catch (Exception e) {
try {
this.image = loadFromUrl(new URL("http://a1.twimg.com/profile_images/82885809/mdw_hr_reasonably_small.png"));
} catch (MalformedURLException e1) {}
}
}
public void setAuthor(String author){
this.author = author;
}
public String toString(){
return this.title;
}
private Bitmap loadFromUrl(URL link) {
Bitmap bitmap = null;
InputStream in = null;
try {
in = link.openConnection().getInputStream();
bitmap = BitmapFactory.decodeStream(in, null, null);
in.close();
} catch (IOException e) {}
return bitmap;
}
}
MyApp
En el capítulo anterior el listado de artículo los representamos como una lista estática dentro de la clase de la activity principal, mencioné que esa no era la forma correcta por que debían utilizarse clases de aplicación y para eso utilizaremos MyApp.
No es posible utilizar una variable local o una variable de instancia porque la Activity es constantemente destruida y creada de nuevo. Por ejemplo, al rotar el teléfono. A pesar de estar representado dentro de una clase esta información no deja de ser volátil, para almacenamiento permanente es necesario una base de datos de SQLite.
Para decirle a la aplicación que es de tipo MyApp es necesario editar el manifest AndroidManifest.xml y en los atributos de la etiqueta aplicación agregar android:name="MyApp"
Inicialmente decía:
Al modificarlo debe decir:
Dentro de la clase MyApp vamos a guardar dos cosas:
Además de estas dos variables de instancia, vamos a incluir métodos para guardar y devolver estas variables (getters y setters). Para representar la opción elegida por el usuario utilizaremos enteros dentro de la Activity principal, estos están incluidos en el código base y fueron definidos así:
final static int APP_VIEW = 1; final static int BROWSER_VIEW = 2;
Al iniciar la aplicación, colocaremos el valor de APP_VIEW en el campo que guarda la preferencia del usuario dentro de la clase de aplicación, el código de la clase MyApp queda de la siguiente forma:
package com.android.mdw.demo;
import java.util.LinkedList;
import android.app.Application;
public class MyApp extends Application {
private LinkedList data = null;
private int selectedOption = Main.APP_VIEW;
public LinkedList getData(){
return this.data;
}
public void setData(LinkedList d){
this.data = d;
}
public int getSelectedOption(){
return this.selectedOption;
}
public void setSelectedOption(int selectedOption) {
this.selectedOption = selectedOption;
}
}
MyAdapater
La clase MyAdapter será una personalización de un ArrayAdapter, un adaptador que recibe un arreglo (o listado) de elementos. La clase MyAdapter hereda de ArrayAdapter
Dentro del constructor, llamamos al padre (super) y asignamos a los campos (variables de instancia). Además, dado que queremos personalizar la representación (rendering) de cada fila, necesitamos sobrecargar revisar (Override) el método getView.
Dentro de este método, cuando sea necesario instanciamos el archivo de su diseño correspondiente row.xml y le asignamos valor a sus Views (imagen y texto) a partir de la información guardada en el listado de artículos.
El código de la clase MyAdapter es el siguiente:
package com.android.mdw.demo;
import java.util.LinkedList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends ArrayAdapter {
LayoutInflater inf;
LinkedList objects;
public MyAdapter(Context context, int resource,
int textViewResourceId,
LinkedList objects) {
super(context, resource, textViewResourceId, objects);
this.inf = LayoutInflater.from(context);
this.objects = objects;
}
public View getView(int position, View convertView,
ViewGroup parent){
View row = convertView;
Element currentElement = (Element)objects.get(position);
if (row == null) {
row = inf.inflate(R.layout.row, null);
}
ImageView iv = (ImageView) row.findViewById(R.id.imgElement);
iv.setImageBitmap(currentElement.getImage());
iv.setScaleType(ImageView.ScaleType.FIT_XY);
TextView tv = (TextView) row.findViewById(R.id.txtElement);
tv.setText(currentElement.getTitle());
return row;
}
}
ShowElement
Por último tenemos la Activity que nos mostrará la vista previa del artículo seleccionado, cada vez que se agrega un Activity es necesario especificarlo en el manifest AndroidManifest.xml, bajo la etiqueta de aplicación (
"
Para pasar la información entre las Activities utilizaremos la información extra puede llevar un intent previo a levantar una Activity. Disponemos de un diccionario que en nuestro caso utilizaremos para mandar un entero representando la posición del elemento que queremos visualizar.
Para empezar obtenemos elintent que levanto esta activity que debería traer la posición del elemento a visualizar, si en caso fuera nulo (el intent que levanto esta aplicación no manda nada) entonces se le asigna el valor por defecto (esperamos que la posición sea mayor o igual que 0, entonces para distinguir un error podemos asignar -1).
Intent it = getIntent(); int position = it.getIntExtra(Main.POSITION_KEY, -1);
Con la posición recibida, buscamos a través de la clase de aplicación MyApp y llamando al método getData obtenemos el artículo que nos interesa:
MyApp appState = ((MyApp)getApplication()); Element e = appState.getData().get(position);
Luego colocamos esos valores en los campos correspondientes definidos en su diseño showelement.xml si en caso no se recibiera una posición válida, se regresa a la Activity principal enviando la posición -1 para indicar que hubo un error:
Intent backToMainActivity = new Intent(this, Main.class); backToMainActivity.putExtra(Main.POSITION_KEY, -1); startActivity(backToMainActivity);
Al finalizar, el código de esta clase es el siguiente:
package com.android.mdw.demo;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
public class ShowElement extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.showelement);
Intent it = getIntent();
int position = it.getIntExtra(Main.POSITION_KEY, -1);
if (position != -1) {
MyApp appState = ((MyApp)getApplication());
Element e = appState.getData().get(position);
TextView txtTitle = (TextView)findViewById(R.id.txtTitle);
txtTitle.setText(e.getTitle());
TextView txtAuthor = (TextView)findViewById(R.id.txtAuthor);
txtAuthor.setText("por " + e.getAuthor());
TextView txtDesc = (TextView)findViewById(R.id.txtDesc);
txtDesc.setText(e.getDescription());
} else {
Intent backToMainActivity = new Intent(this, Main.class);
backToMainActivity.putExtra(Main.POSITION_KEY, -1);
startActivity(backToMainActivity);
}
}
}
Esas son las clases adicionales luego el trabajo es sobre la Activity principal: la clase Main.java
La función auxiliar setDatade la guía anterior se ve drásticamente reducida ya que ahora utilizamos nuestro adaptador y por tener una activity que hereda de ListActivity la asignación al ListView lo hacemos con setListAdapter.
private void setData(){
this.setListAdapter(new MyAdapter(this, R.layout.row, 0, appState.getData()));
}
Esta herencia de nuestra Activity, también nos permite colocar la función onListItemClick para especificar la operación a realizar cuando el usuario presiona click sobre un elemento. Validamos la opción seleccionada por el usuario guardada en la clase de aplicación y dependiendo del caso levantamos un intent diferente.
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent nextActivity = null;
if (appState.getSelectedOption() == APP_VIEW) {
nextActivity = new Intent(this, ShowElement.class);
nextActivity.putExtra(POSITION_KEY, position);
} else {
LinkedList data = appState.getData();
nextActivity = new Intent(Intent.ACTION_VIEW,
Uri.parse(data.get(position).getLink()));
}
this.startActivity(nextActivity);
}
Permanece la variable de instancia para el diálogo de progreso y el manejador es ligeramente distinto porque ya no recibe los datos a través del mensaje.
private final Handler progressHandler = new Handler() {
public void handleMessage(Message msg) {
setData();
progressDialog.dismiss();
}
};
La carga de datos no cambia mucho, seguimos teniendo el diálogo de progreso pero ahora ya no necesitamos mandar los datos reconocidos por el parser a través del manejador si no puedo guardarlos en la clase de aplicación directamente y mando un mensaje vacío.
private void loadData() {
progressDialog = ProgressDialog.show(
Main.this,
"",
"Por favor espere mientras se cargan los datos...",
true);
new Thread(new Runnable(){
@Override
public void run() {
XMLParser parser = new XMLParser(feedUrl);
appState.setData(parser.parse());
progressHandler.sendEmptyMessage(0);
}}).start();
}
Necesitamos indicarle qué hacer cuando el usuario presione la tecla de menú en el teléfono, como en este caso construimos el menú en un XML solo es necesario crear una instancia.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.layout.menu, menu);
return true;
}
Además, requerimos sobrecargar otra función que se dispara cuando el usuario elige alguna de las opciones del menú. Aquí guardaremos en la clase de aplicación lo que sea que el usuario haya elegido.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mmElementApp:
appState.setSelectedOption(APP_VIEW);
break;
case R.id.mmElementBrw:
appState.setSelectedOption(BROWSER_VIEW);
break;
}
return true;
}
Dentro del cuerpo de la function oCreate inicializamos la variable para nuestra clase de aplicación:
appState = ((MyApp)getApplication());
Validamos si el intent lo levantó alguna otra Activity y si viene un -1 en el mensaje mostramos un error:
Intent it = getIntent();
int fromShowElement = it.getIntExtra(POSITION_KEY, 0);
if (fromShowElement == -1) {
Toast.makeText(this, "Error, imposible visualizar el elemento", Toast.LENGTH_LONG);
}
La acción sobre el click del botón no cambia mucho, la verificación la hemos cambiado y en lugar de ver si el adaptar ya tiene datos revisamos si la clase de aplicación devuelve algo diferente de null:
LinkedList data = appState.getData();
if (data != null) { … }
Puedes descargar el código de la aplicación completa y funcional en: UI en Android y aumentar la funcionalidad de un lector de feeds.
Comparte:
Tu Comentario| Nombre | |
| Localidad | |
| País | |
| Comentario |
|
Divum.es | Licencia de Uso | Enlaces de Interés

Esta obra está bajo una licencia de Creative Commons.