sexta-feira, 15 de maio de 2015

Trabalhando com listViews (Caixas de listas)

Neste tutorial aprenderemos como utilizar as famosas "caixas de listas" (listViews). A ideia é apresentar ao usuário uma série de opções em uma tela, para sua escolha, dispostas na forma de linhas roláveis, conforme ilustrado na imagem:






Primeiro vamos criar um novo projeto em nosso ambiente de desenvolvimento, e nomeá-lo "projetoListView".

Depois, vamos abrir a pasta "res" (resources) e clicar com o botão direito do mouse sobre a pasta "layout", escolhendo a opção "New->Android XML File", ou seja, vamos criar um novo layout de tela.

Na janela que se abre, vamos digitar o nome da nova tela: "selecao_funcionario" e clicar no botão "OK".

Substitua, então, o código do novo arquivo pelo que se segue:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/listViewFuncionarios"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="#00000000"
        android:padding="0dp" >
    </ListView>

</LinearLayout>


Experimente alterar o modo de visualização do layout para "Graphical Layout" e perceba que já aparece uma prévia de como será disposta a lista. Mas falta ainda configurar a aparência individual das linhas.

Vamos, portanto, criar um novo arquivo xml, clicando com o botão direito do mouse sobre "layout" e escolhendo "New->Android XML File". Vamos nomeá-lo "linha_funcionario" e depois clicar em "OK".

Vamos agora substituir por completo o código do arquivo criado pelo código abaixo:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/linearLayoutLinha"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5dp"
        android:paddingTop="5dp" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <ImageView
                android:id="@+id/imageViewFoto"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:padding="5dp"
                android:src="@drawable/ic_launcher" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="0dp" >

            <TextView
                android:id="@+id/textViewNome"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:padding="2dp"
                android:text="Nome do funcionário" />

            <TextView
                android:id="@+id/TextViewCargo"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:padding="2dp"
                android:text="Cargo" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>


Com isto definimos o layout de uma tela que conterá uma lista de funcionários. Também criamos o design da linha individual da lista, cada uma sendo composta pela foto do funcionário, nome e cargo.

Precisaremos agora criar uma classe "FuncionarioDTO", descrevendo os campos de um item "funcionário".  Ele será composto pela foto do funcionário, nome e cargo. Então, na pasta "src" (sources), clique sobre o nome do pacote ("com.example.projetolistview") e escolha "New->Class". Então, na nova classe  criada, substitua integralmente pelo código abaixo:

package com.example.projetolistview;

import android.graphics.Bitmap;

public class FuncionarioDTO
{

   private Bitmap foto;
   private String nome;
   private String cargo;
  
   public Bitmap getFoto()
   {
      return foto;
   }
   public void setFoto(Bitmap foto)
   {
      this.foto=foto;
   }
   public String getNome()
   {
      return nome;
   }
   public void setNome(String nome)
   {
      this.nome=nome;
   }
   public String getCargo()
   {
      return cargo;
   }
   public void setCargo(String cargo)
   {
      this.cargo=cargo;
   }
  
}


Vamos criar também a classe "ArrayListAdapter" (pelo mesmo procedimento apresentado) e substituir pelo código abaixo:


package com.example.projetolistview;

import java.util.ArrayList;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import com.example.projetolistview.R;

public class ArrayListAdapter extends ArrayAdapter<Object>
{

   protected Context context;
   protected ArrayList<?> list=null;
   public int count=20;
   protected ArrayListAdapter.Callback callback=null;
   private int layout=R.layout.linha_funcionario;

   public ArrayListAdapter(Context context, int layout, ArrayList<?> list)
   {
      super(context, 0);

      this.context=context;
      this.list=list;

   }

   public ArrayListAdapter(Context context, ArrayList<?> list)
   {
      super(context, 0);

      this.context=context;
      this.list=list;

   }

   public void setLayout(int resLayoutId)
   {
      this.layout=resLayoutId;
   }

   public int getLayout()
   {
      return this.layout;
   }

   public interface Callback
   {
      View execute(final int position, final View convertView, final ViewGroup parent);
   }

