package com.yalantis.guillotine.animation; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.os.Build; import android.view.View; import android.view.ViewTreeObserver; import com.yalantis.guillotine.interfaces.GuillotineListener; import com.yalantis.guillotine.util.ActionBarInterpolator; import com.yalantis.guillotine.util.GuillotineInterpolator; /** * Created by Dmytro Denysenko on 5/6/15. */ public class GuillotineAnimation { private static final String ROTATION = "rotation"; private static final float GUILLOTINE_CLOSED_ANGLE = -90f; private static final float GUILLOTINE_OPENED_ANGLE = 0f; private static final int DEFAULT_DURATION = 625; private static final float ACTION_BAR_ROTATION_ANGLE = 3f; private final View mGuillotineView; private final long mDuration; private final ObjectAnimator mOpeningAnimation; private final ObjectAnimator mClosingAnimation; private final GuillotineListener mListener; private final boolean mIsRightToLeftLayout; private final TimeInterpolator mInterpolator; private final View mActionBarView; private final long mDelay; private boolean isOpening; private boolean isClosing; private GuillotineAnimation(GuillotineBuilder builder) { this.mActionBarView = builder.actionBarView; this.mListener = builder.guillotineListener; this.mGuillotineView = builder.guillotineView; this.mDuration = builder.duration > 0 ? builder.duration : DEFAULT_DURATION; this.mDelay = builder.startDelay; this.mIsRightToLeftLayout = builder.isRightToLeftLayout; this.mInterpolator = builder.interpolator == null ? new GuillotineInterpolator() : builder.interpolator; setUpOpeningView(builder.openingView); setUpClosingView(builder.closingView); this.mOpeningAnimation = buildOpeningAnimation(); this.mClosingAnimation = buildClosingAnimation(); if (builder.isClosedOnStart) { mGuillotineView.setRotation(GUILLOTINE_CLOSED_ANGLE); mGuillotineView.setVisibility(View.GONE); } //TODO handle right-to-left layouts //TODO handle landscape orientation } public void open() { if (!isOpening) { mOpeningAnimation.start(); } } public void close() { if (!isClosing) { mClosingAnimation.start(); } } private void setUpOpeningView(final View openingView) { if (mActionBarView != null) { mActionBarView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mActionBarView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { mActionBarView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } mActionBarView.setPivotX(calculatePivotX(openingView)); mActionBarView.setPivotY(calculatePivotY(openingView)); } }); } openingView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { open(); } }); } private void setUpClosingView(final View closingView) { mGuillotineView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mGuillotineView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { mGuillotineView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } mGuillotineView.setPivotX(calculatePivotX(closingView)); mGuillotineView.setPivotY(calculatePivotY(closingView)); } }); closingView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { close(); } }); } private ObjectAnimator buildOpeningAnimation() { ObjectAnimator rotationAnimator = initAnimator(ObjectAnimator.ofFloat(mGuillotineView, ROTATION, GUILLOTINE_CLOSED_ANGLE, GUILLOTINE_OPENED_ANGLE)); rotationAnimator.setInterpolator(mInterpolator); rotationAnimator.setDuration(mDuration); rotationAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mGuillotineView.setVisibility(View.VISIBLE); isOpening = true; } @Override public void onAnimationEnd(Animator animation) { isOpening = false; if (mListener != null) { mListener.onGuillotineOpened(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); return rotationAnimator; } private ObjectAnimator buildClosingAnimation() { ObjectAnimator rotationAnimator = initAnimator(ObjectAnimator.ofFloat(mGuillotineView, ROTATION, GUILLOTINE_OPENED_ANGLE, GUILLOTINE_CLOSED_ANGLE)); rotationAnimator.setDuration((long) (mDuration * GuillotineInterpolator.ROTATION_TIME)); rotationAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isClosing = true; mGuillotineView.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { isClosing = false; mGuillotineView.setVisibility(View.GONE); startActionBarAnimation(); if (mListener != null) { mListener.onGuillotineClosed(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); return rotationAnimator; } private void startActionBarAnimation() { ObjectAnimator actionBarAnimation = ObjectAnimator.ofFloat(mActionBarView, ROTATION, GUILLOTINE_OPENED_ANGLE, ACTION_BAR_ROTATION_ANGLE); actionBarAnimation.setDuration((long) (mDuration * (GuillotineInterpolator.FIRST_BOUNCE_TIME + GuillotineInterpolator.SECOND_BOUNCE_TIME))); actionBarAnimation.setInterpolator(new ActionBarInterpolator()); actionBarAnimation.start(); } private ObjectAnimator initAnimator(ObjectAnimator animator) { animator.setStartDelay(mDelay); return animator; } private float calculatePivotY(View burger) { return burger.getTop() + burger.getHeight() / 2; } private float calculatePivotX(View burger) { return burger.getLeft() + burger.getWidth() / 2; } public static class GuillotineBuilder { private final View guillotineView; private final View openingView; private final View closingView; private View actionBarView; private GuillotineListener guillotineListener; private long duration; private long startDelay; private boolean isRightToLeftLayout; private TimeInterpolator interpolator; private boolean isClosedOnStart; public GuillotineBuilder(View guillotineView, View closingView, View openingView) { this.guillotineView = guillotineView; this.openingView = openingView; this.closingView = closingView; } public GuillotineBuilder setActionBarViewForAnimation(View view) { this.actionBarView = view; return this; } public GuillotineBuilder setGuillotineListener(GuillotineListener guillotineListener) { this.guillotineListener = guillotineListener; return this; } public GuillotineBuilder setDuration(long duration) { this.duration = duration; return this; } public GuillotineBuilder setStartDelay(long startDelay) { this.startDelay = startDelay; return this; } public GuillotineBuilder setRightToLeftLayout(boolean isRightToLeftLayout) { this.isRightToLeftLayout = isRightToLeftLayout; return this; } public GuillotineBuilder setInterpolator(TimeInterpolator interpolator) { this.interpolator = interpolator; return this; } public GuillotineBuilder setClosedOnStart(boolean isClosedOnStart) { this.isClosedOnStart = isClosedOnStart; return this; } public GuillotineAnimation build() { return new GuillotineAnimation(this); } } }