自定义
public class FloatingActionsMenu extends ViewGroup {
public static final int EXPAND_UP = 0 ;
public static final int EXPAND_DOWN = 1 ;
public static final int EXPAND_LEFT = 2 ;
public static final int EXPAND_RIGHT = 3 ;
public static final int LABELS_ON_LEFT_SIDE = 0 ;
public static final int LABELS_ON_RIGHT_SIDE = 1 ;
private static final int ANIMATION_DURATION = 300 ;
private static final float COLLAPSED_PLUS_ROTATION = 0 f;
private static final float EXPANDED_PLUS_ROTATION = 90 f + 45 f;
private int mAddButtonPlusColor;
private int mAddButtonColorNormal;
private int mAddButtonColorPressed;
private int mAddButtonSize;
private boolean mAddButtonStrokeVisible;
private int mExpandDirection;
private int mButtonSpacing;
private int mLabelsMargin;
private int mLabelsVerticalOffset;
private boolean mExpanded;
private AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private AddFloatingActionButton mAddButton;
private RotatingDrawable mRotatingDrawable;
private int mMaxButtonWidth;
private int mMaxButtonHeight;
private int mLabelsStyle;
private int mLabelsPosition;
private int mButtonsCount;
private TouchDelegateGroup mTouchDelegateGroup;
private OnFloatingActionsMenuUpdateListener mListener;
public interface OnFloatingActionsMenuUpdateListener {
void onMenuExpanded();
void onMenuCollapsed();
}
public FloatingActionsMenu (Context context) {
this (context, null );
}
public FloatingActionsMenu (Context context, AttributeSet attrs) {
super (context, attrs);
init(context, attrs);
}
public FloatingActionsMenu (Context context, AttributeSet attrs, int defStyle) {
super (context, attrs, defStyle);
init(context, attrs);
}
private void init (Context context, AttributeSet attributeSet) {
mButtonSpacing = (int ) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset));
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
mTouchDelegateGroup = new TouchDelegateGroup(this );
setTouchDelegate(mTouchDelegateGroup);
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0 , 0 );
mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor, getColor(android.R.color.white));
mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal, getColor(android.R.color.holo_blue_dark));
mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed, getColor(android.R.color.holo_blue_light));
mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL);
mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true );
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0 );
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
attr.recycle();
if (mLabelsStyle != 0 && expandsHorizontally()) {
throw new IllegalStateException("Action labels in horizontal expand orientation is not supported." );
}
createAddButton(context);
}
public void setOnFloatingActionsMenuUpdateListener (OnFloatingActionsMenuUpdateListener listener) {
mListener = listener;
}
private boolean expandsHorizontally () {
return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
}
private static class RotatingDrawable extends LayerDrawable {
public RotatingDrawable (Drawable drawable) {
super (new Drawable[] { drawable });
}
private float mRotation;
@SuppressWarnings ("UnusedDeclaration" )
public float getRotation () {
return mRotation;
}
@SuppressWarnings ("UnusedDeclaration" )
public void setRotation (float rotation) {
mRotation = rotation;
invalidateSelf();
}
@Override
public void draw (Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
super .draw(canvas);
canvas.restore();
}
}
private void createAddButton (Context context) {
mAddButton = new AddFloatingActionButton(context) {
@Override
void updateBackground() {
mPlusColor = mAddButtonPlusColor;
mColorNormal = mAddButtonColorNormal;
mColorPressed = mAddButtonColorPressed;
mStrokeVisible = mAddButtonStrokeVisible;
super .updateBackground();
}
@Override
Drawable getIconDrawable() {
final RotatingDrawable rotatingDrawable = new RotatingDrawable(super .getIconDrawable());
mRotatingDrawable = rotatingDrawable;
final OvershootInterpolator interpolator = new OvershootInterpolator();
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation" , EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation" , COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
collapseAnimator.setInterpolator(interpolator);
expandAnimator.setInterpolator(interpolator);
mExpandAnimation.play(expandAnimator);
mCollapseAnimation.play(collapseAnimator);
return rotatingDrawable;
}
};
mAddButton.setId(R.id.fab_expand_menu_button);
mAddButton.setSize(mAddButtonSize);
mAddButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick (View v) {
toggle();
}
});
addView(mAddButton, super .generateDefaultLayoutParams());
mButtonsCount++;
}
public void addButton (FloatingActionButton button) {
addView(button, mButtonsCount - 1 );
mButtonsCount++;
if (mLabelsStyle != 0 ) {
createLabels();
}
}
public void removeButton (FloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null );
mButtonsCount--;
}
private int getColor (@ColorRes int id) {
return getResources().getColor(id);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0 ;
int height = 0 ;
mMaxButtonWidth = 0 ;
mMaxButtonHeight = 0 ;
int maxLabelWidth = 0 ;
for (int i = 0 ; i < mButtonsCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue ;
}
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
height += child.getMeasuredHeight();
break ;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += child.getMeasuredWidth();
mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
break ;
}
if (!expandsHorizontally()) {
TextView label = (TextView) child.getTag(R.id.fab_label);
if (label != null ) {
maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
}
}
}
if (!expandsHorizontally()) {
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0 );
} else {
height = mMaxButtonHeight;
}
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
height += mButtonSpacing * (mButtonsCount - 1 );
height = adjustForOvershoot(height);
break ;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += mButtonSpacing * (mButtonsCount - 1 );
width = adjustForOvershoot(width);
break ;
}
setMeasuredDimension(width, height);
}
private int adjustForOvershoot (int dimension) {
return dimension * 12 / 10 ;
}
@Override
protected void onLayout (boolean changed, int l, int t, int r, int b) {
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
boolean expandUp = mExpandDirection == EXPAND_UP;
if (changed) {
mTouchDelegateGroup.clearTouchDelegates();
}
int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0 ;
int buttonsHorizontalCenter = mLabelsPosition == LABELS_ON_LEFT_SIDE
? r - l - mMaxButtonWidth / 2
: mMaxButtonWidth / 2 ;
int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2 ;
mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight());
int labelsOffset = mMaxButtonWidth / 2 + mLabelsMargin;
int labelsXNearButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
? buttonsHorizontalCenter - labelsOffset
: buttonsHorizontalCenter + labelsOffset;
int nextY = expandUp ?
addButtonY - mButtonSpacing :
addButtonY + mAddButton.getMeasuredHeight() + mButtonSpacing;
for (int i = mButtonsCount - 1 ; i >= 0 ; i--) {
final View child = getChildAt(i);
if (child == mAddButton || child.getVisibility() == GONE) continue ;
int childX = buttonsHorizontalCenter - child.getMeasuredWidth() / 2 ;
int childY = expandUp ? nextY - child.getMeasuredHeight() : nextY;
child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
float collapsedTranslation = addButtonY - childY;
float expandedTranslation = 0 f;
child.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
child.setAlpha(mExpanded ? 1 f : 0 f);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
params.setAnimationsTarget(child);
View label = (View) child.getTag(R.id.fab_label);
if (label != null ) {
int labelXAwayFromButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
? labelsXNearButton - label.getMeasuredWidth()
: labelsXNearButton + label.getMeasuredWidth();
int labelLeft = mLabelsPosition == LABELS_ON_LEFT_SIDE
? labelXAwayFromButton
: labelsXNearButton;
int labelRight = mLabelsPosition == LABELS_ON_LEFT_SIDE
? labelsXNearButton
: labelXAwayFromButton;
int labelTop = childY - mLabelsVerticalOffset + (child.getMeasuredHeight() - label.getMeasuredHeight()) / 2 ;
label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight());
Rect touchArea = new Rect(
Math.min(childX, labelLeft),
childY - mButtonSpacing / 2 ,
Math.max(childX + child.getMeasuredWidth(), labelRight),
childY + child.getMeasuredHeight() + mButtonSpacing / 2 );
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(touchArea, child));
label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
label.setAlpha(mExpanded ? 1 f : 0 f);
LayoutParams labelParams = (LayoutParams) label.getLayoutParams();
labelParams.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
labelParams.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
labelParams.setAnimationsTarget(label);
}
nextY = expandUp ?
childY - mButtonSpacing :
childY + child.getMeasuredHeight() + mButtonSpacing;
}
break ;
case EXPAND_LEFT:
case EXPAND_RIGHT:
boolean expandLeft = mExpandDirection == EXPAND_LEFT;
int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0 ;
int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2 ;
mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight());
int nextX = expandLeft ?
addButtonX - mButtonSpacing :
addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing;
for (int i = mButtonsCount - 1 ; i >= 0 ; i--) {
final View child = getChildAt(i);
if (child == mAddButton || child.getVisibility() == GONE) continue ;
int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX;
int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2 ;
child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
float collapsedTranslation = addButtonX - childX;
float expandedTranslation = 0 f;
child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation);
child.setAlpha(mExpanded ? 1 f : 0 f);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
params.setAnimationsTarget(child);
nextX = expandLeft ?
childX - mButtonSpacing :
childX + child.getMeasuredWidth() + mButtonSpacing;
}
break ;
}
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams () {
return new LayoutParams(super .generateDefaultLayoutParams());
}
@Override
public ViewGroup.LayoutParams generateLayoutParams (AttributeSet attrs) {
return new LayoutParams(super .generateLayoutParams(attrs));
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams (ViewGroup.LayoutParams p) {
return new LayoutParams(super .generateLayoutParams(p));
}
@Override
protected boolean checkLayoutParams (ViewGroup.LayoutParams p) {
return super .checkLayoutParams(p);
}
private static Interpolator sExpandInterpolator = new OvershootInterpolator();
private static Interpolator sCollapseInterpolator = new DecelerateInterpolator(3 f);
private static Interpolator sAlphaExpandInterpolator = new DecelerateInterpolator();
private class LayoutParams extends ViewGroup .LayoutParams {
private ObjectAnimator mExpandDir = new ObjectAnimator();
private ObjectAnimator mExpandAlpha = new ObjectAnimator();
private ObjectAnimator mCollapseDir = new ObjectAnimator();
private ObjectAnimator mCollapseAlpha = new ObjectAnimator();
private boolean animationsSetToPlay;
public LayoutParams (ViewGroup.LayoutParams source) {
super (source);
mExpandDir.setInterpolator(sExpandInterpolator);
mExpandAlpha.setInterpolator(sAlphaExpandInterpolator);
mCollapseDir.setInterpolator(sCollapseInterpolator);
mCollapseAlpha.setInterpolator(sCollapseInterpolator);
mCollapseAlpha.setProperty(View.ALPHA);
mCollapseAlpha.setFloatValues(1 f, 0 f);
mExpandAlpha.setProperty(View.ALPHA);
mExpandAlpha.setFloatValues(0 f, 1 f);
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
mCollapseDir.setProperty(View.TRANSLATION_Y);
mExpandDir.setProperty(View.TRANSLATION_Y);
break ;
case EXPAND_LEFT:
case EXPAND_RIGHT:
mCollapseDir.setProperty(View.TRANSLATION_X);
mExpandDir.setProperty(View.TRANSLATION_X);
break ;
}
}
public void setAnimationsTarget (View view) {
mCollapseAlpha.setTarget(view);
mCollapseDir.setTarget(view);
mExpandAlpha.setTarget(view);
mExpandDir.setTarget(view);
if (!animationsSetToPlay) {
addLayerTypeListener(mExpandDir, view);
addLayerTypeListener(mCollapseDir, view);
mCollapseAnimation.play(mCollapseAlpha);
mCollapseAnimation.play(mCollapseDir);
mExpandAnimation.play(mExpandAlpha);
mExpandAnimation.play(mExpandDir);
animationsSetToPlay = true ;
}
}
private void addLayerTypeListener (Animator animator, final View view) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd (Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null );
}
@Override
public void onAnimationStart (Animator animation) {
view.setLayerType(LAYER_TYPE_HARDWARE, null );
}
});
}
}
@Override
protected void onFinishInflate () {
super .onFinishInflate();
bringChildToFront(mAddButton);
mButtonsCount = getChildCount();
if (mLabelsStyle != 0 ) {
createLabels();
}
}
private void createLabels () {
Context context = new ContextThemeWrapper(getContext(), mLabelsStyle);
for (int i = 0 ; i < mButtonsCount; i++) {
FloatingActionButton button = (FloatingActionButton) getChildAt(i);
String title = button.getTitle();
if (button == mAddButton || title == null ||
button.getTag(R.id.fab_label) != null ) continue ;
TextView label = new TextView(context);
label.setTextAppearance(getContext(), mLabelsStyle);
label.setText(button.getTitle());
addView(label);
button.setTag(R.id.fab_label, label);
}
}
public void collapse () {
collapse(false );
}
public void collapseImmediately () {
collapse(true );
}
private void collapse (boolean immediately) {
if (mExpanded) {
mExpanded = false ;
mTouchDelegateGroup.setEnabled(false );
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
mCollapseAnimation.start();
mExpandAnimation.cancel();
if (mListener != null ) {
mListener.onMenuCollapsed();
}
}
}
public void toggle () {
if (mExpanded) {
collapse();
} else {
expand();
}
}
public void expand () {
if (!mExpanded) {
mExpanded = true ;
mTouchDelegateGroup.setEnabled(true );
mCollapseAnimation.cancel();
mExpandAnimation.start();
if (mListener != null ) {
mListener.onMenuExpanded();
}
}
}
public boolean isExpanded () {
return mExpanded;
}
@Override
public void setEnabled (boolean enabled) {
super .setEnabled(enabled);
mAddButton.setEnabled(enabled);
}
@Override
public Parcelable onSaveInstanceState () {
Parcelable superState = super .onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.mExpanded = mExpanded;
return savedState;
}
@Override
public void onRestoreInstanceState (Parcelable state) {
if (state instanceof SavedState) {
SavedState savedState = (SavedState) state;
mExpanded = savedState.mExpanded;
mTouchDelegateGroup.setEnabled(mExpanded);
if (mRotatingDrawable != null ) {
mRotatingDrawable.setRotation(mExpanded ? EXPANDED_PLUS_ROTATION : COLLAPSED_PLUS_ROTATION);
}
super .onRestoreInstanceState(savedState.getSuperState());
} else {
super .onRestoreInstanceState(state);
}
}
public static class SavedState extends BaseSavedState {
public boolean mExpanded;
public SavedState (Parcelable parcel) {
super (parcel);
}
private SavedState (Parcel in) {
super (in);
mExpanded = in.readInt() == 1 ;
}
@Override
public void writeToParcel (@NonNull Parcel out, int flags) {
super .writeToParcel(out, flags);
out.writeInt(mExpanded ? 1 : 0 );
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel (Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray (int size) {
return new SavedState[size];
}
};
}
}
public class FloatingActionButton extends ImageButton {
public static final int SIZE_NORMAL = 0 ;
public static final int SIZE_MINI = 1 ;
@Retention (RetentionPolicy.SOURCE)
@IntDef ({ SIZE_NORMAL, SIZE_MINI })
public @interface FAB_SIZE {
}
int mColorNormal;
int mColorPressed;
int mColorDisabled;
String mTitle;
@DrawableRes
private int mIcon;
private Drawable mIconDrawable;
private int mSize;
private float mCircleSize;
private float mShadowRadius;
private float mShadowOffset;
private int mDrawableSize;
boolean mStrokeVisible;
public FloatingActionButton (Context context) {
this (context, null );
}
public FloatingActionButton (Context context, AttributeSet attrs) {
super (context, attrs);
init(context, attrs);
}
public FloatingActionButton (Context context, AttributeSet attrs, int defStyle) {
super (context, attrs, defStyle);
init(context, attrs);
}
void init(Context context, AttributeSet attributeSet) {
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0 , 0 );
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, getColor(android.R.color.holo_blue_dark));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(android.R.color.holo_blue_light));
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(android.R.color.darker_gray));
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0 );
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true );
attr.recycle();
updateCircleSize();
mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
updateDrawableSize();
updateBackground();
}
private void updateDrawableSize () {
mDrawableSize = (int ) (mCircleSize + 2 * mShadowRadius);
}
private void updateCircleSize () {
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
}
public void setSize (@FAB_SIZE int size) {
if (size != SIZE_MINI && size != SIZE_NORMAL) {
throw new IllegalArgumentException("Use @FAB_SIZE constants only!" );
}
if (mSize != size) {
mSize = size;
updateCircleSize();
updateDrawableSize();
updateBackground();
}
}
@FAB _SIZE
public int getSize () {
return mSize;
}
public void setIcon (@DrawableRes int icon) {
if (mIcon != icon) {
mIcon = icon;
mIconDrawable = null ;
updateBackground();
}
}
public void setIconDrawable (@NonNull Drawable iconDrawable) {
if (mIconDrawable != iconDrawable) {
mIcon = 0 ;
mIconDrawable = iconDrawable;
updateBackground();
}
}
/**
* @return the current Color for normal state.
*/
public int getColorNormal () {
return mColorNormal;
}
public void setColorNormalResId (@ColorRes int colorNormal) {
setColorNormal(getColor(colorNormal));
}
public void setColorNormal (int color) {
if (mColorNormal != color) {
mColorNormal = color;
updateBackground();
}
}
/**
* @return the current color for pressed state.
*/
public int getColorPressed () {
return mColorPressed;
}
public void setColorPressedResId (@ColorRes int colorPressed) {
setColorPressed(getColor(colorPressed));
}
public void setColorPressed (int color) {
if (mColorPressed != color) {
mColorPressed = color;
updateBackground();
}
}
/**
* @return the current color for disabled state.
*/
public int getColorDisabled () {
return mColorDisabled;
}
public void setColorDisabledResId (@ColorRes int colorDisabled) {
setColorDisabled(getColor(colorDisabled));
}
public void setColorDisabled (int color) {
if (mColorDisabled != color) {
mColorDisabled = color;
updateBackground();
}
}
public void setStrokeVisible (boolean visible) {
if (mStrokeVisible != visible) {
mStrokeVisible = visible;
updateBackground();
}
}
public boolean isStrokeVisible () {
return mStrokeVisible;
}
int getColor(@ColorRes int id) {
return getResources().getColor(id);
}
float getDimension(@DimenRes int id) {
return getResources().getDimension(id);
}
public void setTitle (String title) {
mTitle = title;
TextView label = getLabelView();
if (label != null ) {
label.setText(title);
}
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle () {
return mTitle;
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super .onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDrawableSize, mDrawableSize);
}
void updateBackground() {
final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
final float halfStrokeWidth = strokeWidth / 2 f;
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[] {
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini),
createFillDrawable(strokeWidth),
createOuterStrokeDrawable(strokeWidth),
getIconDrawable()
});
int iconOffset = (int ) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2 ;
int circleInsetHorizontal = (int ) (mShadowRadius);
int circleInsetTop = (int ) (mShadowRadius - mShadowOffset);
int circleInsetBottom = (int ) (mShadowRadius + mShadowOffset);
layerDrawable.setLayerInset(1 ,
circleInsetHorizontal,
circleInsetTop,
circleInsetHorizontal,
circleInsetBottom);
layerDrawable.setLayerInset(2 ,
(int ) (circleInsetHorizontal - halfStrokeWidth),
(int ) (circleInsetTop - halfStrokeWidth),
(int ) (circleInsetHorizontal - halfStrokeWidth),
(int ) (circleInsetBottom - halfStrokeWidth));
layerDrawable.setLayerInset(3 ,
circleInsetHorizontal + iconOffset,
circleInsetTop + iconOffset,
circleInsetHorizontal + iconOffset,
circleInsetBottom + iconOffset);
setBackgroundCompat(layerDrawable);
}
Drawable getIconDrawable() {
if (mIconDrawable != null ) {
return mIconDrawable;
} else if (mIcon != 0 ) {
return getResources().getDrawable(mIcon);
} else {
return new ColorDrawable(Color.TRANSPARENT);
}
}
private StateListDrawable createFillDrawable (float strokeWidth) {
StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int [] { -android.R.attr.state_enabled }, createCircleDrawable(mColorDisabled, strokeWidth));
drawable.addState(new int [] { android.R.attr.state_pressed }, createCircleDrawable(mColorPressed, strokeWidth));
drawable.addState(new int [] { }, createCircleDrawable(mColorNormal, strokeWidth));
return drawable;
}
private Drawable createCircleDrawable (int color, float strokeWidth) {
int alpha = Color.alpha(color);
int opaqueColor = opaque(color);
ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = fillDrawable.getPaint();
paint.setAntiAlias(true );
paint.setColor(opaqueColor);
Drawable[] layers = {
fillDrawable,
createInnerStrokesDrawable(opaqueColor, strokeWidth)
};
LayerDrawable drawable = alpha == 255 || !mStrokeVisible
? new LayerDrawable(layers)
: new TranslucentLayerDrawable(alpha, layers);
int halfStrokeWidth = (int ) (strokeWidth / 2 f);
drawable.setLayerInset(1 , halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
return drawable;
}
private static class TranslucentLayerDrawable extends LayerDrawable {
private final int mAlpha;
public TranslucentLayerDrawable (int alpha, Drawable... layers) {
super (layers);
mAlpha = alpha;
}
@Override
public void draw (Canvas canvas) {
Rect bounds = getBounds();
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha, Canvas.ALL_SAVE_FLAG);
super .draw(canvas);
canvas.restore();
}
}
private Drawable createOuterStrokeDrawable (float strokeWidth) {
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true );
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAlpha(opacityToAlpha(0.02 f));
return shapeDrawable;
}
private int opacityToAlpha (float opacity) {
return (int ) (255 f * opacity);
}
private int darkenColor (int argb) {
return adjustColorBrightness(argb, 0.9 f);
}
private int lightenColor (int argb) {
return adjustColorBrightness(argb, 1.1 f);
}
private int adjustColorBrightness (int argb, float factor) {
float [] hsv = new float [3 ];
Color.colorToHSV(argb, hsv);
hsv[2 ] = Math.min(hsv[2 ] * factor, 1 f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private int halfTransparent (int argb) {
return Color.argb(
Color.alpha(argb) / 2 ,
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private int opaque (int argb) {
return Color.rgb(
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private Drawable createInnerStrokesDrawable (final int color, float strokeWidth) {
if (!mStrokeVisible) {
return new ColorDrawable(Color.TRANSPARENT);
}
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final int bottomStrokeColor = darkenColor(color);
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
final int topStrokeColor = lightenColor(color);
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true );
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
shapeDrawable.setShaderFactory(new ShaderFactory() {
@Override
public Shader resize (int width, int height) {
return new LinearGradient(width / 2 , 0 , width / 2 , height,
new int [] { topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor },
new float [] { 0 f, 0.2 f, 0.5 f, 0.8 f, 1 f },
TileMode.CLAMP
);
}
});
return shapeDrawable;
}
@SuppressWarnings ("deprecation" )
@SuppressLint ("NewApi" )
private void setBackgroundCompat (Drawable drawable) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
@Override
public void setVisibility (int visibility) {
TextView label = getLabelView();
if (label != null ) {
label.setVisibility(visibility);
}
super .setVisibility(visibility);
}
}
public class AddFloatingActionButton extends FloatingActionButton {
int mPlusColor;
public AddFloatingActionButton (Context context) {
this (context, null );
}
public AddFloatingActionButton (Context context, AttributeSet attrs) {
super (context, attrs);
}
public AddFloatingActionButton (Context context, AttributeSet attrs, int defStyle) {
super (context, attrs, defStyle);
}
@Override
void init(Context context, AttributeSet attributeSet) {
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0 , 0 );
mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_fab_plusIconColor, getColor(android.R.color.white));
attr.recycle();
super .init(context, attributeSet);
}
/**
* @return the current Color of plus icon.
*/
public int getPlusColor () {
return mPlusColor;
}
public void setPlusColorResId (@ColorRes int plusColor) {
setPlusColor(getColor(plusColor));
}
public void setPlusColor (int color) {
if (mPlusColor != color) {
mPlusColor = color;
updateBackground();
}
}
@Override
public void setIcon (@DrawableRes int icon) {
throw new UnsupportedOperationException("Use FloatingActionButton if you want to use custom icon" );
}
@Override
Drawable getIconDrawable() {
final float iconSize = getDimension(R.dimen.fab_icon_size);
final float iconHalfSize = iconSize / 2 f;
final float plusSize = getDimension(R.dimen.fab_plus_icon_size);
final float plusHalfStroke = getDimension(R.dimen.fab_plus_icon_stroke) / 2 f;
final float plusOffset = (iconSize - plusSize) / 2 f;
final Shape shape = new Shape() {
@Override
public void draw (Canvas canvas, Paint paint) {
canvas.drawRect(plusOffset, iconHalfSize - plusHalfStroke, iconSize - plusOffset, iconHalfSize + plusHalfStroke, paint);
canvas.drawRect(iconHalfSize - plusHalfStroke, plusOffset, iconHalfSize + plusHalfStroke, iconSize - plusOffset, paint);
}
};
ShapeDrawable drawable = new ShapeDrawable(shape);
final Paint paint = drawable.getPaint();
paint.setColor(mPlusColor);
paint.setStyle(Style.FILL);
paint.setAntiAlias(true );
return drawable;
}
}
TouchDelegateGroup
public class TouchDelegateGroup extends TouchDelegate {
private static final Rect USELESS_HACKY_RECT = new Rect();
private final ArrayList<TouchDelegate> mTouchDelegates = new ArrayList<TouchDelegate>();
private TouchDelegate mCurrentTouchDelegate;
private boolean mEnabled;
public TouchDelegateGroup (View uselessHackyView) {
super (USELESS_HACKY_RECT, uselessHackyView);
}
public void addTouchDelegate (@NonNull TouchDelegate touchDelegate) {
mTouchDelegates.add(touchDelegate);
}
public void removeTouchDelegate (TouchDelegate touchDelegate) {
mTouchDelegates.remove(touchDelegate);
if (mCurrentTouchDelegate == touchDelegate) {
mCurrentTouchDelegate = null ;
}
}
public void clearTouchDelegates () {
mTouchDelegates.clear();
mCurrentTouchDelegate = null ;
}
@Override
public boolean onTouchEvent (@NonNull MotionEvent event) {
if (!mEnabled) return false ;
TouchDelegate delegate = null ;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (int i = 0 ; i < mTouchDelegates.size(); i++) {
TouchDelegate touchDelegate = mTouchDelegates.get(i);
if (touchDelegate.onTouchEvent(event)) {
mCurrentTouchDelegate = touchDelegate;
return true ;
}
}
break ;
case MotionEvent.ACTION_MOVE:
delegate = mCurrentTouchDelegate;
break ;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
delegate = mCurrentTouchDelegate;
mCurrentTouchDelegate = null ;
break ;
}
return delegate != null && delegate.onTouchEvent(event);
}
public void setEnabled (boolean enabled) {
mEnabled = enabled;
}
}
drawable/fab_label_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android ="http://schemas.android.com/apk/res/android" >
<solid android:color ="@color/black_semi_transparent" />
<padding
android:left ="16dp"
android:top ="4dp"
android:right ="16dp"
android:bottom ="4dp" />
<corners
android:radius ="2dp" />
</shape >
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources >
<declare-styleable name ="FloatingActionButton" >
<attr name ="fab_colorPressed" format ="color" />
<attr name ="fab_colorDisabled" format ="color" />
<attr name ="fab_colorNormal" format ="color" />
<attr name ="fab_icon" format ="reference" />
<attr name ="fab_size" format ="enum" >
<enum name ="normal" value ="0" />
<enum name ="mini" value ="1" />
</attr >
<attr name ="fab_title" format ="string" />
<attr name ="fab_stroke_visible" format ="boolean" />
</declare-styleable >
<declare-styleable name ="AddFloatingActionButton" >
<attr name ="fab_plusIconColor" format ="color" />
</declare-styleable >
<declare-styleable name ="FloatingActionsMenu" >
<attr name ="fab_addButtonColorPressed" format ="color" />
<attr name ="fab_addButtonColorNormal" format ="color" />
<attr name ="fab_addButtonSize" format ="enum" >
<enum name ="normal" value ="0" />
<enum name ="mini" value ="1" />
</attr >
<attr name ="fab_addButtonPlusIconColor" format ="color" />
<attr name ="fab_addButtonStrokeVisible" format ="boolean" />
<attr name ="fab_labelStyle" format ="reference" />
<attr name ="fab_labelsPosition" format ="enum" >
<enum name ="left" value ="0" />
<enum name ="right" value ="1" />
</attr >
<attr name ="fab_expandDirection" format ="enum" >
<enum name ="up" value ="0" />
<enum name ="down" value ="1" />
<enum name ="left" value ="2" />
<enum name ="right" value ="3" />
</attr >
</declare-styleable >
</resources >
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources >
<color name ="colorPrimary" > #3F51B5</color >
<color name ="colorPrimaryDark" > #303F9F</color >
<color name ="colorAccent" > #FF4081</color >
<color name ="black_semi_transparent" > #B2000000</color >
<color name ="background" > #e5e5e5</color >
<color name ="half_black" > #808080</color >
<color name ="white" > #fafafa</color >
<color name ="white_pressed" > #f1f1f1</color >
<color name ="pink" > #e91e63</color >
<color name ="pink_pressed" > #ec407a</color >
<color name ="blue_semi_transparent" > #805677fc</color >
<color name ="blue_semi_transparent_pressed" > #80738ffe</color >
</resources >
dimens.xml
<resources >
<dimen name ="fab_size_normal" > 56dp</dimen >
<dimen name ="fab_size_mini" > 40dp</dimen >
<dimen name ="fab_icon_size" > 24dp</dimen >
<dimen name ="fab_plus_icon_size" > 14dp</dimen >
<dimen name ="fab_plus_icon_stroke" > 2dp</dimen >
<dimen name ="fab_shadow_offset" > 3dp</dimen >
<dimen name ="fab_shadow_radius" > 9dp</dimen >
<dimen name ="fab_stroke_width" > 1dp</dimen >
<dimen name ="fab_actions_spacing" > 16dp</dimen >
<dimen name ="fab_labels_margin" > 8dp</dimen >
</resources >
ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources >
<item name ="fab_expand_menu_button" type ="id" />
<item name ="fab_label" type ="id" />
</resources >
styles.xml
<style name ="menu_labels_style" >
<item name ="android:background" >@drawable/fab_label_background</item >
<item name ="android:textColor" >@color/white</item >
</style>
themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android ="http://schemas.android.com/apk/res/android" >
<style name ="Theme" parent ="@android:style/Theme.Holo.Light.DarkActionBar" >
<item name ="android :textViewStyle" >@style/theme_textview_style</item>
</style >
<style name ="theme_textview_style" parent ="@android:style/Widget.Holo.Light.TextView" >
<item name ="android :textColor" >@color/pink</item>
</style >
</resources >
activity_main.xml
<!-- FloatingActonBar-->
<com .xhly .animation .catchpic .view .FloatingActionsMenu
android:id="@+id/multiple_actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
fab:fab_addButtonColorNormal="@color/white"
fab:fab_addButtonColorPressed="@color/white_pressed"
fab:fab_addButtonPlusIconColor="@color/half_black"
fab:fab_labelStyle="@style/menu_labels_style"
android:layout_marginBottom="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp" >
<com .xhly .animation .catchpic .view .FloatingActionButton
android:id="@+id/action_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/white"
fab:fab_icon="@drawable/icon_s_download_press"
fab:fab_colorPressed="@color/white_pressed"
/>
<com .xhly .animation .catchpic .view .FloatingActionButton
android:id="@+id/action_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/white"
fab:fab_icon="@drawable/toolbar_pop_send_press"
fab:fab_colorPressed="@color/white_pressed"
/>
</com .xhly .animation .catchpic .view .FloatingActionsMenu >