   public void setCallback(Callback callback2)
   {
      this.callback=callback2;
   }

   @Override
   public void notifyDataSetChanged()
   {
      super.notifyDataSetChanged();
   }

   @Override
   public int getCount()
   {
      return list.size();
   }

   @Override
   public Object getItem(int position)
   {
      return list.get(position);
   }

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

   @Override
   public View getView(int position, View convertView, ViewGroup parent)
   {

      LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      View view=inflater.inflate(getLayout(), null);

      if (list != null)
      {

         if (callback != null)
         {
            view=callback.execute(position, view, parent);
         }

      }

      return view;
   }

}

Por último, vamos abrir o arquivo "MainActivity.java" e substituir o código por completo, pelo disposto abaixo:


package com.example.projetolistview;

import java.util.ArrayList;

import com.example.projetolistview.R;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.view.ContextMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity
{

   Context ctx;
   private ListView listViewFuncionarios;
   private ArrayListAdapter adapter;

   // Define uma lista de funcionarios.
   private ArrayList<FuncionarioDTO> listaDeFuncionarios;

  
   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.selecao_funcionario);
     
      // Cria a lista de funcionarios e carrega.
      listaDeFuncionarios = new ArrayList<FuncionarioDTO>();
      listaDeFuncionarios = carregarFuncionarios();    
     
      // Obtém o contexto da tela.
      ctx=this;

      // Obtém o elemento ListView para manipulação.
      listViewFuncionarios=(ListView) findViewById(R.id.listViewFuncionarios);

      // Cria o ArrayListAdapter e o configura.
      adapter=new ArrayListAdapter(this, listaDeFuncionarios);
      adapter.setLayout(R.layout.linha_funcionario);
      adapter.notifyDataSetChanged();
      adapter.setCallback(new ArrayListAdapter.Callback()
      {

         @Override
         public View execute(final int position, final View view, final ViewGroup parent)
         {
            String nome="";
            String cargo="";
            Bitmap foto;
           
            if (position <= listaDeFuncionarios.size())
            {
               // Para cada elemento da lista, obtém os campos, para preenchimento posterior.
               nome=listaDeFuncionarios.get(position).getNome();
               cargo=listaDeFuncionarios.get(position).getCargo();
               foto=listaDeFuncionarios.get(position).getFoto();

               // Preenche o conteúdo dos campos do listView.
               final TextView textViewNome=(TextView) view.findViewById(R.id.textViewNome);
               textViewNome.setText(nome);

               final TextView textViewCargo=(TextView) view.findViewById(R.id.TextViewCargo);
               textViewCargo.setText(cargo);
              
               final ImageView imageViewFoto=(ImageView) view.findViewById(R.id.imageViewFoto);
               imageViewFoto.setImageBitmap(foto);
              
               final LinearLayout linha=(LinearLayout) view.findViewById(R.id.linearLayoutLinha);

               // Alterna as cores das linhas.
               if (position % 2 == 0)
               {
                  linha.setBackgroundColor(Color.LTGRAY);
               }
               else
               {
                  linha.setBackgroundColor(Color.WHITE);
               }
            }

            return view;
         }
      });

      listViewFuncionarios.setAdapter(adapter);
      listViewFuncionarios.setFastScrollEnabled(true);

      // Define o comportamento do clique sobre um dos itens.
      listViewFuncionarios.setOnItemClickListener(new ListView.OnItemClickListener()
      {
         @Override
         public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3)
         {
            // Recupera nome e cargo do item selecionado pelo usuário.
            String nome  = listaDeFuncionarios.get(pos).getNome();
            String cargo = listaDeFuncionarios.get(pos).getCargo();
           
            // Mostra mensagem na tela.
            Toast.makeText(getApplicationContext(), "Funcionário " + nome + " (" + cargo +  ") selecionado", Toast.LENGTH_SHORT).show();
           
         }
      });

      listViewFuncionarios.setOnScrollListener(new AbsListView.OnScrollListener()
      {

         int page=1;
         private int currentFirstVisibleItem;
         private int currentVisibleItemCount;
         private int currentScrollState;
         private boolean isLoading;
         private boolean mIsScrollingUp;
         private int mLastFirstVisibleItem;

         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
         {
            this.currentFirstVisibleItem=firstVisibleItem;
            this.currentVisibleItemCount=visibleItemCount;
         }

         public void onScrollStateChanged(AbsListView view, int scrollState)
         {
            this.currentScrollState=scrollState;

            if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE)
            {
              
               if (!isLoading)
               {
                  isLoading=true;
               }
            }
         }

      });

      registerForContextMenu(listViewFuncionarios);
   }

   public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
   {

      final AdapterView.AdapterContextMenuInfo info=(AdapterView.AdapterContextMenuInfo) menuInfo;

   }

  
   private ArrayList<FuncionarioDTO> carregarFuncionarios()
   {
           
      FuncionarioDTO funcionarioDTO;
     
      // Cria um objeto que armazenará a foto do funcionário.
      // Neste exemplo, utilizaremos a mesma foto para todos os funcionários.
      // Aqui estamos obtendo a imagem da foto a partir de um PNG (ic_laucher) da pasta drawable.
      Bitmap foto;
      foto=BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_launcher);
     
      // Preenche os funcionários um a um e os acrescenta à lista.
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("João Silva");
      funcionarioDTO.setCargo("Auxiliar administrativo");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Maria Barbosa");
      funcionarioDTO.setCargo("Cozinheira");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Helena Dias");
      funcionarioDTO.setCargo("Gerente");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Pedro Salas");
      funcionarioDTO.setCargo("Diretor");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Arnaldo Xavier");
      funcionarioDTO.setCargo("Ascensorista");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Luiz Quintela");
      funcionarioDTO.setCargo("Programador");
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Ana Afonso");
      funcionarioDTO.setCargo("Analista de sistemas");  
      listaDeFuncionarios.add(funcionarioDTO);

      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Manoela Farias");
      funcionarioDTO.setCargo("Advogada");  
      listaDeFuncionarios.add(funcionarioDTO);

      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Cesar Alfredo");
      funcionarioDTO.setCargo("Contador");  
      listaDeFuncionarios.add(funcionarioDTO);

      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Marcio Teixeira");
      funcionarioDTO.setCargo("Vendedor");  
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Daniela Dantas");
      funcionarioDTO.setCargo("Advogada");  
      listaDeFuncionarios.add(funcionarioDTO);

      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Otavio Cintra");
      funcionarioDTO.setCargo("Programador");  
      listaDeFuncionarios.add(funcionarioDTO);

      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Dhenis Pereira");
      funcionarioDTO.setCargo("Gerente");  
      listaDeFuncionarios.add(funcionarioDTO);
     
      funcionarioDTO = new FuncionarioDTO();
      funcionarioDTO.setFoto(foto);
      funcionarioDTO.setNome("Erica Montalvo");
      funcionarioDTO.setCargo("Vendedora");  
      listaDeFuncionarios.add(funcionarioDTO);

      // Retorna a lista de funcionários criada.
      return listaDeFuncionarios;
   }
  
}


