Android: Material Card View Animation — Pt 2 (Reveal)
Olá pessoal!

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 xmlA 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”.

Enfim, esse foi mais um tópico sobre animações. Espero que tenham gostado :)
Obrigado e até a próxima!
