title: FloatingActionMenu Class Add Spacing Between Buttons
body: <p>I was researching on how to make a Floating Action Menu out of Floating Action Buttons (FABs) and I came upon  <a href="https://stackoverflow.com/questions/30523144/floating-action-menu-implementation-using-float-action-button-from-android-desig">this question</a>.  In the comments, <a href="https://stackoverflow.com/questions/30523144/floating-action-menu-implementation-using-float-action-button-from-android-desig#comment50022985_30523144">this</a> comment on it gave a class that offered the FloatingActionMenu option.  I implemented the class and xml file and it works with only one issue.  </p>
  
  <p><strong>Issue:</strong> In the menu there are four FABs, the first one is the button that you click to expand the menu. Between all of the FABs I would like for there to be spacing, yet, there is no spacing and I cannot add any. (See Image Below)</p>
  
  <p><img src="https://i.stack.imgur.com/YIXuJm.jpg" alt="FAB without margin"></p>
  
  <p><strong>FloatingActionMenu Class</strong></p>
  
  <pre><code>public class FloatingActionMenu extends ViewGroup {
  
      private static final long ANIMATION_DURATION = 300;
      private FloatingActionButton mMenuButton;
      private ArrayList&lt;FloatingActionButton&gt; mMenuItems;
      private ArrayList&lt;TextView&gt; mMenuItemLabels;
      private ArrayList&lt;ItemAnimator&gt; mMenuItemAnimators;
      private int mItemMargin;
  
  
      private AnimatorSet mOpenAnimatorSet = new AnimatorSet();
      private AnimatorSet mCloseAnimatorSet = new AnimatorSet();
      private static final int DEFAULT_CHILD_GRAVITY = Gravity.END | Gravity.BOTTOM;
      private ImageView mIcon;
      private boolean mOpen;
      private boolean animating;
      private boolean mIsSetClosedOnTouchOutside = true;
  
      public interface OnMenuToggleListener {
          void onMenuToggle(boolean opened);
      }
  
      public interface OnMenuItemClickListener {
          void onMenuItemClick(FloatingActionMenu fam, int index, FloatingActionButton item);
      }
  
  
      private OnMenuItemClickListener onMenuItemClickListener;
      private OnMenuToggleListener onMenuToggleListener;
  
      public FloatingActionMenu(Context context) {
          this(context, null, 0);
      }
  
      public FloatingActionMenu(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          mMenuItems = new ArrayList&lt;&gt;(5);
          mMenuItemAnimators = new ArrayList&lt;&gt;(5);
  
          mMenuItemLabels = new ArrayList&lt;&gt;(5);
          mIcon = new ImageView(context);
      }
  
  
      @Override
      protected void onFinishInflate() {
          bringChildToFront(mMenuButton);
          bringChildToFront(mIcon);
          super.onFinishInflate();
      }
  
      @Override
      public void addView(@NonNull View child, int index, LayoutParams params) {
          super.addView(child, index, params);
          if (getChildCount() &gt; 1) {
              if (child instanceof FloatingActionButton) {
                  addMenuItem((FloatingActionButton) child);
              }
          } else {
              mMenuButton = (FloatingActionButton) child;
              mIcon.setImageDrawable(mMenuButton.getDrawable());
              addView(mIcon);
              mMenuButton.setImageDrawable(null);
              createDefaultIconAnimation();
              mMenuButton.setOnClickListener(new OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      toggle();
                  }
              });
          }
      }
  
      public void toggle() {
          if (!mOpen) {
              open();
          } else {
              close();
          }
      }
  
      public void open() {
          d("open");
          startOpenAnimator();
          mOpen = true;
          if (onMenuToggleListener != null) {
              onMenuToggleListener.onMenuToggle(true);
          }
      }
  
      public void close() {
          startCloseAnimator();
          mOpen = false;
          if (onMenuToggleListener != null) {
              onMenuToggleListener.onMenuToggle(true);
          }
      }
  
      protected void startCloseAnimator() {
          mCloseAnimatorSet.start();
          for (ItemAnimator anim : mMenuItemAnimators) {
              anim.startCloseAnimator();
          }
      }
  
      protected void startOpenAnimator() {
          mOpenAnimatorSet.start();
          for (ItemAnimator anim : mMenuItemAnimators) {
              anim.startOpenAnimator();
          }
      }
  
      public void addMenuItem(FloatingActionButton item) {
          mMenuItems.add(item);
          mMenuItemAnimators.add(new ItemAnimator(item));
          TextView button = new TextView(getContext());
          button.setBackgroundResource(R.drawable.rounded_corners);
          button.setPadding(8,8,8,8);
          button.setTextColor(Color.WHITE);
          button.setText(item.getContentDescription());
          addView(button);
          mMenuItemLabels.add(button);
          item.setTag(button);
          item.setOnClickListener(mOnItemClickListener);
          button.setOnClickListener(mOnItemClickListener);
      }
  
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int widthSize = MeasureSpec.getSize(widthMeasureSpec);
          int widthMode = MeasureSpec.getMode(widthMeasureSpec);
          int width;
          int heightSize = MeasureSpec.getSize(heightMeasureSpec);
          int heightMode = MeasureSpec.getMode(heightMeasureSpec);
          int height;
          final int count = getChildCount();
          int maxChildWidth = 0;
          for (int i = 0; i &lt; count; i++) {
              View child = getChildAt(i);
              measureChild(child, widthMeasureSpec, heightMeasureSpec);
          }
          for (int i = 0; i &lt; mMenuItems.size(); i++) {
              FloatingActionButton fab = mMenuItems.get(i);
              TextView label = mMenuItemLabels.get(i);
              maxChildWidth = Math.max(maxChildWidth, label.getMeasuredWidth() + fab.getMeasuredWidth() + mItemMargin);
          }
  
          maxChildWidth = Math.max(mMenuButton.getMeasuredWidth(), maxChildWidth);
  
          if (widthMode == MeasureSpec.EXACTLY) {
              width = widthSize;
          } else {
              width = maxChildWidth;
          }
          if (heightMode == MeasureSpec.EXACTLY) {
              height = heightSize;
          } else {
              int heightSum = 0;
              for (int i = 0; i &lt; count; i++) {
                  View child = getChildAt(i);
                  heightSum += child.getMeasuredHeight();
              }
              height = heightSum;
          }
  
          setMeasuredDimension(resolveSize(width, widthMeasureSpec),
                  resolveSize(height, heightMeasureSpec));
      }
  
  //    Rect rect = new Rect();
  //    Paint paint = new Paint();
  //
  //    @Override
  //    protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
  //        boolean b = super.drawChild(canvas, child, drawingTime);
  //        paint.setColor(0xFFFF0000);
  //        paint.setStyle(Paint.Style.STROKE);
  //        rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
  //        canvas.drawRect(rect, paint);
  //        return b;
  //    }
  
      @Override
      public boolean onTouchEvent(@NonNull MotionEvent event) {
          if (mIsSetClosedOnTouchOutside) {
              return mGestureDetector.onTouchEvent(event);
          } else {
              return super.onTouchEvent(event);
          }
      }
  
      GestureDetector mGestureDetector = new GestureDetector(getContext(),
              new GestureDetector.SimpleOnGestureListener() {
  
                  @Override
                  public boolean onDown(MotionEvent e) {
                      return mIsSetClosedOnTouchOutside &amp;&amp; isOpened();
                  }
  
                  @Override
                  public boolean onSingleTapUp(MotionEvent e) {
                      close();
                      return true;
                  }
              });
  
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          System.out.println("onLayout:" + changed);
          if (changed) {
              int right = r - getPaddingRight();
              int bottom = b - getPaddingBottom();
              int top = bottom - mMenuButton.getMeasuredHeight();
  
  
              mMenuButton.layout(right - mMenuButton.getMeasuredWidth(), top, right, bottom);
              int dw = (mMenuButton.getMeasuredWidth() - mIcon.getMeasuredWidth()) / 2;
              int dh = (mMenuButton.getMeasuredHeight() - mIcon.getMeasuredHeight()) / 2;
              mIcon.layout(right - mIcon.getMeasuredWidth() - dw, bottom - mIcon.getMeasuredHeight() - dh, right - dw, bottom - dh);
              for (int i = 0; i &lt; mMenuItems.size(); i++) {
                  FloatingActionButton item = mMenuItems.get(i);
                  TextView label = mMenuItemLabels.get(i);
                  bottom = top;
                  top -= item.getMeasuredHeight();
                  int width = item.getMeasuredWidth();
                  int d = (mMenuButton.getMeasuredWidth() - width) / 2;
                  item.layout(right - width - d, top, right - d, bottom);
                  d = (item.getMeasuredHeight() - label.getMeasuredHeight()) / 2;
                  label.layout(item.getLeft() - mItemMargin - label.getMeasuredWidth(), item.getTop() + d, item.getLeft() - mItemMargin, item.getTop() + d + label.getMeasuredHeight());
                  if (!animating) {
                      if (!mOpen) {
                          item.setTranslationY(mMenuButton.getTop() - item.getTop());
                          item.setVisibility(GONE);
                          label.setVisibility(GONE);
                      } else {
                          item.setTranslationY(0);
                          item.setVisibility(VISIBLE);
                          label.setVisibility(VISIBLE);
                      }
                  }
              }
              if (!animating &amp;&amp; getBackground() != null) {
                  if (!mOpen) {
                      getBackground().setAlpha(0);
                  } else {
                      getBackground().setAlpha(0xff);
                  }
              }
          }
      }
  
      private void createDefaultIconAnimation() {
          Animator.AnimatorListener listener = new Animator.AnimatorListener() {
              @Override
              public void onAnimationStart(Animator animation) {
                  animating = true;
              }
  
              @Override
              public void onAnimationEnd(Animator animation) {
                  animating = false;
              }
  
              @Override
              public void onAnimationCancel(Animator animation) {
                  animating = false;
              }
  
              @Override
              public void onAnimationRepeat(Animator animation) {
  
              }
          };
          ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(
                  mIcon,
                  "rotation",
                  135f,
                  0f
          );
  
          ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(
                  mIcon,
                  "rotation",
                  0f,
                  135f
          );
  
          if (getBackground() != null) {
  
  
              ValueAnimator hideBackgroundAnimator = ObjectAnimator.ofInt(0xff, 0);
              hideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
                      Integer alpha = (Integer) animation.getAnimatedValue();
                      //System.out.println(alpha);
                      getBackground().setAlpha(alpha &gt; 0xff ? 0xff : alpha);
                  }
              });
              ValueAnimator showBackgroundAnimator = ObjectAnimator.ofInt(0, 0xff);
              showBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
  
                      Integer alpha = (Integer) animation.getAnimatedValue();
                      //System.out.println(alpha);
                      getBackground().setAlpha(alpha &gt; 0xff ? 0xff : alpha);
                  }
              });
  
              mOpenAnimatorSet.playTogether(expandAnimator, showBackgroundAnimator);
              mCloseAnimatorSet.playTogether(collapseAnimator, hideBackgroundAnimator);
          } else {
              mOpenAnimatorSet.playTogether(expandAnimator);
              mCloseAnimatorSet.playTogether(collapseAnimator);
          }
  
          mOpenAnimatorSet.setInterpolator(DEFAULT_OPEN_INTERPOLATOR);
          mCloseAnimatorSet.setInterpolator(DEFAULT_CLOSE_INTERPOLATOR);
  
          mOpenAnimatorSet.setDuration(ANIMATION_DURATION);
          mCloseAnimatorSet.setDuration(ANIMATION_DURATION);
  
          mOpenAnimatorSet.addListener(listener);
          mCloseAnimatorSet.addListener(listener);
      }
  
      static final TimeInterpolator DEFAULT_OPEN_INTERPOLATOR = new OvershootInterpolator();
      static final TimeInterpolator DEFAULT_CLOSE_INTERPOLATOR = new AnticipateInterpolator();
  
      public boolean isOpened() {
          return mOpen;
      }
  
      private class ItemAnimator implements Animator.AnimatorListener {
          private View mView;
          private boolean playingOpenAnimator;
  
          public ItemAnimator(View v) {
              v.animate().setListener(this);
              mView = v;
          }
  
          public void startOpenAnimator() {
              mView.animate().cancel();
              playingOpenAnimator = true;
              mView.animate().translationY(0).setInterpolator(DEFAULT_OPEN_INTERPOLATOR).start();
          }
  
          public void startCloseAnimator() {
              mView.animate().cancel();
              playingOpenAnimator = false;
              mView.animate().translationY((mMenuButton.getTop() - mView.getTop())).setInterpolator(DEFAULT_CLOSE_INTERPOLATOR).start();
          }
  
          @Override
          public void onAnimationStart(Animator animation) {
              if (playingOpenAnimator) {
                  mView.setVisibility(VISIBLE);
              } else {
                  ((TextView) mView.getTag()).setVisibility(GONE);
              }
          }
  
          @Override
          public void onAnimationEnd(Animator animation) {
              if (!playingOpenAnimator) {
                  mView.setVisibility(GONE);
              } else {
                  ((TextView) mView.getTag()).setVisibility(VISIBLE);
              }
          }
  
          @Override
          public void onAnimationCancel(Animator animation) {
  
          }
  
          @Override
          public void onAnimationRepeat(Animator animation) {
          }
      }
  
  
      @Override
      public Parcelable onSaveInstanceState() {
          d("onSaveInstanceState");
          Bundle bundle = new Bundle();
          bundle.putParcelable("instanceState", super.onSaveInstanceState());
          bundle.putBoolean("mOpen", mOpen);
          // ... save everything
          return bundle;
      }
  
      @Override
      public void onRestoreInstanceState(Parcelable state) {
          d("onRestoreInstanceState");
          if (state instanceof Bundle) {
              Bundle bundle = (Bundle) state;
              mOpen = bundle.getBoolean("mOpen");
              // ... load everything
              state = bundle.getParcelable("instanceState");
          }
          super.onRestoreInstanceState(state);
      }
  
      @Override
      protected void onDetachedFromWindow() {
          d("onDetachedFromWindow");
          //getBackground().setAlpha(bgAlpha);//reset default alpha
          super.onDetachedFromWindow();
      }
  
  
      @Override
      public void setBackground(Drawable background) {
          if (background instanceof ColorDrawable) {
              // after activity finish and relaucher , background drawable state still remain?
              int bgAlpha = Color.alpha(((ColorDrawable) background).getColor());
              d("bg:" + Integer.toHexString(bgAlpha));
              super.setBackground(background);
          } else {
              throw new IllegalArgumentException("floating only support color background");
          }
      }
  
      private OnClickListener mOnItemClickListener = new OnClickListener() {
          @Override
          public void onClick(View v) {
              if (v instanceof FloatingActionButton) {
                  int i = mMenuItems.indexOf(v);
                  if (onMenuItemClickListener != null) {
                      onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, (FloatingActionButton) v);
                  }
              } else if (v instanceof TextView) {
                  int i = mMenuItemLabels.indexOf(v);
                  if (onMenuItemClickListener != null) {
                      onMenuItemClickListener.onMenuItemClick(FloatingActionMenu.this, i, mMenuItems.get(i));
                  }
              }
              close();
          }
      };
  
      public OnMenuToggleListener getOnMenuToggleListener() {
          return onMenuToggleListener;
      }
  
      public void setOnMenuToggleListener(OnMenuToggleListener onMenuToggleListener) {
          this.onMenuToggleListener = onMenuToggleListener;
      }
  
      public OnMenuItemClickListener getOnMenuItemClickListener() {
          return onMenuItemClickListener;
      }
  
      public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
          this.onMenuItemClickListener = onMenuItemClickListener;
      }
  
  
      protected void d(String msg) {
          Log.d("FAM", msg == null ? null : msg);
      }
  }
  </code></pre>
  
  <p><strong>The Menu XML:</strong></p>
  
  <pre><code>&lt;terranovaproductions.newcomicreader.FloatingActionMenu
      android:id="@+id/fab_menu"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:padding="16dp"&gt;
  
      &lt;!--First button as menu button--&gt;
      &lt;android.support.design.widget.FloatingActionButton
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:src="@drawable/search"
          android:paddingBottom="@dimen/menu_button_margin"
          fab:fabSize="normal"
          android:id="@+id/fab_main" /&gt;
  
      &lt;!-- Other button as menu items--&gt;
      &lt;android.support.design.widget.FloatingActionButton
          android:id="@+id/fab_random"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:contentDescription="@string/default_random"
          android:src="@drawable/random"
          android:paddingBottom="@dimen/menu_button_margin"
          fab:fabSize="mini"/&gt;
  
  
      &lt;android.support.design.widget.FloatingActionButton
          android:id="@+id/fab_search"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:contentDescription="@string/default_search"
          android:src="@drawable/search"
          android:paddingBottom="@dimen/menu_button_margin"
          fab:fabSize="mini"/&gt;
  
      &lt;android.support.design.widget.FloatingActionButton
          android:id="@+id/fab_browser"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:contentDescription="@string/default_browser"
          android:src="@drawable/browseropen"
          android:paddingBottom="@dimen/menu_button_margin"
          fab:fabSize="mini"/&gt;
  
  &lt;/terranovaproductions.newcomicreader.FloatingActionMenu&gt;
  </code></pre>
  
  <p>This XML code is in a RelativeLayout with other items.</p>
  
  <p><strong>What I have Tried:</strong></p>
  
  <ol>
  <li>I have tried adding padding in the XML to the individual buttons</li>
  <li>I have tried adding margins to the individual buttons in both the class and XML</li>
  <li>I have looked for where the buttons are drawn to find a way to add space, but I have had no luck.</li>
  </ol>