O que fizemos no MainActivity foi um método que cria uma lista de funcionários fictícios. Esse método devolve a lista, que é então utilizada para configurar o ArrayListAdapter. O resultado é uma tela com uma lista de funcionários rolável. Ao clicar sobre o nome de um funcionário, o usuário verá uma mensagem informando o nome e cargo do item "funcionário" escolhido.

Para os que preferirem obter o código-fonte completo, disponibilizo-o para download. Quem quiser apenas instalar o aplicativo no celular, segue o APK.

Ficamos por aqui. Até a próxima!




quinta-feira, 14 de maio de 2015

Rolamento azimute - Determinando a direção de um veículo a partir de sua posição inicial e final


Uma dúvida bastante frequente quando começamos a trabalhar com objetos que se movem em um mapa ao longo de um período de tempo, é se é possível determinar a direção que o objeto tomou.

A resposta é sim. Se considerarmos que as linhas que cortam um mapa (meridianos e paralelos) fazem dele um eixo cartesiano, é fácil concluir que se tomarmos dois pontos geográficos, sendo um inicial (de onde partiu o objeto) e um final (para onde foi o objeto), será possível de alguma maneira determinar a direção do movimento.

Embora estejamos trazendo esta problemática para o mundo do desenvolvimento de aplicativos Android, essa questão é tão antiga quanto a história da navegação.

