package org.aisen.weibo.sina.ui.widget.sheetfab; import android.os.Build; import android.os.Handler; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import org.aisen.android.R; import org.aisen.weibo.sina.ui.widget.sheetfab.animations.AnimationListener; import org.aisen.weibo.sina.ui.widget.sheetfab.animations.FabAnimation; import org.aisen.weibo.sina.ui.widget.sheetfab.animations.MaterialSheetAnimation; import org.aisen.weibo.sina.ui.widget.sheetfab.animations.OverlayAnimation; import org.aisen.weibo.sina.ui.widget.io.codetail.animation.arcanimator.Side; /** * Created by Gordon Wong on 7/9/2015. * * Handles the interactions between the FAB and the material sheet that the FAB morphs into. */ public class MaterialSheetFab<FAB extends View & AnimatedFab> { // TODO (7/9/15): Remove platform-specific constants (currently needed because of the different // interpolators used) private static final boolean IS_LOLLIPOP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; private static final int ANIMATION_SPEED = 1; // Animation durations private static final int SHEET_ANIM_DURATION = (IS_LOLLIPOP ? 600 : 300) * ANIMATION_SPEED; private static final int SHOW_SHEET_COLOR_ANIM_DURATION = (int) (SHEET_ANIM_DURATION * 0.75); private static final int HIDE_SHEET_COLOR_ANIM_DURATION = IS_LOLLIPOP ? (int) (SHEET_ANIM_DURATION * 1.5) : (SHEET_ANIM_DURATION * 2); private static final int FAB_ANIM_DURATION = 300 * ANIMATION_SPEED; private static final int SHOW_OVERLAY_ANIM_DURATION = MaterialSheetFab.SHOW_SHEET_ANIM_DELAY + SHEET_ANIM_DURATION; private static final int HIDE_OVERLAY_ANIM_DURATION = SHEET_ANIM_DURATION; // Animation delays private static final int SHOW_SHEET_ANIM_DELAY = (int) (FAB_ANIM_DURATION * 0.5); private static final int MOVE_FAB_ANIM_DELAY = IS_LOLLIPOP ? (int) (SHEET_ANIM_DURATION * 0.3) : (int) (SHEET_ANIM_DURATION * 0.6); // Other animation constants private static final float FAB_SCALE_FACTOR = 0.6f; private static final int FAB_ARC_DEGREES = 0; // Views protected FAB fab; // Animations protected FabAnimation fabAnimation; protected MaterialSheetAnimation sheetAnimation; protected OverlayAnimation overlayAnimation; // State protected int anchorX; protected int anchorY; private boolean isShowing; private boolean isHiding; private boolean hideSheetAfterSheetIsShown; // Listeners private MaterialSheetFabEventListener eventListener; public enum RevealXDirection { LEFT, RIGHT } public enum RevealYDirection { UP, DOWN } /** * Creates a MaterialSheetFab instance and sets up the necessary click listeners. * * @param fab The FAB view. * @param sheet The sheet view. * @param overlay The overlay view. * @param sheetColor The background color of the material sheet. * @param fabColor The background color of the FAB. */ public MaterialSheetFab(FAB fab, View sheet, View overlay, int sheetColor, int fabColor) { Interpolator interpolator = AnimationUtils.loadInterpolator(sheet.getContext(), R.interpolator.msf_interpolator); this.fab = fab; // Create animations fabAnimation = new FabAnimation(fab, interpolator); sheetAnimation = new MaterialSheetAnimation(sheet, sheetColor, fabColor, interpolator); overlayAnimation = new OverlayAnimation(overlay, interpolator); // Set initial visibilities sheet.setVisibility(View.INVISIBLE); overlay.setVisibility(View.GONE); // Set listener to morph FAB into sheet when clicked fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showSheet(); } }); // Set listener to hide the sheet when touching the overlay overlay.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { // Only hide if the sheet is visible and if this is the first touch event if (isSheetVisible() && motionEvent.getAction() == MotionEvent.ACTION_DOWN) { hideSheet(); } return true; } }); // Set listener for when FAB view is laid out fab.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // Remove listener so that this is only called once MaterialSheetFab.this.fab.getViewTreeObserver() .removeGlobalOnLayoutListener(this); // Initialize FAB anchor when the FAB view is laid out updateFabAnchor(); } }); } /** * Shows the FAB. */ public void showFab() { showFab(0, 0); } /** * Shows the FAB and sets the FAB's translation. * * @param translationX translation X value * @param translationY translation Y value */ public void showFab(float translationX, float translationY) { // Update FAB's anchor setFabAnchor(translationX, translationY); // Show the FAB in the new position if the sheet is not visible if (!isSheetVisible()) { fab.show(translationX, translationY); } } /** * Shows the sheet. */ public void showSheet() { if (isAnimating()) { return; } isShowing = true; // Show overlay overlayAnimation.show(SHOW_OVERLAY_ANIM_DURATION, null); // Morph FAB into sheet morphIntoSheet(new AnimationListener() { @Override public void onEnd() { // Call event listener if (eventListener != null) { eventListener.onSheetShown(); } // Assuming that this is the last animation to finish isShowing = false; // Hide sheet after it is shown if (hideSheetAfterSheetIsShown) { hideSheet(); hideSheetAfterSheetIsShown = false; } } }); // Call event listener if (eventListener != null) { eventListener.onShowSheet(); } } /** * Hides the sheet. */ public void hideSheet() { hideSheet(null); } protected void hideSheet(final AnimationListener endListener) { if (isAnimating()) { // Wait until the sheet is shown and then hide it if (isShowing) { hideSheetAfterSheetIsShown = true; } return; } isHiding = true; // Hide overlay overlayAnimation.hide(HIDE_OVERLAY_ANIM_DURATION, null); // Morph FAB from sheet morphFromSheet(new AnimationListener() { @Override public void onEnd() { // Call event listeners if (endListener != null) { endListener.onEnd(); } if (eventListener != null) { eventListener.onSheetHidden(); } // Assuming that this is the last animation to finish isHiding = false; } }); // Call event listener if (eventListener != null) { eventListener.onHideSheet(); } } /** * Hides the sheet (if visible) and then hides the FAB. */ public void hideSheetThenFab() { AnimationListener listener = new AnimationListener() { @Override public void onEnd() { fab.hide(); } }; // Hide sheet then hide FAB if (isSheetVisible()) { hideSheet(listener); } // Hide FAB else { listener.onEnd(); } } protected void morphIntoSheet(final AnimationListener endListener) { // Update FAB anchor to ensure that the FAB returns to the correct position when hiding the // sheet updateFabAnchor(); // Align sheet's position with FAB sheetAnimation.alignSheetWithFab(fab); // Morph FAB into sheet fabAnimation.morphIntoSheet(sheetAnimation.getSheetRevealCenterX(), sheetAnimation.getSheetRevealCenterY(fab), getFabArcSide(sheetAnimation.getRevealXDirection()), FAB_ARC_DEGREES, FAB_SCALE_FACTOR, FAB_ANIM_DURATION, null); // Show sheet after a delay new Handler().postDelayed(new Runnable() { @Override public void run() { // Hide FAB fab.setVisibility(View.INVISIBLE); // Show sheet sheetAnimation.morphFromFab(fab, SHEET_ANIM_DURATION, SHOW_SHEET_COLOR_ANIM_DURATION, endListener); } }, SHOW_SHEET_ANIM_DELAY); } protected void morphFromSheet(final AnimationListener endListener) { // Morph sheet into FAB sheetAnimation.morphIntoFab(fab, SHEET_ANIM_DURATION, HIDE_SHEET_COLOR_ANIM_DURATION, null); // Show FAB after a delay new Handler().postDelayed(new Runnable() { @Override public void run() { // Hide sheet sheetAnimation.setSheetVisibility(View.INVISIBLE); // Show FAB fabAnimation.morphFromSheet(anchorX, anchorY, getFabArcSide(sheetAnimation.getRevealXDirection()), FAB_ARC_DEGREES, -FAB_SCALE_FACTOR, FAB_ANIM_DURATION, endListener); } }, MOVE_FAB_ANIM_DELAY); } protected void updateFabAnchor() { // Update the anchor with the current translation setFabAnchor(fab.getTranslationX(), fab.getTranslationY()); } protected void setFabAnchor(float translationX, float translationY) { anchorX = Math .round(fab.getX() + (fab.getWidth() / 2) + (translationX - fab.getTranslationX())); anchorY = Math .round(fab.getY() + (fab.getHeight() / 2) + (translationY - fab.getTranslationY())); } private Side getFabArcSide(RevealXDirection revealXDirection) { if (revealXDirection == RevealXDirection.LEFT) { return Side.LEFT; } else { return Side.RIGHT; } } private boolean isAnimating() { return isShowing || isHiding; } public boolean isSheetVisible() { return sheetAnimation.isSheetVisible(); } public void setEventListener(MaterialSheetFabEventListener eventListener) { this.eventListener = eventListener; } }