Livro 4 - Projeto da Previsão do Tempo

6. Adicionando a Lógica da Aplicação

6.2. Classe WeatherArrayAdapter

A classe WeatherArrayAdapter usa herança da classe ArrayAdapter para vincular um ArrayList<Weather> ao elemento ListView da tela principal do aplicativo. Os itens da lista usam um layout personalizado para exibir os dados climáticos na tela como desenhado no arquivo weather_list_item.xml. Assim, devemos mapear os valores de cada objeto Weather aos componentes da interface gráfica que representam a imagem do ícone, o texto representando o dia, as temperaturas mínima e máxima, e a umidade. Esse mapeamento é programado sobrescrevendo o método getView de ArrayAdapter.

Adicione uma nova classe ao projeto, adiciona a herança da classe ArrayAdapter<Weather> e configure suas importações e variáveis como:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WeatherArrayAdapter extends ArrayAdapter<Weather> {
private static class ViewHolder {
ImageView conditionImageView;
TextView dayTextView;
TextView lowTextView;
TextView highTextView;
TextView humidityTextView;
}

private Map<String, Bitmap> bitmaps = new HashMap<>();

A classe interna ViewHolder define variáveis de instancia que a classe WeatherArrayAdapter acessa diretamente ao manipular objetos ViewHolder. Quando um item da lista for criado, vamos associar a ele um novo objeto ViewHolder. Se for possível reutilizar um item da lista, vamos obter o objeto ViewHolder vinculado esse item e atualizar seus campos.

A variável bitmaps será utilizada para armazenar os ícones baixados durante o uso do aplicativo. A cada ícone será assimilado um valor string para sua identificação (seu nome) e um valor bitmap para armazenar a imagem em si.

6.2.1 Construtor

O construtor da classe inicializa com a chamada do construtor da classe pai (método super).

public WeatherArrayAdapter(Context context, List<Weather> forecast) {
super(context, -1, forecast);
}

O primeiro argumento é contexto atual da aplicação, o segundo (-1) significa que o ArrayAdapter vai utilizar uma view personalizada e o terceiro representa a lista de dados a serem exibidos.

6.2.2 Método getView de ArrayAdapter

O método getView é chamado para obter a view que exibe os dados de um item de ListView. Sobrescrever esse método permite você a mapear dados para um item personalizado da lista. O método recebe como argumento a posição do item, a view que representa o item e o pai do item. Manipulando convertView, você pode personalizar o conteúdo do item na lista.

public View getView(int position, View convertView, ViewGroup parent) {
// obtém objeto Weather para esta posição de ListView especificada
Weather day = getItem(position);
// objeto que referencia as views do item da lista
ViewHolder viewHolder;
// verifica se há ViewHolder reutilizável de um item
// de ListView que rolou para fora da tela
// caso contrário, cria um novo ViewHolder
if (convertView == null) {
// não pode reciclar, então cria um novo
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.weather_list_item, parent, false);
viewHolder.conditionImageView = (ImageView) convertView.findViewById(R.id.conditionImageView);
viewHolder.dayTextView = (TextView) convertView.findViewById(R.id.dayTextView);
viewHolder.lowTextView = (TextView) convertView.findViewById(R.id.lowTextView);
viewHolder.highTextView = (TextView) convertView.findViewById(R.id.highTextView);
viewHolder.humidityTextView = (TextView) convertView.findViewById(R.id.humidityTextView);
convertView.setTag(viewHolder);
} else {
// pode reusuar
viewHolder = (ViewHolder) convertView.getTag();
}
// se o icone da condição climatica já foi baixado, o utiliza
// caso contrario, baixo o icone em uma thread separada
if (bitmaps.containsKey(day.iconURL)) {
viewHolder.conditionImageView.setImageBitmap(bitmaps.get(day.iconURL));
} else { // baixa e exibe a imagem de condição climática

new LoadImageTask(viewHolder.conditionImageView).execute(day.iconURL);
}
// obtém outros dados do objeto Weather e coloca nas Views
Context context = getContext();
// para carregar os recursos string
viewHolder.dayTextView.setText(context.getString(R.string.day_description, day.dayOfWeek, day.description));
viewHolder.lowTextView.setText(context.getString(R.string.low_temp, day.minTemp));
viewHolder.highTextView.setText(context.getString(R.string.high_temp, day.maxTemp));
viewHolder.humidityTextView.setText(context.getString(R.string.humidity, day.humidity));
return convertView;
}

