De volta à plataforma Android desta vez para fazer um pequeno programa que vai permitir cronometrar e guardar tempos.
A interface é simples:
O botão Guardar vai permitir adicionar o tempo atual aos tempos intermédios. O botão Iniciar vair intercalar a interface entre a hora atual e o cronometro. O botão Recomeçar vai recolocar o temporizador a zero.
Sempre que carregar no botão Guardar o tempo do cronometro é adicionado aos tempos intermédios o que implica ter uma área que se pode deslizar para cima e para baixo uma vez que não existe limite no número de tempos que podemos adicionar.
Os desafios deste pequeno programa são dois:
- primeiro a atualização da interface implica criar uma thread separada, de outro modo o sistema não atualiza a informação mostrada ao utilizador.
- segundo temos os cálculos com tempo, neste aspeto encontrei um problema inesperado! O programa apresenta resultados diferentes quando executado no emulador de quando é executado no dispositivo real. Ainda não percebi porquê mas testei com duas versões diferentes do emulador e os resultados foram os mesmos, mas quando executei num Huawei com a versão 4.0.3 do Android os resultados não coincidiam com os apresentados pelo emulador.
A interfaceVamos começar pela interface. O código é:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" >
<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="wrap_content" >
<Button android:id="@+id/bt_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:onClick="bt_guardar_click" android:text="@string/guardar" />
<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/bt_start" android:layout_marginTop="16dp" android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/textView1" android:textAppearance="?android:attr/textAppearanceMedium" />
<Button android:id="@+id/bt_iniciar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/bt_start" android:onClick="bt_iniciar_click" android:text="@string/inicio" />
<Button android:id="@+id/bt_continuar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/bt_iniciar" android:onClick="bt_continuar_click" android:text="@string/reiniciar" />
<TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textView2" android:layout_alignBottom="@+id/textView2" android:layout_toRightOf="@+id/bt_start" android:text="@string/intermedios" android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout></ScrollView>
Resumindo temos:
- 1 scrollview para podermos deslizar o conteúdo
- 3 botões
- 3 Textviews: 1 para mostrar a hora ou o tempo cronometrado, 1 para o texto "Intermédios" e por fim para os tempos intermédios guardados.
O códigoEm relação ao código vamos apresentar a função on create
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_times_up); tv1=(TextView) findViewById(R.id.textView1); tv2=(TextView) findViewById(R.id.textView2); iniciado=false;
//thread que vai atualizar a UI Thread th = new Thread(){ @Override public void run(){ while(true){ try { Thread.sleep(250); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable(){ public void run(){ String t; if(!iniciado){ cal = Calendar.getInstance(); t=String.format("%02d:%02d:%02d",cal.get(Calendar.HOUR_OF_DAY),cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND)); tv1.setText(t); }else{ int m=0,h=0,s=0; cal = Calendar.getInstance(); cal.setTimeInMillis(cal.getTimeInMillis()-inicio.getTimeInMillis()); //h=cal.get(Calendar.HOUR_OF_DAY); h=(int)cal.getTimeInMillis()/(1000*60*60); m=cal.get(Calendar.MINUTE); s=cal.get(Calendar.SECOND); t=String.format("%02d:%02d:%02d",h,m,s); tv1.setText(t);// + "-" + Long.toString(mil)); } //if else }//run });//runOnUiThread }//whiler }//run };//thread th.start(); }//oncreateEste código cria um ciclo infinito que vai atualizar e apresentar a hora ou o tempo cronometrado de acordo com a variável "iniciado" que indica se o cronometro foi ou não iniciado.
A linha de código que está comentada é a que apresenta um valor diferente quando executada no emulador de quando executada no dispositivo real. Esta linha devia calcular quantas horas correspondem ao valor de milissegundos calculados como diferença entre o tempo inicial do cronometro e o atual. No emulador o valor é calculado corretamente como sendo zero inicialmente enquanto que no dispositivo começa em um!! Vai-se lá saber porquê.
Esta é a imagem do programa a correr no dispositivo real.
E esta é a imagem do programa a correr no emulador.
Para resolver este problema decidi calcular a hora de outro modo utilizando a linha
h=(int)cal.getTimeInMillis()/(1000*60*60);
Por fim criei um menu com duas opções:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item1" android:title="Sobre"></item> <item android:id="@+id/item2" android:title="Sair"></item></menu>
O código que cria o menu e responde às opções é apresentado a seguir:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_times_up, menu);
return true;
}
//opções do menu
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getTitle().toString().equals("Sobre"))
showMessage("Paulo Ferreira");
if(item.getTitle().toString().equals("Sair")){
showMessage("That's all folks!");
finish();
}
return true;
}
A primeira função é criada pelo Eclipse automaticamente enquanto que a segunda é a que reage ao clique nas opções dos menus.
A função showMessage é foi apresentada noutros projetos e somente cria uma Toast com o texto que lhe foi passado:
/* mostra o texto passado numa pequena mensagem */
private void showMessage(CharSequence text) {
Context context = getApplicationContext();
int duration = Toast.LENGTH_SHORT;
//faz aparecer uma mensagem pequena durante um certo tempo
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
Por fim temos as funções que implementam a lógica dos três botões:
//botão guardar
public void bt_guardar_click(View v){
String t;
t=(String) tv2.getText();
t = t + "\n" + tv1.getText();
tv2.setText(t);
}
//botão iniciar
public void bt_iniciar_click(View v){
if(inicio==null)
inicio = Calendar.getInstance();
iniciado=!iniciado;
}
//botão recomeçar
public void bt_continuar_click(View v){
inicio = Calendar.getInstance();
}
Um dos problemas que ainda temos para resolver é o que acontece quando mudamos a orientação do dispositivo, uma vez que o programa é reiniciado o tempo do temporizador perdido. Assim podemos fixar a orientação em vertical (portait) com a seguinte linha:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
Basta adicionar no inicio da função onCreate.
Uma funcionalidade que fica para depois, espero que não muito depois, é adicionar a possibilidade de guardar os tempos para mais tarde poder recarregar e comparar.
Como sidekick temos uma caraterística interessante que permite que o programa mesmo não estando aberto em primeiro plano continua a calcular o tempo no cronometro graças ao modo como o Android executa os programas, assim podemos ter o programa aberto a executar em modo cronometro e atender chamadas ou executar outro programa que quando voltamos o tempo continua.