Assim, é fácil supor que uma solução para isto já exista há alguns séculos. Realmente. O nome técnico para a determinação da direção de um objeto a partir do movimento que fez em um mapa é "Rolamento Azimute".

Há uma fórmula matemática de domínio público, utilizada há tempos pela marinha e aeronáutica em navegação, conhecida como “Fórmula do rolamento azimute”. Esta fórmula tem como argumentos duas posições de GPS (latitude e longitude), sendo uma a posição inicial (de onde veio) e a outra a posição final (para onde foi). À partir dessas informações, retorna um valor em radianos, representando a direção do objeto em relação ao norte geográfico.

Ei-la:


angulo = atan2(sin(longitude2-longitude1)*cos(latitude2), cos(latitude1)*sin(latitude2) - sin(latitude1)*cos(latitude2)*cos(longitude2-longitude1) )


O resultado vem em radianos. Portanto, deve-se convertê-lo para graus:


angulo = angulo * 57.2957795

 
Também é preciso garantir que o ângulo esteja entre zero e 360º, pois o resultado da nossa fórmula retorna um campo negativo de -1 a -180º e outro positivo, de 1 a 180º. Para corrigir, é simples:


Se angulo < 0
   angulo=abs(angulo)
Senao
    angulo=360-angulo


Depois destes acertos, “angulo” conterá o valor desejado. Ou seja, o ângulo em relação ao norte geográfico, do deslocamento do objeto no espaço. Em outras palavras, a direção em que o objeto se moveu.

É claro que, embora ter o conhecimento dessa fórmula seja meio caminho andado, há outras questões envolvidas no cálculo. Por exemplo, num conjunto de posições atuais de objetos, é preciso ter todas as sua posições anteriores. E calcular a direção para cada uma delas, através do uso da fórmula. Onde isso será feito? No aplicativo? Em uma API que será consumida pelo aplicativo? Há questões de performance embutidas na discussão.

Além disso é preciso pensar na maneira de informar isso ao usuário. Como a direção será  disposta no mapa? Podemos utilizar uma imagem de uma seta sobre cada objeto e rotacioná-la de acordo com o ângulo calculado. Ou ainda ter vários desenhos de seta diferentes, que serão mostrados de acordo com uma faixa de ângulos.

De toda forma penso que esta é uma fórmula de extrema utilidade, que muitos procuram sem sucesso. E, por conseguinte, tomei a decisão de publicá-la aqui, acreditando que ajudará muita gente.

Ficamos por aqui. Um abraço. E fique ligado!

Calculando a distância em metros entre dois pontos geográficos

Compartilho aqui este método de enorme utilidade. Ele calcula, a partir de dois pontos "latitude, longitude", a distância entre eles (em metros). Desconheço o autor desta rotina. Entretanto, é um código facilmente encontrado na Internet, nos fóruns de programação Android:

public static double distFrom(double lat1, double lng1, double lat2, double lng2)
{
   double earthRadius=3958.75;
   double dLat=Math.toRadians(lat2 - lat1);
   double dLng=Math.toRadians(lng2 - lng1);
 double a=Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
   double c=2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
   double dist=earthRadius * c;
   int meterConversion=1609;

   double retValue;
   retValue=dist * meterConversion;

   return retValue;
}

O método recebe quatro parâmetros: Latitude1, Longitude1, Latitude2 e Longitude2. Calcula a distância entre os dois pontos, considerando a curvatura do planeta Terra. O retorno do método é o resultado em metros.

Em breve mais dicas!


Como tirar fotos a partir de um app Android

A possibilidade de utilizar a câmera do dispositivo para tirar fotos a partir de nosso próprio app Android é muito atraente e certamente será de grande importância para dezenas de aplicações.

