quarta-feira, 26 de setembro de 2012

Construindo interfaces - Layouts

Olá pessoal,

Após algumas semanas sem escrever venho com mais um post interessante sobre desenvolvimento Android, vou falar sobre a utilização de layouts. Antes é importante entender que a programação para desenvolvimento Android trás conceitos de nomes diferentes dos que já estamos acostumados, mas com funcionalidades muito semelhantes. Os controles que conhecemos como objetos visíveis uteis para a interação entre o usuário e o aplicativo no Android são chamados de Views. Como disse no post anterior, as Activities funcionam de modo muito parecido ao que chamamos de forms. Bom estes são dois conceitos fáceis de entender pois são muito parecidos com os que os programadores já conhecem de outras linguagens e tipos de programação.

Além destes há também as View Groups que são extensões das Views que podem conter outras views internamente. A partir do Android 3.0 foram introduzidos também os Fragments que são partes da interface com "vida própria" e que interagem com a Activity.

Neste post vou falar de Layouts que são extensões de View Groups, que por sua vez são extensões de Views.

Os Layouts são utilizados para posicionar as Views dentro da interface visível ao usuário. As classes mais comuns de layout são:

  • LinearLayout
  • RelativeLayout
  • GridLayout

Vamos falar um pouco de cada uma delas com exemplo de código.

LinearLayout

A classe LinearLayout permite que sejam construídas interfaces alinhando controles na orientação horizontal ou vertical. É um tipo de layout muito simples de utilizar, porém limitado e geralmente é utilizado em conjunto com outros tipos de layout. No nosso exemplo teremos 3 botões na horizontal preenchendo completamente a largura da tela do dispositivo. Repare no código que não há nenhum número definindo explicitamente a posição ou largura dos botões.



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

    <Button
        android:id="@+id/btn1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/bt1txt"
        android:layout_weight="1"
       />

    <Button
        android:id="@+id/btn2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="@string/bt2txt"
 />

    <Button
        android:id="@+id/btn3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="@string/bt3txt"
   />

</LinearLayout>


Repare no resultado:

Vamos as considerações. Se você reparar no código, irá notar que o parametro  android:orientation="horizontal" define a orientação do nosso layout. Todos os botões utilizam a largura como fill_parent, que é uma constante que define que a view utilizará a medida do parent para se redimensionar. Pensando assim, teriamos um problema de layout, pois todos os botões ficariam sobrepostos e apenas o último adicionado seria visível. Mas o que acontece aqui? Estamos utilizando uma propriedade muito interessante, o layout_weight que define o "peso" que cada controle tem dentro do layout. Como defini o valor 1 para cada um dos botões, todos eles ficaram um ao lado do outro ocupando a largura da tela.
A propriedade height está definida como wrap_content que define a altura como o tamanho suficiente para caber o conteúdo interno da view.

RelativeLayout

Com o RelativeLayout você pode posicionar suas Views dentro da Activity relacionando com outras Views ou com o seu parent. No nosso exemplo abaixo, estou alinhando os botões de acordo com posições relativas na tela.


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

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="@string/bt1txt" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/bt2txt" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="@string/bt3txt" />

</RelativeLayout>


GridLayout

O GridLayout é um dos layouts mais flexíveis e com ele podemos alinhar nossos controles utilizando linhas e colunas arbitrárias. Repare que no caso do GridLayout nós utilizamos a propriedade gravity para definir o tamanho dos botões.

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="fill_horizontal"
        android:text="@string/bt1txt" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="fill"
        android:text="@string/bt2txt" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="fill_horizontal"
        android:text="@string/bt3txt" />

</GridLayout>


Todos estes layouts estão no arquivo deste artigo. Eu deixei comentada as linhas que carregam os layouts no arquivo MainActivity.java, você pode descomentá-los para testar. Eu separei os arquivos propositalmente.

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        
        //setContentView(R.layout.linear_layout);
        //setContentView(R.layout.relative_layout);
        setContentView(R.layout.grid_layout);
    }


Abraço


sábado, 8 de setembro de 2012

Ciclo de Vida de uma Activity

Olá pessoal,

Hoje vamos abordar mais um assunto importante no desenvolvimento de aplicações Android, o ciclo de vida de uma activity.

