quarta-feira, 28 de setembro de 2011

AlertDialog Personalizado no Android

Olá povo,

Ultimamente estou escrevendo post curtos, com soluções para problemas que as equipes onde eu trabalho/trabalhei têm/tiveram, e que ao meu ver poderiam ter sido feitas de outra forma. A última foi como personalizar o AlertDialog do Android, dependendo da versão do Android (e até do fabricante) o aspecto visual das mensagens que são exibidas para o usuário tem um visual diferente. Por causa disso, o pessoal de UX (User eXperience) resolveu padronizar isso. Só que o pessoal estava usando uma Activity com o tema de diálogo.

Eu não acho essa uma boa abordagem, uma vez que você mexe com o ciclo de vida da activity (métodos onCreate, onStart, onPause, ...) para exibir um simples dialog. Esse tutorial do site do Android Developers mostra como criar um dialog com o conteúdo personalizado.

Sim, mas e se eu quiser mudar o background do título e dos botões? Nesse post vou mostrar como fazer isso. O código abaixo mostra como personalizar as 3 áreas de um AlertDialog: a barra de título, a área de conteúdo e parte inferior onde ficam os botões.
// O parâmetro temTitulo deve ser informado,
// pois não há como saber se o Dialog tem
// título para mudar a imagem de background
// do topo e do meio.
public void mostraDialog(
AlertDialog dialog, boolean temTitulo) {

dialog.show();
Resources res = dialog.getContext().getResources();

// Pega a View do AlertDialog toda
ViewGroup contentView = (ViewGroup)
dialog.findViewById(android.R.id.content);

// Pega a ViewGroup com os filhos
// (title, textView, customView e barra dos botões)
ViewGroup contentChilds =
(ViewGroup)contentView.getChildAt(0);

// Barra de título
if (temTitulo)
contentChilds.getChildAt(0).setBackgroundDrawable(
res.getDrawable(R.drawable.box_top));

// Layout onde fica o texto (mensagem) do dialog
contentChilds.getChildAt(1).setBackgroundDrawable(
res.getDrawable(
temTitulo?
R.drawable.box_middle :
R.drawable.box_top));

// Layout onde fica a View personalizada (quando usada)
contentChilds.getChildAt(2).setBackgroundDrawable(
res.getDrawable(
temTitulo?
R.drawable.box_middle :
R.drawable.box_top));

// Layout dos botões
// Se não colocar wrap_content, o botão fica cortado
contentChilds.getChildAt(3).setLayoutParams(
new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));

contentChilds.getChildAt(3).setBackgroundDrawable(
res.getDrawable(R.drawable.box_bottom));

// Alterando botões
Button btn = (Button)dialog.findViewById(
android.R.id.button1);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_verde);
}

btn = (Button)dialog.findViewById(
android.R.id.button2);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_vermelho);
}
btn = (Button)dialog.findViewById(
android.R.id.button3);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_vermelho);
}
}

Como todos devem/deveriam saber tudo que é exibido no Android contém objetos da classe View. A classe ViewGroup é uma implementação do padrão de projeto Composite, e como tal, herda de View e é composto de várias Views. Dessa forma, após exibir o Alert, pegamos a ViewGroup que representa o AlertDialog e obtemos cada um dos seus "filhos". O Alert é composto de 4 filhos: o primeiro contém a view com o título e o ícone; a segunda tem o texto do Alert; o terceiro pode conter uma view personalizada associada ao Alert; e o último contém os botões (no máximo 3: positivo, negativo e neutro). Nesse exemplo, pegamos cada filho e alteramos o seu background.

As imagens de background utilizadas estão no formato 9.png que falei nesse post aqui. Mas um detalhe interessante, é que o background do botão é definido utilizando os drawable selector_botao_verde e selector_botao_vermelho. Esses dois arquivos, apesar de ficarem na pasta res/drawable, não são imagens. Na verdade eles são arquivos XML que descrevem as imagens que serão utilizadas pelos botões dependendo do seu estado. Abaixo temos o XML que descreve o botão verde.
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/btn_verde"
android:state_pressed="false"/>
<item
android:drawable="@drawable/btn_verde_pressionado"
android:state_pressed="true" />
</selector>

Notem que devemos ter (por uma questão de usabilidade) duas imagens: uma imagem para o botão não pressionado, e outra para ele pressionado. Isso dará a sensação que o usuário estará pressionando no botão. Essa prática pode ser adotada para todos os botões da sua aplicação (inclusive para o botão vermelho usado nesse post).

Para utilizar o AlertDialog personalizado, basta criá-lo normalmente e passá-lo para o método que criamos anteriormente.
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle("Titulo")
.setMessage("Mensagem")
.setPositiveButton("Sim", null)
.setNegativeButton("Não", null);
AlertDialog d = builder.create();
mostraDialog(d, true);

Abaixo temos um exemplo do nosso Alert em funcionamento.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

7 comentários:

Eric Braga disse...

Muito bom este seu post Glauber, aproveitando queria fazer uma pergunta: Não tem como criarmos um novo AlertDialog com styles para que possamos fazer reaproveitamento, ao invés de utilizarmos este método que foi criado?

Nelson Glauber disse...

Oi Braga,

Desconheço... Se alguém souber, posta um comment aqui pls! :)

4br4ç05,
nglauber

Anônimo disse...

Oi,

E para mudar a cor da linha que separa titulo e o texto?
;)

Christian disse...

Ola Glauber estava vendo aqui seu post, como faço para poder alterar a cor só do texto:
mensagem.setTitle("TEXTO");

Nelson Glauber disse...

Oi Cristian,

Não testei, mas creio que seja só pegar o primeiro child do meio, fazer um cast para TextView, depois é só definir a cor.
TextView txt = (TextView)contentChilds.getChildAt(1).getChildAt(0);
txt.setTextColor(Color.RED);

4br4ç05,
nglauber

Diário de uma vida disse...

Amigo, sou novato no Android e não consegui montar seu aplicativo que me parece ótimo. Por exemplo, não sei onde colocar esse trecho:"AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle("Titulo")
.setMessage("Mensagem")
.setPositiveButton("Sim", null)
.setNegativeButton("Não", null);
AlertDialog d = builder.create();
mostraDialog(d, true);"
Também vi que o pacote tem mais arquivos Layout, tipo Tela3.xml que não consta na matéria.
Resumindo, pergunto se poderias enviar-me o pacote completo compactado, pois lhe ficaria muito agradecido. Abs
elfogaca@gmail.com

Nelson Glauber disse...

Oi Leone,

Recomendo você dar uma olhada no DialogFragment ao invés do AlertDialog.
http://www.nglauber.com.br/2013/10/adeus-alertdialog-bem-vindo.html

4br4ç05,
nglauber