Geralmente no Android há duas formas de realizar determinadas operações. Como exemplo podemos citar o envio de mensagens SMS. É possível chamar o aplicativo nativo de envio de SMS já passando como parâmetros destinatário e o assunto, e então o próprio aplicativo nativo envia a mensagem. Ou, alternativamente, pode-se fazer com que nosso próprio app envie o SMS. É uma questão de escolha. Neste tutorial ensinaremos a primeira abordagem, ou seja, utilizaremos o aplicativo padrão de câmera do dispositivo.

Embora possa parecer algo complicado numa primeira análise, na verdade é extremamente simples. Chamamos o aplicativo de câmera nativo do aparelho (informando o diretório onde desejamos que a foto seja gravada) e então, concluída a operação de obtenção da fotografia, lemos o arquivo JPG e mostramos na tela do nosso app.

Então vamos lá...

Primeiro vamos criar um projeto novo e chamá-lo de "fotografo". Depois vamos abrir o arquivo "AndroidManifest.xml" e acrescentar (após o elemento "<uses-sdk>") a seguinte permissão:


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />



Isto fará com que nosso app possa gravar no cartão de memória do dispositivo (SDCARD).

Agora vamos abrir o arquivo "strings.xml" da pasta "res/values" e acrescentar os dois parâmetros abaixo, marcados em azul, para que fique assim:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">fotografo</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
   
    <string name="resultado"></string>
    <string name="textoBotaoTirarFoto">Fotografar</string>

</resources>


O que estamos fazendo com isto é definir um campo de mensagens, para mostrar o resultado da operação e também o texto do botão que colocaremos na tela.

Em seguida vamos construir o layout da tela que apresentaremos ao usuário. Abramos portanto o arquivo "activity_main.xml", da pasta "res/layout" e substituamos pelo código abaixo:

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

    <TextView
        android:id="@+id/textViewResultado"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/resultado" />

    <LinearLayout
        android:id="@+id/LayoutPrincipal"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/textViewResultado"
        android:gravity="bottom|center_horizontal"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/imageViewFoto"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|center_vertical"
            android:layout_weight="0.67"
            android:scaleType="center"
            android:src="@drawable/preview" />

        <Button
            android:id="@+id/btnTirarFoto"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/imageViewFoto"
            android:text="@string/textoBotaoTirarFoto" />
    </LinearLayout>

</RelativeLayout>


Aqui estamos acrescentando um "TextView", que utilizaremos para mostrar o resultado da operação de tirar a foto (textViewResultado). Também um "ImageView" , onde mostraremos a imagem da foto tirada (imageViewFoto). Por último, estamos incluindo um botão que será utilizado pelo usuário para chamar a operação de tirar a foto ("btnTirarFoto").


Faça o download da figura abaixo. Crie uma pasta sob o diretório "res" chamada "drawable". Grave esta imagem (preview.png) nesta pasta.






Agora a parte final. Vamos abrir o arquivo "MainActivity.java" da pasta "src" e substituir integralmente o código por este:


package com.example.fotografo;

import java.io.File;
import java.util.Random;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.view.View.OnClickListener;

public class MainActivity extends Activity
{
   private static final int IMAGEM_VIA_CAMERA=0;
   private Button btnTirarFoto;
   private TextView textViewResultado;
   private Intent intent;
   private Bitmap mImageBitmap;
   private Bitmap mImageBitmapThumb;
   private ImageView imageViewFoto;
   private String nomeDoArquivo;

   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      // Obtém os elementos da tela.
      btnTirarFoto=(Button) findViewById(R.id.btnTirarFoto);
      textViewResultado=(TextView) findViewById(R.id.textViewResultado);
      imageViewFoto=(ImageView) findViewById(R.id.imageViewFoto);