Inclusive, vou abrir com uma frase interessante do livro Professional Android 4 Application Development, que diz o seguinte:


"A good understanding of the Activity lifecycle is vital to ensure that your application provides a
seamless user experience and properly manages its resources."

Em português

"Um bom entendimento do ciclo de vida da Activity é vital para garantir que sua aplicação provê uma experiencia continua de usuário e gerencia seus recursos apropriadamente."

Isso significa que devemos estar conscientes do que acontece quando a nossa Activity é iniciada, está em processo e é finalizada, e se neste processo está utilizando e liberando os recursos corretamente. Se tomarmos como exemplo um aplicativo que utiliza a camera do dispositivo, temos que "prender" o recurso somente quando necessário e liberá-lo quando não estamos utilizando. Mas o que quer dizer "somente quando necessário"? Ainda falando de um aplicativo que utiliza a camera do dispositivo, imagine que você está utilizando o aplicativo e de repente você precisa abrir o seu gerenciador de e-mails, que é uma outra aplicação, enquanto você estava vendo o seu aplicativo, sua Activity estava ativa, no momento em que você abriu seu gerenciador de e-mail sua aplicação ficou escondida e teoricamente você não está utilizando a camera, neste momento você deve liberar este recurso. 

Pilha de Activities


No Android há uma pilha de Activities, onde sempre a ultima  Activity aberta fica visível, exceto quando é uma  Activity em background. Enquanto ela está visível, ela tem prioridade de processamento, pois em tese, é a  Activity que o usuário está utilizando. 
De tempos em tempos o sistema operacional verifica se há  Activities que não estão sendo mais utilizadas na fila, estas  Activities são finalizadas sem que o usuário seja notificado, assim o sistema operacional consegue liberar recursos. Se você por exemplo abrir um jogo como o Angry Birds por exemplo, se você o deixar em segundo plano, um tempo depois quando você for abrir o jogo carrega tudo novamente, pois o Android detectou que esta era uma  Activity que não estava sendo utilizada.

Estados da Activity


As Activities podem estar em quatro estados:

Active: Quando está visível e com foco. O sistema operacional neste momento faz o possível para mantê-la ativa. Quando não está mais em foco, ela entra em pausa.

Paused: Quando a  Activity está visível mas não está sendo utilizada pelo usuário por não estar com foco.

Stopped: Quando a  Activity não está visível para o usuário ela para e fica na memória se tornando forte candidata a se removida da memória para liberar recursos.

Inactive: Ocorre quando a Activity está morta e antes de ser chamada novamente. Esta Activity é removida da memória para liberar recursos.

É importante saber que esta transição de estados entre as Activities deve ser transparente para o usuário e se o aplicativo necessitar manter o estado do aplicativo entre abrir e fechar, o estado deve ser salvo e recuperado entre estes estados. 
A imagem abaixo foi extraída do livro Professional Android 4 Application Development e representa de uma maneira simples e fácil de entender o ciclo de vida de uma Activity.


Eu escrevi um aplicativo simples para rastrear todos os eventos que a Activity sofre durante seu ciclo de vida. No exemplo abaixo é possível visualizar a chamada de todos os eventos, e também as funções auxiliares que criei.

package com.example.ciclo.vida.activity;

import android.os.Bundle;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

private History history = History.getInstance();

@Override
protected void onStart() {
super.onStart();
this.history.setHistoryText("onStart");

}

@Override
protected void onRestart() {
super.onRestart();
this.ShowHistory("onRestart");

}

@Override
protected void onResume() {
super.onResume();
this.ShowHistory("onResume");

}

@Override
protected void onPause() {
super.onPause();
this.ShowHistory("onPause");
}

@Override
protected void onStop() {
super.onStop();
this.ShowHistory("onStop");
}

@Override
protected void onDestroy() {
super.onDestroy();
this.ShowHistory("onDestroy");
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.ShowHistory("onCreate");

if (savedInstanceState != null) {
this.ShowHistory(savedInstanceState
.getString(getString(R.string.state_history)));
}

}

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putString(getString(R.string.state_history),
history.getHistoryText());
this.ShowHistory("onSaveInstanceState");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);

this.history.clearHistoryText();
this.ShowHistory("onRestoreInstanceState");
this.ShowHistory(savedInstanceState
.getString(getString(R.string.state_history)));

}

