package rjsv.floatingmenu.animation.handlers; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.graphics.Point; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; import java.util.ArrayList; import java.util.Collections; import java.util.List; import rjsv.floatingmenu.animation.enumerators.AnimationType; import rjsv.floatingmenu.animation.enumerators.MenuState; import rjsv.floatingmenu.animation.listeners.FloatingMenuButtonAnimationListener; import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; /** * Description * * @author <a href="mailto:[email protected]">RJSV</a> * @version $Revision : 1 $ */ /** * An example animation handler * Animates translation, rotation, scale and alpha at the same time using Property Animation APIs. */ public class FloatingMenuAnimationHandler extends AnimationHandler implements Animator.AnimatorListener { private int openingDuration = 500; private int closingDuration = 500; private int lagBetweenItems = 100; private boolean animating; private FloatingMenuButtonAnimationListener menuButtonAnimationListener; private Interpolator openingInterpolator; private Interpolator closingInterpolator; private boolean shouldRotate = true; private boolean shouldFade = true; private boolean shouldScale = true; private FloatingMenuButton floatingMenuButton; private MenuState currentState; private AnimatorSet openingAnimation; private AnimatorSet closingAnimation; // Constructors public FloatingMenuAnimationHandler(FloatingMenuButton floatingMenuButton) { setAnimating(false); this.floatingMenuButton = floatingMenuButton; this.openingInterpolator = new OvershootInterpolator(0.9f); this.closingInterpolator = new AccelerateDecelerateInterpolator(); } // Overridden Methods @Override public void animateMenuOpening(Point center, AnimationType animationType) { super.animateMenuOpening(center, animationType); currentState = animationType == AnimationType.RADIAL ? MenuState.OPENING_RADIAL : MenuState.OPENING; openingAnimation = currentState == MenuState.OPENING_RADIAL ? openRadialMenuAnimation(center) : openMenuAnimation(center); openingAnimation.start(); } @Override public void animateMenuClosing(Point center, AnimationType animationType) { super.animateMenuClosing(center, animationType); currentState = animationType == AnimationType.RADIAL ? MenuState.CLOSING_RADIAL : MenuState.CLOSING; closingAnimation = currentState == MenuState.CLOSING_RADIAL ? closeRadialMenuAnimation(center) : closeMenuAnimation(center); closingAnimation.start(); } @Override public void animateMenuReOpening(Point center) { super.animateMenuReOpening(center); animateMenuClosing(center, AnimationType.EXPAND); currentState = MenuState.REOPENING; } @Override public boolean isAnimating() { return animating; } @Override public void setAnimating(boolean animating) { this.animating = animating; if (!this.animating && currentState != MenuState.REOPENING) { currentState = MenuState.IDLE; } } // Expand animation private AnimatorSet openMenuAnimation(Point center) { setAnimating(true); Animator lastAnimation = null; List<SubButton> subActionItems = menu.getSubMenuButtons(); ArrayList<Animator> animatorArrayList = new ArrayList<>(); for (int i = 0; i < subActionItems.size(); i++) { SubButton currentSubButton = subActionItems.get(i); ArrayList<PropertyValuesHolder> properties = new ArrayList<>(); properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, currentSubButton.getX() - center.x + currentSubButton.getWidth() / 2)); properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, currentSubButton.getY() - center.y + currentSubButton.getHeight() / 2)); if (shouldRotate) { properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, 720)); } if (shouldScale) { properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 1)); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 1)); } if (shouldFade) { properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 1)); } PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; parameters = properties.toArray(parameters); final ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(currentSubButton.getView(), parameters); animation.setDuration(openingDuration); animation.setInterpolator(openingInterpolator); menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.OPENING); animation.addListener(menuButtonAnimationListener); if (i == 0) { lastAnimation = animation; } animation.setStartDelay((subActionItems.size() - i) * lagBetweenItems); animatorArrayList.add(animation); } if (lastAnimation != null) { lastAnimation.addListener(this); } AnimatorSet openAnimatorSet = new AnimatorSet(); openAnimatorSet.playTogether(animatorArrayList); return openAnimatorSet; } private AnimatorSet closeMenuAnimation(Point center) { setAnimating(true); Animator lastAnimation = null; ArrayList<Animator> animatorArrayList = new ArrayList<>(); // We reverse the list to have the animation was intended ArrayList<SubButton> reverseSubActionItems = new ArrayList<>(menu.getSubMenuButtons()); Collections.reverse(reverseSubActionItems); for (int i = 0; i < reverseSubActionItems.size(); i++) { SubButton currentSubButton = reverseSubActionItems.get(i); ArrayList<PropertyValuesHolder> properties = new ArrayList<>(); properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -(currentSubButton.getX() - center.x + currentSubButton.getWidth() / 2))); properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -(currentSubButton.getY() - center.y + currentSubButton.getHeight() / 2))); if (shouldRotate) { properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, -720)); } if (shouldScale) { properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 0)); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 0)); } if (shouldFade) { properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 0)); } PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; parameters = properties.toArray(parameters); ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(currentSubButton.getView(), parameters); animation.setDuration(closingDuration); animation.setInterpolator(closingInterpolator); menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.CLOSING); animation.addListener(menuButtonAnimationListener); if (i == 0) { lastAnimation = animation; } animation.setStartDelay((reverseSubActionItems.size() - i) * lagBetweenItems); animatorArrayList.add(animation); } if (lastAnimation != null) { lastAnimation.addListener(this); } AnimatorSet closeAnimatorSet = new AnimatorSet(); closeAnimatorSet.playTogether(animatorArrayList); return closeAnimatorSet; } // Radial Animation private AnimatorSet openRadialMenuAnimation(Point center) { setAnimating(true); Animator lastAnimation = null; int startAngle = floatingMenuButton.getStartAngle(); int endAngle = floatingMenuButton.getEndAngle(); int radius = floatingMenuButton.getRadius(); List<SubButton> subActionItems = menu.getSubMenuButtons(); ArrayList<Animator> animatorArrayList = new ArrayList<>(); for (int i = 0; i < subActionItems.size(); i++) { final SubButton currentSubButton = subActionItems.get(i); final View currentSubButtonView = currentSubButton.getView(); // reset the sub button's properties resetSubButton(center, currentSubButton, currentSubButtonView); // Button Properties along the animation ArrayList<PropertyValuesHolder> properties = new ArrayList<>(); properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, 720)); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 1)); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 1)); properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 1)); PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; parameters = properties.toArray(parameters); ObjectAnimator propertiesAnimation = ObjectAnimator.ofPropertyValuesHolder(currentSubButtonView, parameters); // Button Position along the animation final ArrayList<Point> radialPointsPorCurrentPosition = getArcPointsForButton(center, startAngle, endAngle, subActionItems.size(), i, radius); ValueAnimator positionAnimation = ValueAnimator.ofFloat(0, radialPointsPorCurrentPosition.size()); positionAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float value = ((Float) animation.getAnimatedValue()); if (value.intValue() < radialPointsPorCurrentPosition.size()) { Point p = radialPointsPorCurrentPosition.get(value.intValue()); currentSubButtonView.setX(p.x); currentSubButtonView.setY(p.y); currentSubButton.setX(p.x); currentSubButton.setY(p.y); } } }); positionAnimation.setDuration(openingDuration); positionAnimation.setInterpolator(openingInterpolator); menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.OPENING_RADIAL); positionAnimation.addListener(menuButtonAnimationListener); if (i == 0) { lastAnimation = positionAnimation; } animatorArrayList.add(propertiesAnimation); animatorArrayList.add(positionAnimation); } if (lastAnimation != null) { lastAnimation.addListener(this); } AnimatorSet openRadialAnimatorSet = new AnimatorSet(); openRadialAnimatorSet.playTogether(animatorArrayList); return openRadialAnimatorSet; } private AnimatorSet closeRadialMenuAnimation(Point center) { setAnimating(true); Animator lastAnimation = null; int startAngle = floatingMenuButton.getStartAngle(); int endAngle = floatingMenuButton.getEndAngle(); int radius = floatingMenuButton.getRadius(); List<SubButton> subActionItems = menu.getSubMenuButtons(); ArrayList<Animator> animatorArrayList = new ArrayList<>(); for (int i = 0; i < subActionItems.size(); i++) { final SubButton currentSubButton = subActionItems.get(i); final View currentSubButtonView = currentSubButton.getView(); // Button Properties along the animation ArrayList<PropertyValuesHolder> properties = new ArrayList<>(); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, (float) 0.75)); properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, (float) 0.75)); PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; parameters = properties.toArray(parameters); ObjectAnimator propertiesAnimation = ObjectAnimator.ofPropertyValuesHolder(currentSubButtonView, parameters); // Button Position along the animation final ArrayList<Point> radialPointsPorCurrentPosition = getArcPointsForButton(center, startAngle, endAngle, subActionItems.size(), i, radius); Collections.reverse(radialPointsPorCurrentPosition); ValueAnimator positionAnimation = ValueAnimator.ofFloat(0, radialPointsPorCurrentPosition.size()); positionAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float value = ((Float) animation.getAnimatedValue()); if (value.intValue() < radialPointsPorCurrentPosition.size()) { Point p = radialPointsPorCurrentPosition.get(value.intValue()); currentSubButtonView.setX(p.x); currentSubButtonView.setY(p.y); currentSubButton.setX(p.x); currentSubButton.setY(p.y); } } }); positionAnimation.setDuration(closingDuration); positionAnimation.setInterpolator(closingInterpolator); menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.CLOSING_RADIAL); positionAnimation.addListener(menuButtonAnimationListener); if (i == 0) { lastAnimation = positionAnimation; } animatorArrayList.add(propertiesAnimation); animatorArrayList.add(positionAnimation); } if (lastAnimation != null) { lastAnimation.addListener(this); } AnimatorSet closeRadialAnimatorSet = new AnimatorSet(); closeRadialAnimatorSet.playTogether(animatorArrayList); return closeRadialAnimatorSet; } public void cancelMenuAnimations() { if (openingAnimation != null) { openingAnimation.cancel(); openingAnimation = null; } if (closingAnimation != null) { closingAnimation.cancel(); closingAnimation = null; } } // Configuration Methods public FloatingMenuAnimationHandler setOpeningInterpolator(Interpolator interpolator) { this.openingInterpolator = interpolator; return this; } public FloatingMenuAnimationHandler setClosingInterpolator(Interpolator interpolator) { this.closingInterpolator = interpolator; return this; } public FloatingMenuAnimationHandler setOpeningAnimationDuration(int openingDuration) { this.openingDuration = openingDuration; return this; } public FloatingMenuAnimationHandler setClosingAnimationDuration(int closingDuration) { this.closingDuration = closingDuration; return this; } public FloatingMenuAnimationHandler setLagBetweenItems(int duration) { this.lagBetweenItems = duration; return this; } public FloatingMenuAnimationHandler shouldRotate(boolean value) { this.shouldRotate = value; return this; } public FloatingMenuAnimationHandler shouldFade(boolean value) { this.shouldFade = value; return this; } public FloatingMenuAnimationHandler shouldScale(boolean value) { this.shouldScale = value; return this; } // Animator Interface Listener @Override public void onAnimationStart(Animator animation) { setAnimating(true); } @Override public void onAnimationEnd(Animator animation) { setAnimating(false); if (MenuState.REOPENING == currentState) { cancelMenuAnimations(); floatingMenuButton.openMenu(); } } @Override public void onAnimationCancel(Animator animation) { setAnimating(false); } @Override public void onAnimationRepeat(Animator animation) { setAnimating(true); } // General Methods private void resetSubButton(Point center, SubButton subButton, View subButtonView) { if (subButton != null) { int floatingMenuButtonWidth = floatingMenuButton.getWidth(); int floatingMenuButtonHeight = floatingMenuButton.getHeight(); int xResetPos = center.x + floatingMenuButtonWidth / 2; int yResetPos = center.y - floatingMenuButtonHeight / 2; subButtonView.setX(xResetPos); subButtonView.setY(yResetPos); subButtonView.setScaleX(0); subButtonView.setScaleY(0); subButton.setX(xResetPos); subButton.setY(yResetPos); subButton.setAlpha(0); } } private ArrayList<Point> getArcPointsForButton(Point center, int minAngle, int maxAngle, int numberOfButtons, int buttonIndex, int radius) { ArrayList<Point> points = new ArrayList<>(); // Counter the clockwise default for the angles int startAngle = 360 - minAngle; int endAngle = 360 - maxAngle; double radDegree = Math.toRadians(startAngle); if (!(Math.abs(endAngle - startAngle) >= 360 || numberOfButtons <= 1)) { numberOfButtons = numberOfButtons - 1; } SubButton currentButton = menu.getSubMenuButtons().get(buttonIndex); int currentButtonCenterX = currentButton.getView().getWidth() / 2; int currentButtonCenterY = currentButton.getView().getHeight() / 2; int angleDistributionValue = Math.abs(endAngle - startAngle) / numberOfButtons; for (int j = 0; j < radius; j++) { Point p = new Point(); p.x = center.x + (int) (j * Math.cos(radDegree)) - currentButtonCenterX; p.y = center.y - (int) (j * Math.sin(radDegree)) - currentButtonCenterY; points.add(p); } for (int i = startAngle; i <= startAngle + buttonIndex * angleDistributionValue; i++) { Point p = new Point(); radDegree = Math.toRadians(i); p.x = center.x + (int) (radius * Math.cos(radDegree)) - currentButtonCenterX; p.y = center.y - (int) (radius * Math.sin(radDegree)) - currentButtonCenterY; points.add(p); } return points; } }