Skip to content

Instantly share code, notes, and snippets.

@chanakin
Last active January 12, 2018 12:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chanakin/eb315d54be10ce90511f to your computer and use it in GitHub Desktop.
Save chanakin/eb315d54be10ce90511f to your computer and use it in GitHub Desktop.
Code to support a floating action button using Material design and animations that expands to show additional actions; supports API 14+
package com.bigoven.android.widgets;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.bigoven.android.R;
import com.bigoven.android.utilities.UiHelper;
import java.util.List;
/**
*
This class is a custom layout that allows the user to add a primary floating action button with secondary actions
that expand when the primary button is clicked.
Copyright (C) 2015 Chantell Osejo
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
public class ExpandableFloatingActionButtonLayout extends RelativeLayout {
private final static int DEFAULT_MARGIN = UiHelper.convertDpToPixel(16);
private final static int SECONDARY_DEFAULT_MARGIN = UiHelper.convertDpToPixel(8);
private final LinearLayout.LayoutParams mPrimaryLayoutParams = new LinearLayout.LayoutParams(FloatingActionButton.GOOGLE_DESIGN_GUIDELINE_RECOMMENDED_DIAMETER_PX, FloatingActionButton.GOOGLE_DESIGN_GUIDELINE_RECOMMENDED_DIAMETER_PX);
private final LinearLayout.LayoutParams mSecondaryLayoutParams = new LinearLayout.LayoutParams(FloatingActionButton.GOOGLE_DESIGN_GUIDELINE_RECOMMENDED_MINI_DIAMETER_PX, FloatingActionButton.GOOGLE_DESIGN_GUIDELINE_RECOMMENDED_MINI_DIAMETER_PX);
private LinearLayout mButtonContainer;
private boolean mExpanded = false;
private CompositeOnClickListener mPrimaryButtonOnClickListener;
public ExpandableFloatingActionButtonLayout(Context context) {
this(context, null);
}
public ExpandableFloatingActionButtonLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableFloatingActionButtonLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableFloatingActionButtonLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.expandable_floating_action_button_layout, this);
mButtonContainer = (LinearLayout) findViewById(R.id.container);
mPrimaryLayoutParams.setMargins(DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN);
mSecondaryLayoutParams.setMargins(0, SECONDARY_DEFAULT_MARGIN, 0, SECONDARY_DEFAULT_MARGIN);
mPrimaryButtonOnClickListener = new CompositeOnClickListener();
}
public void addFloatingActionButtons(final FloatingActionButton primaryFloatingActionButton, final List<FloatingActionButton> secondaryActionButtons) {
// If this gets called more than once, then we will clean-slate it first.
mButtonContainer.removeAllViews();
mButtonContainer.addView(primaryFloatingActionButton, mPrimaryLayoutParams);
final ObjectAnimator rotate = ObjectAnimator.ofFloat(primaryFloatingActionButton, ROTATION, 0f, 180f);
rotate.setDuration(200);
for (FloatingActionButton secondaryActionButton : secondaryActionButtons) {
secondaryActionButton.setVisibility(View.GONE);
mButtonContainer.addView(secondaryActionButton, 0, mSecondaryLayoutParams);
}
mPrimaryButtonOnClickListener.addOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mExpanded = !mExpanded;
for (int i = 0; i < secondaryActionButtons.size(); i++) {
// Increase the delay for every additional button, so we get the effect of a "ripple" of buttons being added
// We'll reverse this delay if we're hiding them
secondaryActionButtons.get(mExpanded ? i : secondaryActionButtons.size() - 1 - i).setVisibilityAfterDelay(mExpanded ? View.VISIBLE : View.GONE, (i + 1) * 100);
}
rotate.start();
}
});
primaryFloatingActionButton.setOnClickListener(mPrimaryButtonOnClickListener);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP && mExpanded) {
primaryFloatingActionButton.performClick();
}
return false;
}
});
}
public void setPrimaryButtonOnClickListener(OnClickListener onClickListener) {
mPrimaryButtonOnClickListener.addOnClickListener(onClickListener);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment