Android: Material Card View Animation — Pt 2 (Reveal)

Guilherme Ramos
Sep 5, 2018 · 6 min read

Olá pessoal!

Reveal Layout em ação

Bom, vamos a segunda parte de animações do meu componente favorito. Dessa vez irei passar como faço aquela animação de revelar uma view.

Obs 1: Essa animação é possível fazer em qualquer ViewGroup, não apenas em CardView.

Obs 2: Essa animação é para api > 21 (lollipop), na dúvida, faça uma verificação se é api > 21 e se não for, ao invés de usar o reveal, use o fade animation ;)

Supondo que nós temos o seguinte layout dentro de um layout raiz qualquer

<android.support.design.card.MaterialCardView
android:id="@+id/main_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:layout_margin="8dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true"
>

<FrameLayout
android:id="@+id/child1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:visibility="visible"
/>
<FrameLayout
android:id="@+id/child2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:visibility="invisible"
/>

</android.support.design.card.MaterialCardView>

Supondo também que declaramos as views na Activity da seguinte forma:

private View child1; //id = child1 do xml
private View child2; //id = child2 do xml

A animação de revelar, pode ser escrita da seguinte forma:

final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
child2, //view a ser revelada
child2.getWidth() / 2, //ponto x inicial
child2.getHeight() / 2, //ponto y inicial
0, //raio inicial e raio final abaixo
(float) Math.hypot(child2.getWidth(), child2.getHeight()));
circularReveal.setDuration(750);
circularReveal.start();

E para esconder:

final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
child2, //view a ser revelada
child2.getWidth() / 2, //ponto x inicial
child2.getHeight() / 2, //ponto y inicial
(float) Math.hypot(child2.getWidth(), child2.getHeight(),
0);
circularReveal.setDuration(750);
circularReveal.start();

Ou seja, a diferença entre mostrar e esconder é simplesmente o ponto no qual a animação inicia e finaliza (2 últimos parâmetros do createCircularReveal)

Em resumo, para criar a animação, é necessário que a view inicial esteja configurada para ser visível, e a view que será revelada esteja invisível. Após isso, chama-se o createCircularReveal com os parâmetros da view que será exibida/escondida, ou seja, as views serão sobrepostas, então não se esqueça de configurar uma cor de fundo para cada view.

Simples!!

Como da última vez, escrevi uma classe completinha para ajudar a criar tais animações. Bora lá!

Seguindo uma lógica, as coisas que precisamos ter no código pra criar essa animação é:

  • Tempo da animação (long ANIM_DURATION)
  • Ponto na qual a animação começa e termina (enum GRAVITY)
  • Saber quando a animação começa e quando ela termina (listener)

Para escolher o ponto em qual a animação começa e termina de forma dinâmica, criei um enum chamado GRAVITY, na qual fará os cálculos de onde a animação começa e termina. O enum ficou dessa forma:

public enum GRAVITY {
TOP_CENTER, TOP_LEFT, TOP_RIGHT,
CENTER, CENTER_LEFT, CENTER_RIGHT,
BOTTOM_CENTER, BOTTOM_LEFT, BOTTOM_RIGHT
}

E para os cálculos, um método que me retorna o ponto que desejo começar/terminar a animação:

private float getX(View v) {

switch (gravity) {
case CENTER:
case TOP_CENTER:
case BOTTOM_CENTER:
return v.getWidth() / 2;
case TOP_LEFT:
case BOTTOM_LEFT:
case CENTER_LEFT:
return 0;
case TOP_RIGHT:
case BOTTOM_RIGHT:
case CENTER_RIGHT:
return v.getWidth();
default:
}

return 0;
}
private float getY(View v) {

switch (gravity) {
case CENTER:
case CENTER_LEFT:
case CENTER_RIGHT:
return v.getHeight() / 2;
case TOP_CENTER:
case TOP_LEFT:
case TOP_RIGHT:
return 0;
case BOTTOM_CENTER:
case BOTTOM_RIGHT:
case BOTTOM_LEFT:
return v.getHeight();
default:
}

return 0;
}

Quanto a interface, montei um listener que dispara o início e o final da animação:

public interface Action {
void onStart();

void onEnd();
}

Para disparar a animação de revelar e esconder, criei o seguinte código:

//exibindo/revelando
private void dispatchShowReveal(final View toShow, final Action action) {
if (isLessThan21())
return;
int posX = (int) getX(toShow);
int posY = (int) getY(toShow);

final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
toShow,
posX,
posY,
0,
(float) Math.hypot(toShow.getWidth(), toShow.getHeight()));

circularReveal.setInterpolator(new FastOutSlowInInterpolator());

circularReveal.setDuration(ANIM_DURATION);

circularReveal.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
toShow.setVisibility(View.VISIBLE);
if (action != null)
action.onStart();
}

@Override
public void onAnimationEnd(Animator animator) {
if (action != null)
action.onEnd();
}

@Override
public void onAnimationCancel(Animator animator) {

}

@Override
public void onAnimationRepeat(Animator animator) {

}
});
circularReveal.start();
}
//escondendo
private void
dispatchHideReveal(final View toHide, final Action action) {
//verifico se a api > 21
if (isLessThan21())
return;

//obtendo as posições de inicio em x e y
int posX = (int) getX(toHide);
int posY = (int) getY(toHide);


final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
toHide,
posX,
posY,
(float) Math.hypot(toHide.getWidth(), toHide.getHeight()),
0);

