Avançar para o conteúdo principal

Time's Up

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 interface

Vamos 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ódigo

Em 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();
    }//oncreate

Este 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.


Comentários

Mensagens populares deste blogue

Upgrade do Windows Home para Pro sem formatar

 Há algum tempo que tentava fazer o upgrade do meu Windows 10 da versão Home para a versão Pro, mas chegava sempre a um ponto em que me era solicitado para formatar o sistema e não estava para isso. Finalmente conseguinte seguindo estes passos: - seguinte estes passos  utilizei uma das chaves genéricas para o Windows 10 Pro e fui a Settings > Update & Security > Activation > Change the product key; - após inserir uma das chaves o Windows instala as funcionalidades Pro e pede para reiniciar; - agora tem o Windows Pro mas não está ativado, assim fui ao site urcdkeys  onde comprei uma chave para o Windows Pro por menos de €20; - com essa chave voltei a funcionalidade Change the product key e ativei o Windows; - e pronto, Windows Pro ativado sem formatar ou reinstalar. Importante : eu não tenho nada a ver com o site urcdkeys por isso a vossa experiência pode correr de forma diferente da minha.

PONG em Flash AS3.0

Mais um pequeno jogo para demonstrar algumas das funcionalidades do AS3.0. Para este exemplo vamos implementar uma versão do Pong. Para este Pong vamos criar a possibilidade de acelerar a bola com a raqueta e, para tornar o jogo mais difícil, quando se atingir uma determinada pontuação fazemos aparecer uma parede no meio do campo de jogo. O código é muito parecido com o jogo do post anterior, mas um pouco mais complicado. Para controlar a nossa raqueta utilizamos a seguinte função: function teclado(e:KeyboardEvent):void{ dir_j1=0; if (e.keyCode == Keyboard.UP){ if(jogador1.y>0) jogador1.y -=5; dir_j1=-5; } if (e.keyCode == Keyboard.DOWN){ if(jogador1.y<370) jogador1.y +=5; dir_j1=5; } } Agora está mais simples pois só percorremos as linhas, ou seja, a coluna nunca muda. A raqueta que é controlada pelo computador depende do seguinte código: function movepc():void { if (bola.y>jogador2.y) jogador2.y = jogador2.y + velocidade_y; if (bola.y<jogador2.y) joga...

Vamos fazer um carro com o Unity 3D

Neste artigo vamos fazer um carro, simples, com o Unity 3D. A ideia é utilizar o motor de física do Unity 3D para simular o comportamento do carro. Os passos a seguir são: [1] - Criar um projeto novo