package org.aisen.weibo.sina.ui.widget.sheetfab.animations;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.os.Build;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import org.aisen.weibo.sina.ui.widget.io.codetail.animation.SupportAnimator;
import org.aisen.weibo.sina.ui.widget.sheetfab.MaterialSheetFab.RevealXDirection;
import org.aisen.weibo.sina.ui.widget.sheetfab.MaterialSheetFab.RevealYDirection;
import java.lang.reflect.Method;
/**
* Created by Gordon Wong on 7/5/2015.
*
* Animates the material sheet into and out of view.
*/
public class MaterialSheetAnimation {
private static final String SUPPORT_CARDVIEW_CLASSNAME = "android.support.v7.widget.CardView";
private static final int SHEET_REVEAL_OFFSET_Y = 5;
private View sheet;
private int sheetColor;
private int fabColor;
private Interpolator interpolator;
private RevealXDirection revealXDirection;
private RevealYDirection revealYDirection;
private Method setCardBackgroundColor;
private boolean isSupportCardView;
public MaterialSheetAnimation(View sheet, int sheetColor, int fabColor,
Interpolator interpolator) {
this.sheet = sheet;
this.sheetColor = sheetColor;
this.fabColor = fabColor;
this.interpolator = interpolator;
// Default reveal direction is up and to the left (for FABs in the bottom right corner)
revealXDirection = RevealXDirection.LEFT;
revealYDirection = RevealYDirection.UP;
isSupportCardView = sheet.getClass().getName().equals(SUPPORT_CARDVIEW_CLASSNAME);
// Get setCardBackgroundColor() method
if (isSupportCardView) {
try {
// noinspection unchecked
setCardBackgroundColor = sheet.getClass()
.getDeclaredMethod("setCardBackgroundColor", int.class);
} catch (Exception e) {
setCardBackgroundColor = null;
}
}
}
/**
* Aligns the sheet's position with the FAB.
*
* @param fab Floating action button
*/
public void alignSheetWithFab(View fab) {
// NOTE: View.getLocationOnScreen() returns the view's coordinates on the screen
// whereas other view methods like getRight() and getY() return coordinates relative
// to the view's parent. Using those methods can lead to incorrect calculations when
// the two views do not have the same parent.
// Get FAB's coordinates
int[] fabCoords = new int[2];
fab.getLocationOnScreen(fabCoords);
// Get sheet's coordinates
int[] sheetCoords = new int[2];
sheet.getLocationOnScreen(sheetCoords);
// NOTE: Use the diffs between the positions of the FAB and sheet to align the sheet.
// We have to use the diffs because the coordinates returned by getLocationOnScreen()
// include the status bar and any other system UI elements, meaning the coordinates
// aren't representative of the usable screen space.
int leftDiff = sheetCoords[0] - fabCoords[0];
int rightDiff = (sheetCoords[0] + sheet.getWidth()) - (fabCoords[0] + fab.getWidth());
int topDiff = sheetCoords[1] - fabCoords[1];
int bottomDiff = (sheetCoords[1] + sheet.getHeight()) - (fabCoords[1] + fab.getHeight());
// NOTE: Preserve the sheet's margins to allow users to shift the sheet's position
// to compensate for the fact that the design support library's FAB has extra
// padding within the view
ViewGroup.MarginLayoutParams sheetLayoutParams = (ViewGroup.MarginLayoutParams) sheet
.getLayoutParams();
// Set sheet's new coordinates (only if there is a change in coordinates because
// setting the same coordinates can cause the view to "drift" - moving 0.5 to 1 pixels
// around the screen)
if (rightDiff != 0) {
float sheetX = sheet.getX();
// Align the right side of the sheet with the right side of the FAB if
// doing so would not move the sheet off the screen
if (rightDiff <= sheetX) {
sheet.setX(sheetX - rightDiff - sheetLayoutParams.rightMargin);
revealXDirection = RevealXDirection.LEFT;
}
// Otherwise, align the left side of the sheet with the left side of the FAB
else if (leftDiff != 0 && leftDiff <= sheetX) {
sheet.setX(sheetX - leftDiff + sheetLayoutParams.leftMargin);
revealXDirection = RevealXDirection.RIGHT;
}
}
if (bottomDiff != 0) {
float sheetY = sheet.getY();
// Align the bottom of the sheet with the bottom of the FAB
if (bottomDiff <= sheetY) {
sheet.setY(sheetY - bottomDiff - sheetLayoutParams.bottomMargin);
revealYDirection = RevealYDirection.UP;
}
// Otherwise, align the top of the sheet with the top of the FAB
else if (topDiff != 0 && topDiff <= sheetY) {
sheet.setY(sheetY - topDiff + sheetLayoutParams.topMargin);
revealYDirection = RevealYDirection.DOWN;
}
}
}
/**
* Shows the sheet by morphing the FAB into the sheet.
*
* @param fab Floating action button
* @param showSheetDuration Duration of the sheet animation in milliseconds. Use 0 for no
* animation.
* @param showSheetColorDuration Duration of the color animation in milliseconds. Use 0 for no
* animation.
* @param listener Listener for animation events.
*/
public void morphFromFab(View fab, long showSheetDuration, long showSheetColorDuration,
AnimationListener listener) {
sheet.setVisibility(View.VISIBLE);
revealSheetWithFab(fab, getFabRevealRadius(fab), getSheetRevealRadius(), showSheetDuration,
fabColor, sheetColor, showSheetColorDuration, listener);
}
/**
* Hides the sheet by morphing the sheet into the FAB.
*
* @param fab Floating action button
* @param hideSheetDuration Duration of the sheet animation in milliseconds. Use 0 for no
* animation.
* @param hideSheetColorDuration Duration of the color animation in milliseconds. Use 0 for no
* animation.
* @param listener Listener for animation events.
*/
public void morphIntoFab(View fab, long hideSheetDuration, long hideSheetColorDuration,
AnimationListener listener) {
revealSheetWithFab(fab, getSheetRevealRadius(), getFabRevealRadius(fab), hideSheetDuration,
sheetColor, fabColor, hideSheetColorDuration, listener);
}
protected void revealSheetWithFab(View fab, float startRadius, float endRadius,
long sheetDuration, int startColor, int endColor, long sheetColorDuration,
AnimationListener listener) {
if (listener != null) {
listener.onStart();
}
// Pass listener to the animation that will be the last to finish
AnimationListener revealListener = (sheetDuration >= sheetColorDuration) ? listener : null;
AnimationListener colorListener = (sheetColorDuration > sheetDuration) ? listener : null;
// Start animations
startCircularRevealAnim(sheet, getSheetRevealCenterX(), getSheetRevealCenterY(fab),
startRadius, endRadius, sheetDuration, interpolator, revealListener);
startColorAnim(sheet, startColor, endColor, sheetColorDuration, interpolator,
colorListener);
}
protected void startCircularRevealAnim(View view, int centerX, int centerY, float startRadius,
float endRadius, long duration, Interpolator interpolator,
final AnimationListener listener) {
// Use native circular reveal on Android 5.0+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Native circular reveal uses coordinates relative to the view
int relativeCenterX = (int) (centerX - view.getX());
int relativeCenterY = (int) (centerY - view.getY());
// Setup animation
Animator anim = ViewAnimationUtils.createCircularReveal(view, relativeCenterX,
relativeCenterY, startRadius, endRadius);
anim.setDuration(duration);
anim.setInterpolator(interpolator);
// Add listener
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (listener != null) {
listener.onStart();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (listener != null) {
listener.onEnd();
}
}
});
// Start animation
anim.start();
} else {
// Circular reveal library uses absolute coordinates
// Setup animation
SupportAnimator anim = org.aisen.weibo.sina.ui.widget.io.codetail.animation.ViewAnimationUtils
.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
anim.setDuration((int) duration);
anim.setInterpolator(interpolator);
// Add listener
anim.addListener(new SupportAnimator.SimpleAnimatorListener() {
@Override
public void onAnimationStart() {
if (listener != null) {
listener.onStart();
}
}
@Override
public void onAnimationEnd() {
if (listener != null) {
listener.onEnd();
}
}
});
// Start animation
anim.start();
}
}
protected void startColorAnim(final View view, final int startColor, final int endColor,
long duration, Interpolator interpolator, final AnimationListener listener) {
// Setup animation
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
anim.setDuration(duration);
anim.setInterpolator(interpolator);
// Add listeners
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (listener != null) {
listener.onStart();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (listener != null) {
listener.onEnd();
}
}
});
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
// Update background color
Integer color = (Integer) animator.getAnimatedValue();
// Use CardView.setCardBackgroundColor() to avoid crashes on Android < 5.0 and to
// properly set the card's background color without removing the card's other styles
// See https://github.com/gowong/material-sheet-fab/pull/2 and
// https://code.google.com/p/android/issues/detail?id=77843
if (isSupportCardView) {
// Use setCardBackground() method if it is available
if (setCardBackgroundColor != null) {
try {
setCardBackgroundColor.invoke(sheet, color);
} catch (Exception e) {
// Ignore exceptions since there's no other way set a support CardView's
// background color
}
}
}
// Set background color for all other views
else {
view.setBackgroundColor(color);
}
}
});
// Start animation
anim.start();
}
public void setSheetVisibility(int visibility) {
sheet.setVisibility(visibility);
}
public boolean isSheetVisible() {
return sheet.getVisibility() == View.VISIBLE;
}
/**
* @return Sheet reveal's center X coordinate
*/
public int getSheetRevealCenterX() {
return (int) (sheet.getX() + (sheet.getWidth() / 2));
}
/**
* @return Sheet reveal's center Y coordinate
*/
public int getSheetRevealCenterY(View fab) {
if (revealYDirection == RevealYDirection.UP) {
return (int) (sheet.getY()
+ (sheet.getHeight() * (SHEET_REVEAL_OFFSET_Y - 1) / SHEET_REVEAL_OFFSET_Y)
- (fab.getHeight() / 2));
}
return (int) (sheet.getY() + (sheet.getHeight() / SHEET_REVEAL_OFFSET_Y)
+ (fab.getHeight() / 2));
}
protected float getSheetRevealRadius() {
return Math.max(sheet.getWidth(), sheet.getHeight());
}
protected float getFabRevealRadius(View fab) {
return Math.max(fab.getWidth(), fab.getHeight()) / 2;
}
public RevealXDirection getRevealXDirection() {
return revealXDirection;
}
public RevealYDirection getRevealYDirection() {
return revealYDirection;
}
}