O método getItem é usado para obter o objeto Weather na posição informada. Através desse objeto vamos conseguir fazer a leitura dos dados que devem ser exibidos na view personalizada. Antes de fazer isso, verificamos se convertView existe. Se a lista for pequena ou ainda estar sendo preenchida, esse valor será nulo. Logo, devemos inflar uma nova view e criar um objeto ViewHolder para armazenar as referências aos elementos da view criada. Adicionamos esse objeto usando o método setTag. Caso algum item role para fora da tela, convertView não será nulo e portanto uma view pode ser reutilizadas. Dessa forma, usamos o método getTag para obter um objeto ViewHolder, no qual será usado para atualizar os dados exibidos na interface.

Independente de qual forma serão adicionados os dados exibidos na tela, usamos o objeto ViewHolder para colocar as strings pré-definidas nos recursos do projeto com os valores do objeto Weather em questão. No caso do ícone para imagem, primeiro verificamos se o item já foi baixado, ou seja, existe uma chave com seu nome na variável bitmaps. Caso contrário, disparamos uma LoadImageTask para fazer o download da imagem em paralelo. Essa classe precisa do nome da imagem a ser baixada e da ImageView (parâmetro do método execute) os dados devem ser atualizados depois do download.

6.2.3 Classe interna LoadImageTask

A classe interna LoadImageTask usa herança da classe AsyncTask. Ela define como baixar uma imagem de um endereço URL e retorna essa imagem para a thread principal do aplicativo exibir o ícone baixado.

private class LoadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;

// armazena ImageView na qual configura o Bitmap baixado
public LoadImageTask(ImageView imageView) {
this.imageView = imageView;
}

// carrega a imagem
@Override
protected Bitmap doInBackground(String... strings) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
try {
// cria a URL para a imagem
URL url = new URL(strings[0]);
// abre uma HttpURLConnection, obtém seu InputStream
// e baixa a imagem
connection = (HttpURLConnection) url.openConnection();
InputStream inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
// coloca em cache para uso posterior
bitmaps.put(strings[0], bitmap);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}

// configura a imagem da condição climática no item da lista
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}

A classe AsyncTask<?,?,?> é um tipo genérico que exige três parâmetros de tipo:

  • O primeiro é o tipo de parâmetro, de comprimento variável, para o método doInBackground. Esse método é responsável por realizar a operação da thread, nesse caso, fazer o download da imagem.
  • O segundo é o tipo de parâmetro, de comprimento variável, para o método onProgressUpdate. Neste projeto não usaremos esse método, mas você poderia o usar para criar na tela do aplicativo uma barra mostrando a atualização da execução da tarefa, por exemplo, já foi baixado 30% de um arquivo.
  • O terceiro é o tipo de parâmetro, de comprimento variável, para o método onPostExecute. Esse método é executado na thread principal do aplicativo e permite que a ImageView seja atualizada com os dados baixados.

O download da imagem representando o ícone da previsão climática é realizado dentro do método doInBackground. Para fazer o download da imagem, fazemos o uso da classe HttlUrlConnection. Essa classe, abre uma conexão (stream) através de uma URL a um servidor web na internet. Uma vez que essa conexão seja realizada com sucesso (isto é, nenhuma exceção foi disparada), iniciamos o download do arquivo utilizando um objeto InputStream para receber os dados e um objeto BitmapFactory para usar esses dados e criar uma imagem bitmap. Essa imagem então é armazenada junto com uma chave (seu nome) a variável bitmaps.

Após o download da imagem, o método doInBackground termina, disparando logo na sequência a execução do método onPostExecute. Esse método então, apenas atualiza ImageView especificada com a imagem baixada.
se encontra o calendário.