sexta-feira, 28 de dezembro de 2012

Time's Up com ficheiros

Aparentemente não demorou muito tempo, tal como tinha dito no post anterior seria interessante que os tempos guardados pudessem ser recuperados para sessões posteriores, assim adicionei dois botões à interface.



O botão Gravar vai gravar num ficheiro de texto os dados relativos aos tempos intermédios.
O botão Limpar, tal como o nome indica, apaga os tempos da textview não do ficheiro.

O código


A função associada ao botão Limpar:
    //função para limpar a textview 2
    public void bt_limpar_click(View v){
    tv2.setText("");
    }
Simples, sem comentários.


Agora a função associada ao botão Gravar:

    //gravar no ficheiro os tempos
    public void bt_gravar_click(View v){
    try{
    File myfile = new File(ficheiro);
    myfile.createNewFile();
    FileOutputStream fOut = new FileOutputStream(myfile);
    OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
    myOutWriter.append(tv2.getText());
    myOutWriter.close();
    fOut.close();
    showMessage("Gravado com sucesso!");
    }catch (Exception ex){
    showMessage(ex.getMessage());
    }
    }
Nesta função criamos um ficheiro e associamos um Output Stream a esse ficheiro que depois utilizamos para gravar o conteúdo do textView2.

Por fim a função que abre e lê o ficheiro sempre que a aplicação é iniciada:
    //função para ler o ficheiro e adicionar à textview
    public void ler_ficheiro(){
    try{
    File myfile = new File(ficheiro);
    FileInputStream fIn = new FileInputStream(myfile);
    BufferedReader myReader = new BufferedReader(new InputStreamReader(fIn));
   
    String aDataRow="";
    String aBuffer="";
    while((aDataRow=myReader.readLine())!=null){
    aBuffer +=aDataRow + "\n";
    }
    tv2.setText(aBuffer);
    myReader.close();
    showMessage("Tempos carregados!");
    }catch (Exception ex){
   
    }
    }
Esta função é chamada na função onCreate.

Não nos podemos esquecer de adicionar ao manifesto da aplicação a permissão com a seguinte linha:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

E assim temos um projeto completo, o próximo passo é visual, tenho de começar a ter mais cuidado com a interface, até agora tem sido só atirar botões e textviews para o ecrã e depois se vê.

A nova versão do projeto e da aplicação.

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.


sábado, 1 de dezembro de 2012

Bicionário - Windows Phone

Depois da versão Android fica aqui a versão Windows Phone.




Para este pequeno projeto vamos utilizar o Visual Studio 2010 Express for Windows Phone, a linguagem utilizada é o Visual Basic.

Começamos por criar o projeto novo.


Depois criamos a interface com dois radio buttons, uma textbox, um botão e um textblock.


Ainda antes de começar a codificar vamos precisar de adicionar os dois ficheiros txt ao projeto para podermos aceder a eles quando for necessário verificar a palavra introduzida. Para isso basta arrastar os ficheiros para o Solution Explorer.
Depois temos de alterar as propriedades dos dois ficheiros para que sejam compilados como recursos, assim com o botão direito do rato abrimos as propriedades dos ficheiros e alteramos a propriedade Build Action para Resource.

 As hipóteses são:

Agora o código no botão verificar, basta fazer duplo clique para abrir o editor do evento click no botão e inserir o seguinte código:

Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
        Dim palavra As String
        Dim linha As String
        Dim encontra As Boolean
        Dim streamreaderinfo As System.Windows.Resources.StreamResourceInfo

        encontra = False
        palavra = Me.TextBox1.Text
        If palavra.Length = 0 Then
            MessageBox.Show("Preencha com uma palavra por favor!")
            Exit Sub
        End If
        palavra = palavra.ToLower()
        If Me.RadioButton2.IsChecked() = True Then 'português
            streamreaderinfo = Application.GetResourceStream(New Uri("BidicionarioWP;component/portugues.txt", UriKind.Relative))
        Else
            streamreaderinfo = Application.GetResourceStream(New Uri("BidicionarioWP;component/ingles.txt", UriKind.Relative))
        End If

        Me.TextBlock1.Text = "A pesquisar..."
        Try
            Using f As New IO.StreamReader(streamreaderinfo.Stream)
                Do While f.EndOfStream = False
                    linha = f.ReadLine()
                    linha = linha.ToLower()
                    If linha = palavra Then
                        encontra = True
                        Exit Do
                    End If
                Loop
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro no acesso ao ficheiro: " & ex.Message)
            Exit Sub
        End Try

        If encontra = False Then
            Me.TextBlock1.Text = "Palavra não encontrada!"
            MessageBox.Show("Palavra não encontrada!")
        Else
            Me.TextBlock1.Text = "Palavra encontrada!"
            MessageBox.Show("Palavra encontrada!")
        End If

    End Sub

Este código é muito mais simples do que a versão Android e a execução no emulador também é muito mais rápida, a Microsoft já aprendeu há muito tempo que as ferramentas de programação são fundamentais.

Quem já programou em VB.NET vai achar o código muito parecido com o que tem utilizado, incluindo a função que permite apresentar pequenas mensagens (messagebox).

A principal diferença está no modo como referenciamos os ficheiros de texto porque neste caso eles estão inseridos como recursos no projeto e não como ficheiro isolados no sistema de ficheiros do dispositivo.

Uma última palavra sobre o emulador do Windows Phone que é muito rápido e estável comparativamente com o emulador Android que é bastante pesado e lento.

O projeto pode ser picado aqui.