      // Adiciona listener ao botão.
      addListenerOnButtonFotografar();

   }

   public void addListenerOnButtonFotografar()
   {

      // A função de um listener no botão é verificar se houve intervenção do usuário sobre ele.
      // Aqui verificamos se o usuário clicou no botão e em caso afirmativo, tomamos uma ação.
      btnTirarFoto.setOnClickListener(new OnClickListener()
      {

         @Override
         public void onClick(View v)
         {

            // Configura e cria o diretório de gravação como "MinhasImagens" dentro do SDCARD.
            File diretorioDeImagens = new File(Environment.getExternalStorageDirectory(), "MinhasImagens");
            diretorioDeImagens.mkdirs();

            // Cria um prefixo para o nome do arquivo.
            Random r=new Random();
            int prefixo=r.nextInt(10000) + 1;
           
            // Constrói o nome do arquivo e informa onde o aplicativo de câmera deverá gravar a foto.
            nomeDoArquivo = "imagem_" + prefixo + ".jpg";
            File image = new File(diretorioDeImagens, nomeDoArquivo);
            Uri uriSavedImage = Uri.fromFile(image);
           
            // Chama o aplicativo de câmera do dispositivo e aguarda o resultado.           
            intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, uriSavedImage);
            startActivityForResult(intent, IMAGEM_VIA_CAMERA);
            // Note que "IMAGEM_VIA_CAMERA" é o nosso identificador (requestCode).
           
         }

      });

   }

   // Este método será chamado quando houver um resultado originário
   // de uma atividade previamente requisitada.
   public void onActivityResult(int requestCode, int resultCode, Intent intent)
   {
      // requestCode é o identificador que permite identificar de onde veio a chamada.
      if (requestCode == IMAGEM_VIA_CAMERA)
      {
         // Caso o usuário tenha tido êxito em tirar uma foto.
         if (resultCode == RESULT_OK)
         {

            // Atualiza a caixa de mensagens.
            textViewResultado.setText("Imagem gravada com sucesso");

            // Lê a imagem gravada.
            File sdcard = Environment.getExternalStorageDirectory();
            mImageBitmap=BitmapFactory.decodeFile(sdcard+"/MinhasImagens/" + nomeDoArquivo);
           
            // Gera um thumbnail.
            Bitmap imagemRedimensionada=Bitmap.createScaledBitmap(mImageBitmap, 300, 200, false);
           
            // Rotaciona a imagem em 90 graus.
            Matrix matrix = new Matrix();
            matrix.postRotate(90);
    Bitmap imagemRotacionada = Bitmap.createBitmap(imagemRedimensionada , 0, 0, imagemRedimensionada.getWidth(), imagemRedimensionada.getHeight(), matrix, true);
                            
            // Mostra o thumbnail da foto na tela do app.
            mImageBitmapThumb = imagemRotacionada;
            imageViewFoto.setVisibility(View.VISIBLE);
            imageViewFoto.setImageBitmap(mImageBitmapThumb);

         }
         else
         {
            // Atualiza mensagem de erro.
            textViewResultado.setText("Operação cancelada pelo usuário");

            // Restaura imagem original (de preview).
            int id=getResources().getIdentifier("com.example.fotografo:drawable/preview", null, null);
            imageViewFoto.setImageResource(id);
         }
      }
   }
     
}



Pronto! Basta isso. Execute o projeto. O app vai abrir uma tela inicial com a imagem de preview, aguardando que o usuário pressione o botão "fotografar". Ao fazê-lo, ele será redirecionado para o aplicativo nativo de foto do dispositivo. Depois que a foto for tirada e o usuário clicar em "save" na imagem, o controle será retomado pelo nosso app, que mostrará a foto no lugar da imagem de preview:







Também, utilizando as classes "BitMap" e "Matrix", fizemos uma operação de rotação e outra de redimensionamento da imagem, para que aparecesse em pé na tela do celular e na forma de "thumbnail".

Para quem preferir fazer obter o código-fonte completo, eis aqui o link de download. Se quiser visualizar direto no celular, eis o APK.

Até a próxima e fiquem ligados!

terça-feira, 12 de maio de 2015

Utilizando Google Maps no Android

Este é um tutorial de enorme importância. Todo desenvolvedor Android que um dia já se deparou com a necessidade de colocar um mapa da Google na tela do aplicativo sabe o quanto é trabalhoso fazê-lo. Não que seja difícil. Mas é inegável que é bastante burocrático.

O esforço decorre da exigência da Google em atrelar a identificação do ambiente de desenvolvimento, à identidade do aplicativo que usará o mapa e, por último, à identificação do usuário da Google Maps API.