private void ShowHistory(String newState) {

if (newState != "") {
this.history.setHistoryText(newState);

TextView textView = (TextView) findViewById(R.id.mainActivity_textView);
textView.setText(history.getHistoryText());
}
}

}

Atente ao fato de que todas as chamadas aos eventos são sobrecargas definidas pelo atributo override. Quando você executar seu aplicativo irá ver uma imagem semelhante a abaixo:

Faça mais alguns testes, como rotacionar o aparelho, ou apertar o botão voltar ou o home, você verá que os status serão incrementados. Este exemplo mostra também como gravar o estado da Activity e recarregar depois através do onSaveInstanceState e do onRestoreInstanceState. 

O código fonte deste aplicativo pode ser baixado aqui.

Abraço a todos.











domingo, 2 de setembro de 2012

Mudanças de Configuração

Olá pessoal,

Hoje venho trazer um artigo simples, mas muito útil, sobre como detectar as mudanças de configuração de ambiente. Para este post é interessante utilizar o exemplo em um dispositivo físico. O nosso aplicativo de hoje irá detectar quando mudamos a orientação do dispositivo de retrato para paisagem e vice-versa. Já que até o momento não fizemos nenhum exemplo utilizando dispositivos físicos, vou orientar vocês a configurar o dispositivo para isso.

Primeiro de tudo é importante saber que você irá necessitar do ADB, aferramenta que faz a ponte entre o dispositivo e o PC. Este era só um lembrete, uma vez que você já deve ter este executável nas dentro do SDK do Android. Vou fazer a configuração baseada no meu Galaxy S2 com Android 4.0.3. O caminho para chegar até a configuração pode mudar um pouco, dependendo da versão do Android que utiliza.

1 - Entre no menu de configurações
2 - Vá até "Opções do Desenvolvedor"
3 - Marque a opção "Depuração de USB"

Pronto esta é a configuração que você precisa para executar seu aplicativo no seu dispositivo móvel. Obviamente, você precisará do cabo de dados do aparelho também, e em alguns casos, do driver para a comunicação através do ADB.

Vamos deixar o nosso dispositivo de lado um pouco e falar sobre código. Quando estamos trabalhando com a detecção de eventos do sistema, como a troca de orientação da tela, precisamos saber que estes eventos reiniciam a atividade atual para se adaptar as novas configurações adotadas pelo sistema. Se a atividade é extremamente simples e não é um problema ser reiniciada, você não precisa fazer nada, mas se a as atividade não pode ser reiniciada e você quer manipular os dados da nova configuração, você pode inserir a linha em destaque no AndroidManifest.xml


<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:configChanges="orientation"
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>


Você pode ter mais detalhes deste tipo de manipulação no link http://developer.android.com/guide/topics/resources/runtime-changes.html. Você pode aprofundar sua pesquisa neste link e descobrir quais são os eventos de sistema que podem ser manipulados.

Agora que já temos esta consciência, vamos escrever algum código para detectar a mudança de ambiente. O layout deste aplicativo e código fonte podem ser baixados clicando aqui:

Veja no código abaixo que temos um função própria e o tratamento do evento onConfigurationChanged.
A nossa função, setText, recebe um valor e trata o de acordo com o valor recebido da nova configuração para exibição de texto amigável na tela.


         private void setText(int orientation) {
TextView txt = (TextView) findViewById(R.id.txtOrientation);

switch (orientation) {
case (Configuration.ORIENTATION_LANDSCAPE):
txt.setText("Landscape");
break;
case (Configuration.ORIENTATION_PORTRAIT):
txt.setText("Portrait");
break;
}
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setText(newConfig.orientation);
}

Quando a nova configuração é recebida primeiro é chamado o método onConfigurationChanged da classe pai e dois nosso evento é tratado. 

Para testar nosso aplicativo você terá de conectar seu dispositivo ao computador, pela porta USB e executar o projeto no Eclipse. Se você tiver aberto a AVD do Android será mostrado um menu questionando em quais dos dispositivos você quer executar.

Depois que executar, rotacione seu dispositivo e veja que o nosso sistema irá escrever em qual posição que o dispositivo está.

No próximo tópico vou tratar sobre o ciclo de vida de um atividade.

Abraço