package com.reactnativenavigation.views; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.util.Log; import android.view.Gravity; import android.view.View; import com.reactnativenavigation.NavigationApplication; import com.reactnativenavigation.params.FabActionParams; import com.reactnativenavigation.params.FabParams; import com.reactnativenavigation.utils.ViewUtils; import java.util.ArrayList; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; class FloatingActionButtonCoordinator { private static final String TAG = "FloatingActionButtonCoo"; private static final int INITIAL_EXPENDED_FAB_ROTATION = -90; private CoordinatorLayout parent; private FabParams params; private FloatingActionButton collapsedFab; private FloatingActionButton expendedFab; private final int crossFadeAnimationDuration; private final int actionSize; private final int margin = (int) ViewUtils.convertDpToPixel(16); private FloatingActionButtonAnimator fabAnimator; private final ArrayList<FloatingActionButton> actions; FloatingActionButtonCoordinator(CoordinatorLayout parent) { this.parent = parent; actions = new ArrayList<>(); crossFadeAnimationDuration = parent.getResources().getInteger(android.R.integer.config_shortAnimTime); actionSize = (int) ViewUtils.convertDpToPixel(40); } public void add(final FabParams params) { Log.i(TAG, "add() called with: params = [" + params + "]"); if (hasFab()) { remove(new Runnable() { @Override public void run() { add(params); } }); return; } this.params = params; if (!params.isValid()) { return; } createCollapsedFab(); createExpendedFab(); setStyle(); fabAnimator = new FloatingActionButtonAnimator(collapsedFab, expendedFab, crossFadeAnimationDuration); fabAnimator.show(); } void remove(@Nullable final Runnable onComplete) { if (!hasFab()) { if (onComplete != null) { onComplete.run(); } return; } if (fabAnimator != null) { fabAnimator.removeFabFromScreen(expendedFab, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { removeAllViews(); if (onComplete != null) { onComplete.run(); } } }); fabAnimator.removeFabFromScreen(collapsedFab, null); fabAnimator.removeActionsFromScreen(actions); } } private boolean hasFab() { return collapsedFab != null || expendedFab != null; } private void removeAllViews() { parent.removeView(collapsedFab); parent.removeView(expendedFab); collapsedFab = null; expendedFab = null; for (FloatingActionButton action : actions) { ((CoordinatorLayout.LayoutParams) action.getLayoutParams()).setBehavior(null); parent.removeView(action); } actions.clear(); } private void createCollapsedFab() { collapsedFab = createFab(params.collapsedIcon); parent.addView(collapsedFab, createFabLayoutParams()); collapsedFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (params.hasExpendedState()) { fabAnimator.hideCollapsed(); fabAnimator.showExpended(); showActions(); } NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(params.collapsedId, params.navigatorEventId); } }); } private void createExpendedFab() { expendedFab = createFab(params.expendedIcon); parent.addView(expendedFab, createFabLayoutParams()); expendedFab.setVisibility(View.GONE); expendedFab.setRotation(INITIAL_EXPENDED_FAB_ROTATION); expendedFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fabAnimator.collapse(); NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(params.expendedId, params.navigatorEventId); } }); } private FloatingActionButton createFab(Drawable icon) { FloatingActionButton fab = new FloatingActionButton(parent.getContext()); fab.setId(ViewUtils.generateViewId()); fab.setImageDrawable(icon); return fab; } private CoordinatorLayout.LayoutParams createFabLayoutParams() { final CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lp.gravity = Gravity.RIGHT | Gravity.BOTTOM; lp.bottomMargin = margin; lp.rightMargin = margin; lp.topMargin = margin; return lp; } private void setStyle() { collapsedFab.setBackgroundTintList(ColorStateList.valueOf(params.backgroundColor.getColor())); expendedFab.setBackgroundTintList(ColorStateList.valueOf(params.backgroundColor.getColor())); } private void showActions() { if (actions.size() > 0) { return; } for (int i = 0; i < params.actions.size(); i++) { FloatingActionButton action = createAction(i); actions.add(action); parent.addView(action); } } private FloatingActionButton createAction(int index) { final FabActionParams actionParams = params.actions.get(index); FloatingActionButton action = createFab(actionParams.icon); action.setLayoutParams(createActionLayoutParams(index)); action.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(actionParams.id, actionParams.navigatorEventId); fabAnimator.collapse(); } }); if (actionParams.backgroundColor.hasColor()) { action.setBackgroundTintList(ColorStateList.valueOf(actionParams.backgroundColor.getColor())); } action.setSize(FloatingActionButton.SIZE_MINI); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { action.setCompatElevation(0); } return action; } @NonNull private CoordinatorLayout.LayoutParams createActionLayoutParams(int actionIndex) { CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lp.setAnchorId(expendedFab.getId()); lp.anchorGravity = Gravity.CENTER_HORIZONTAL; lp.setBehavior(new ActionBehaviour(expendedFab, (actionIndex + 1) * (actionSize + margin / 2))); return lp; } private static class ActionBehaviour extends CoordinatorLayout.Behavior<FloatingActionButton> { private final int MAX_VALUE = 90; private int dependencyId; private float yStep; ActionBehaviour(View anchor, float yStep) { this.yStep = yStep; this.dependencyId = anchor.getId(); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { return dependency.getId() == dependencyId; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { final View dependentView = parent.findViewById(dependencyId); if (dependentView == null) { return false; } final float dependentValue = dependency.getRotation(); float fraction = calculateTransitionFraction(dependentValue); child.setY(calculateY(dependentView, fraction)); child.setAlpha(calculateAlpha(fraction)); setVisibility(child); return true; } private void setVisibility(FloatingActionButton child) { child.setVisibility(child.getAlpha() == 0 ? View.GONE : View.VISIBLE); } private float calculateAlpha(float fraction) { return 1 * fraction; } private float calculateY(View dependentView, float fraction) { return dependentView.getY() - yStep * fraction; } @FloatRange(from=0.0, to=1.0) private float calculateTransitionFraction(float dependentValue) { return 1 - Math.abs(dependentValue / MAX_VALUE); } } }