Pra economizar o tempo e a sanidade do leitor, decidi tornar público o caminho das pedras em se tratando do uso de mapas no Android. Seguindo diligentemente as instruções fornecidas aqui será possível dispor de um mapa na tela do aplicativo de maneira trivial.

Utilizaremos a biblioteca "Google Play Services for Froyo". A escolha decorre do fato de que, com ela, nosso aplicativo será compatível com praticamente todos os dispositivos Android. Até mesmo aquele celular abandonado no fundo da gaveta vai mostrar o mapa corretamente.

Em nosso "Ambiente de Desenvolvimento Android", prevendo que teríamos em algum momento um tutorial sobre mapas, já dispomos da referida biblioteca, prontinha para uso.

Então vamos ao trabalho. Primeiro vamos criar um novo projeto no Eclipse chamado "projetoMapa".

O próximo passo será justamente, acrescentar a biblioteca "Google Play Services for Froyo". Para isto, clique com o botão direito sobre o nome do projeto e selecione "properties", opção "Android". Então clique no botão "Add". Selecione "google-play-services_lib" e pressione OK:





Com isto estamos preparados para começar a utilizar as funcionalidades de mapa em nosso aplicativo.

O segundo passo é definirmos o local onde o mapa vai aparecer, isto é, em que tela e qual o espaço utilizado por ele. Isto é feito inserindo-se um "fragment". Portanto, vamos agora abrir o arquivo "activity_main.xml", da pasta "res/layout", que corresponde ao design da nossa tela principal (MainActivity). Copie e cole o código abaixo: 

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

    <fragment
        android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_marginTop="50dp"
        class="com.google.android.gms.maps.SupportMapFragment" />

</RelativeLayout>


O terceiro passo é definir as permissões no arquivo "AndroidManifest.xml". Vamos abrir o arquivo e inserir as seguintes linhas, logo apos o elemento "<uses-sdk>":

<permission
    android:name="com.example.projetomapa.permission.MAPS_RECEIVE" android:protectionLevel="signature" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />


Para o quarto passo, precisaremos obter alguns itens:

* O identificador do ambiente de desenvolvimento.
* A identidade do aplicativo que usará o mapa.
* A chave da Google Maps API.

Para obter o primeiro item, precisamos, no Eclipse, abrir o menu "Window->Preferences->Android->Build". E então copiar o texto disposto no campo "SHA1 fingerprint":




Como estamos aqui usando o mesmo ambiente de desenvolvimento, o número será idêntico. O ideal é que seja outro, caso contrário estaremos compartilhando o mesmo ID de ambiente. É possível gerar outro número, mas isto foge do escopo deste tutorial.

O segundo item, a identidade do aplicativo, é o nome que aparece no início do arquivo "AndroidManifest.xml" no campo "package", ou seja: "com.example.projetomapa". É o nome do projeto.

O terceiro item, a chave de permissão de uso da Google Maps API, nós precisamos obter online. Desta maneira:

1) Acessar o site https://console.developers.google.com e fazer login.
2) Criar um novo projeto:



3) Definir o nome do projeto, clicar em CRIAR e aguardar o resultado:






4) Clicar no menu "APIs e autenticação", depois em "Credenciais" e depois no botão "Criar nova chave", opção "Chave do Android":





5) Aqui o mais importante: É nesta janela onde faremos a associação das informações. Cole aqui o "SHA1 fingerprint" que identifica o ambiente de desenvolvimento (no caso do exemplo "02:4A:D9:2D:D2:85:E5:F1:94:95:36:EC:66:02:99:E5:71:BA:C2:7C"). Depois digite um sinal de ponto e vírgula (";") que será o separador. Em seguida digite a identidade do aplicativo: "com.example.projetomapa". Então clique no botão "criar":





6) O resultado da operação é a liberação de uma chave que poderemos utilizar em nossos aplicativos que disponham de mapa, para que o mapa tenha a autorização de ser mostrado. No caso a Google Maps API key "AIzaSyB_i5ss8XwMWOXTDVj50A8jgdecWhH29X0"
Recomendamos utilizar a sua própria chave, pois esta do exemplo poderá ser cancelada, fazendo com que seu aplicativo eventualmente pare de mostrar o mapa:






7) O último passo é ativar a API. Para isto, devemos selecionar a opção do menu lateral "APIs", depois clicar em "Google Maps Android API". Em seguida, clicar no botão "Ativar API", conforme mostram as imagens:







Concluímos, portanto, o quarto passo e o resultado desta etapa foi a ativação do uso da Google Maps API e a obtenção da chave de mapa.

Vamos agora definir dentro de nosso aplicativo, a chave de mapa, para que ele tenha autorização de mostrá-lo.

Vamos abrir o arquivo "AndroidManifest.xml" e colar o trecho abaixo dentro do elemento <application>, substituindo a chave pela chave obtida no item 6 acima, assim:


 <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="[suaChaveDoGoogleMapsAPI]" />


Substitua o trecho em azul pela sua própria chave, obtida no passo 4 (item 6).


Quinto passo

Abrir o arquivo "MainActivity.java", disponível na pasta "src" e substituir o "extends Activity" por "extends FragmentActivity", como se segue:


public class MainActivity extends FragmentActivity
{


Pra facilitar ainda mais, segue a lista dos imports necessários para que tudo seja reconhecido corretamente:


import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;


O passo final é inserir o código abaixo no método "onCreate" do "MainActivity.java", logo após a instrução "setContentView":


// Verifica se o aparelho possui o Google Play Services (requisito necessário para o
// funcionamento do mapa) e mostra aviso caso não possua.
int resultCode=GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext());

if (resultCode == ConnectionResult.SUCCESS)
{

   // Inicializa o mapa, para utilização
   FragmentManager fmanager=getSupportFragmentManager();
   Fragment fragment=fmanager.findFragmentById(R.id.map);
   SupportMapFragment supportmapfragment=(SupportMapFragment) fragment;
   final GoogleMap supportMap=supportmapfragment.getMap();

   // Configura o mapa como sendo do tipo padrão
   supportMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

   // Exibe marcador da posição atual do usuário (bolinha azul do Google Maps).
   supportMap.setMyLocationEnabled(true);

   // Cria um ponto (Latitude, Longitude) que será o centro do mapa.
   LatLng latlng=new LatLng(-22.910509, -43.203932);

   // Desenha o mapa, definindo como centro a posição criada acima. 
   CameraPosition cameraPosition=new CameraPosition.Builder().target(latlng).zoom(15).build();
   supportMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

}
else
{
   try
   {
      int RQS_GooglePlayServices=1;
      
      GooglePlayServicesUtil.getErrorDialog(resultCode, this, RQS_GooglePlayServices);
   }
   catch (Exception erro)
   {
      finish();
   }
}

O que o código acima está fazendo é, primeiro verificando se o "Google Play Services" está presente no dispositivo, pois é pré-requisito para o mapa funcionar. Caso não esteja instalado no aparelho, uma mensagem de alerta é mostrada.

Caso a condição tenha sido atendida, então o mapa é inicializado e é criado um ponto LatLng que será o centro do mapa. Também estamos dizendo para o mapa mostrar o "ponto azul" com a localização automática do usuário. As últimas instruções efetivamente realizam o trabalho final, apresentando na tela o resultado:







Podemos considerar este artigo uma espécie de cartilha no que diz respeito ao uso de mapas da Google em aplicativos Android. A intenção foi tornar o processo mais fácil para quem está começando, de forma a eliminar as frustrações que são frequentes quando o desenvolvedor começa a se aventurar por este mundo desavisadamente.

Para aqueles que quiserem baixar o projeto completo,ei-lo aqui. Para quem preferir o APK para ver o resultado direto no celular, baixe-o aqui.

Uma vez que o mapa tiver sido mostrado corretamente, daí pra frente é só evolução.

Em tutoriais futuros, mostraremos como colocar marcadores no mapa e executar animações de câmera, para pontos específicos ou ainda para mostrar um determinado conjunto de marcadores.

Até lá!