circularReveal.setInterpolator(new FastOutSlowInInterpolator());

circularReveal.setDuration(ANIM_DURATION);

circularReveal.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
if (action != null)
action.onStart();
}

@Override
public void onAnimationEnd(Animator animator) {
toHide.setVisibility(View.INVISIBLE);
if (action != null)
action.onEnd();
}

@Override
public void onAnimationCancel(Animator animator) {

}

@Override
public void onAnimationRepeat(Animator animator) {

}
});
circularReveal.start();
}

E como está em uma classe a parte, criei os métodos públicos para que possam ser chamados de outras classes:

public void hide(final View toHide, final Action action) {
dispatchHideReveal(toHide, action);
}

public void show(final View toShow, final Action action) {
dispatchShowReveal(toShow, action);
}

Sendo assim, a classe ficou da seguinte maneira:

public class MaterialRevealHelper {

private long ANIM_DURATION = 750;
private GRAVITY gravity = GRAVITY.CENTER;

public enum GRAVITY {
TOP_CENTER, TOP_LEFT, TOP_RIGHT,
CENTER, CENTER_LEFT, CENTER_RIGHT,
BOTTOM_CENTER, BOTTOM_LEFT, BOTTOM_RIGHT
}

//configurando um tempo de animação personalizado
public void setAnimDuration(long duration) {
ANIM_DURATION = duration;
}

//verificando se a view está sendo exibida
public boolean isShow(View v) {
return v.getVisibility() == View.VISIBLE;
}

//configurando a gravity da animação
public void setGravity(GRAVITY gravity) {
this.gravity = gravity;
}

//escondendo
public void hide(final View toHide, final Action action) {
dispatchHideReveal(toHide, action);
}

//exibindo
public void show(final View toShow, final Action action) {
dispatchShowReveal(toShow, action);
}

private void dispatchHideReveal(final View toHide, final Action action) {
if (isLessThan21())
return;

int posX = (int) getX(toHide);
int posY = (int) getY(toHide);


final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
toHide,
posX,
posY,
(float) Math.hypot(toHide.getWidth(), toHide.getHeight()),
0);

circularReveal.setInterpolator(new FastOutSlowInInterpolator());

circularReveal.setDuration(ANIM_DURATION);

circularReveal.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
if (action != null)
action.onStart();
}

@Override
public void onAnimationEnd(Animator animator) {
toHide.setVisibility(View.INVISIBLE);
if (action != null)
action.onEnd();
}

@Override
public void onAnimationCancel(Animator animator) {

}

@Override
public void onAnimationRepeat(Animator animator) {

}
});
circularReveal.start();
}

private void dispatchShowReveal(final View toShow, final Action action) {
if (isLessThan21())
return;
int posX = (int) getX(toShow);
int posY = (int) getY(toShow);

final Animator circularReveal = ViewAnimationUtils.createCircularReveal(
toShow,
posX,
posY,
0,
(float) Math.hypot(toShow.getWidth(), toShow.getHeight()));

circularReveal.setInterpolator(new FastOutSlowInInterpolator());

circularReveal.setDuration(ANIM_DURATION);

circularReveal.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
toShow.setVisibility(View.VISIBLE);
if (action != null)
action.onStart();
}

@Override
public void onAnimationEnd(Animator animator) {
if (action != null)
action.onEnd();
}

@Override
public void onAnimationCancel(Animator animator) {

}

@Override
public void onAnimationRepeat(Animator animator) {

}
});
circularReveal.start();
}

private float getX(View v) {

switch (gravity) {
case CENTER:
case TOP_CENTER:
case BOTTOM_CENTER:
return v.getWidth() / 2;
case TOP_LEFT:
case BOTTOM_LEFT:
case CENTER_LEFT:
return 0;
case TOP_RIGHT:
case BOTTOM_RIGHT:
case CENTER_RIGHT:
return v.getWidth();
default:
}

return 0;
}

private float getY(View v) {

switch (gravity) {
case CENTER:
case CENTER_LEFT:
case CENTER_RIGHT:
return v.getHeight() / 2;
case TOP_CENTER:
case TOP_LEFT:
case TOP_RIGHT:
return 0;
case BOTTOM_CENTER:
case BOTTOM_RIGHT:
case BOTTOM_LEFT:
return v.getHeight();
default:
}

return 0;
}

public interface Action {
void onStart();

void onEnd();
}

//verificando se api é inferior a 21
private boolean isLessThan21() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
}

}

Pronto. Para usar, é da seguinte forma:

MaterialRevealHelper reveal = new MaterialRevealHelper();
reveal.setAnimDuration(1000); //opcional
reveal.setGravity(GRAVITY.CENTER); //opcional
reveal.show(mChild2, new MaterialRevealHelper.Action() {
@Override
public void onStart() {
// caso queira que execute algo ao iniciar a animação
}

@Override
public void onEnd() {
// caso queira que execute algo no fim da animação
}
});

Fim!

Segue um exemplo que de animação em que usei o reveal de modo mais “robusto”.

Exemplo do uso do reveal layout (iBuild App)

Enfim, esse foi mais um tópico sobre animações. Espero que tenham gostado :)

Obrigado e até a próxima!

Guilherme Ramos

Written by

Desenvolvedor Android no iFood. #foodLover, apaixonado por design, animações e arquitetura de projetos mobile

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade