Concrete Logo
Hamburger button

Views customizadas no Android – Parte 1

  • Blog
  • 6 de Janeiro de 2016
Share

*Esse post foi originalmente publicado no Medium e no Blog pessoal do autor.

Para implementar uma aplicação típica, o Android disponibiliza uma grande variedade de widgets:

Entretanto, apesar dessa grande quantidade de widgets fornecidos pela plataforma, quase sempre nos deparamos com cenários nos quais o pessoal de UX/UI especificam componentes peculiares que o Android não possui por padrão.

Para construir esses componentes, a maioria dos desenvolvedores não se preocupa com o baixo acoplamento, reuso e alta coesão e acabam construindo e replicando código dentro de Activities ou até mesmo criando Fragments para uma simples View. Isso faz com que eles tenham que lidar com o ciclo de vida dos Fragments:

Ciclo de vida do fragment

Ciclo de vida do fragment

Vamos supor que no aplicativo que estamos desenvolvendo teremos que implementar um botão que pisque toda vez que for clicado. Para esse exemplo, vamos usar o retrolambda, que possibilita a utilização de lambda do Java 8 em projetos Android.

@MainActivity.java

[java]
package com.tpinho.customviews.ui.activity;

import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;

import com.tpinho.customviews.R;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

final Button button = (Button) findViewById(R.id.button_main_click);
button.setOnClickListener(view -> {
blinkView(view);
Snackbar.make(view, getString(R.string.clicked, button.getText()), Snackbar.LENGTH_LONG).show();
});
}

private void blinkView(final View view) {
Animation alphaAnimation = new AlphaAnimation(1f, 0f);
alphaAnimation.setDuration(1000);
view.startAnimation(alphaAnimation);
}

}
[/java]

@layout/activity_main.xml

[code language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<Button
android:id="@+id/button_main_click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/click" />

</LinearLayout>
[/code]

A implementação funciona perfeitamente, mas se precisarmos implementar o efeito de piscar em outros botões teremos que replicar o código ou criar uma classe utilitária para isso. Além disso, estaremos misturando efeitos visuais com a regra de negócio, o que vai dificultar os testes e a evolução do código.

Então, o que fazer? Customizar o widget de botão do Android e implementar a funcionalidade de piscar o botão toda vez que for clicado:

@BlinkButton.java

[code language=”java”]
package com.tpinho.customviews.ui.custom;

import android.content.Context;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;

public class BlinkButton extends AppCompatButton {

private int times = 0;

private OnClickListener onClickListener;

public BlinkButton(Context context) {
super(context);
init();
}

public BlinkButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public BlinkButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
super.setOnClickListener(view -> blink(view));
}

@Override
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}

private void blink(final View view) {
Animation alphaAnimation = new AlphaAnimation(1f, 0f);
alphaAnimation.setDuration(1000);
alphaAnimation.setRepeatCount(times);
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}

@Override
public void onAnimationRepeat(Animation animation) {
}

@Override
public void onAnimationEnd(Animation animation) {
if(onClickListener != null)
onClickListener.onClick(view);
}
});
startAnimation(alphaAnimation);
}

}
[/code]

@MainActivity.java

[code language=”java”]
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

final Button button = (Button) findViewById(R.id.button_main_click);
button.setOnClickListener(view -> {
Snackbar.make(view, getString(R.string.clicked, button.getText()), Snackbar.LENGTH_LONG).show();
});
}

}
[/code]

@layout/activity_main.xml

[code language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<com.tpinho.customviews.ui.custom.BlinkButton
android:id="@+id/button_main_click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/click" />

</LinearLayout>
[/code]

Outra opção é criar um agrupamento de Views no Android utilizando o ViewGroup ou classes filhas, como LinearLayout, RelativeLayout, GridLayout e etc.

Para exemplificar, vamos criar uma view customizada para exibição de valores monetários, na qual o símbolo de moeda tem um tamanho diferente do tamanho definido para o valor e o valor já é inserido formatado (utilizei a biblioteca Canarinho) no TextView.

@layout/view_money.xml

[code language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:id="@+id/text_money_currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
tools:text="R$" />

<TextView
android:id="@+id/text_money_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:textSize="20sp"
tools:text="10.000,00" />

</LinearLayout>
[/code]

@MoneyView.java

[code language=”java”]
package com.tpinho.customviews.ui.custom;

import android.annotation.TargetApi;
import android.content.Context;
import android.support.annotation.StringRes;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.tpinho.customviews.R;

import br.com.concretesolutions.canarinho.formatador.FormatadorValor;

import static android.os.Build.VERSION_CODES.LOLLIPOP;

public class MoneyView extends LinearLayout {

private TextView textMoneyCurrency;
private TextView textMoneyValue;

public MoneyView(Context context) {
super(context);
init();
}

public MoneyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public MoneyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@TargetApi(LOLLIPOP)
public MoneyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}

private void init() {
inflate(getContext(), R.layout.view_money, this);

textMoneyCurrency = (TextView) findViewById(R.id.text_money_currency);
textMoneyValue = (TextView) findViewById(R.id.text_money_value);

setCurrency(R.string.default_currency);
setValue(R.string.default_value);
}

public void setCurrency(@StringRes int text) {
textMoneyCurrency.setText(text);
}

public void setCurrency(String text) {
textMoneyCurrency.setText(text);
}

public void setValue(@StringRes int value) {
setValue(getResources().getString(value));
}

public void setValue(String value) {
textMoneyValue.setText(FormatadorValor.VALOR.formata(value));
}

}
[/code]

Podemos incluir a MoneyView diretamente no xml correspondente ao layout:

@layout/activity_main.xml

[code language=”xml”]

<com.tpinho.customviews.ui.custom.MoneyView
android:id="@+id/money_main_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />


[/code]

@MainActivity.java

[code language=”java”]

final MoneyView moneyView = (MoneyView) findViewById(R.id.money_main_price);
moneyView.setValue("15000");


[/code]

Ou podemos adicionar a MoneyView programaticamente:

[code language=”java”]
MoneyView moneyView = new MoneyView(context);
view.addView(moneyView);
[/code]

E é isso! O projeto de exemplo pode ser encontrado neste repositório do Github. Ficou alguma dúvida ou tem alguma sugestão? Aproveite os campos abaixo!

Na segunda parte desse post, vou mostrar como criar os atributos customizados para que seja possível definir valores para a view customizada direto do xml. Até lá!