Search code examples
javaandroidandroid-4.4-kitkatfloating-action-button

KitKat FloatingActionButton has extra margins


I'm trying to present a PopupWindow from a FloatingActionButton Since this is part of an SDK intended for others to use, I can't depend on the location or some attributes of the FloatingActionButton Instead, I have to find an appropriate location near the FAB to show the PopupWindow I currently have some code which seems to be working fine on Lollipop and newer. When I run it on KitKat, there is some additional spacing between the FAB and the Popup. I've managed to eliminate the space by hardcoding some offsets, but that's a really ugly solution (and depends on the elevation of the FAB to be identical to how I've configured it, which also isn't a reliable precondition)

Expected appearance (works on Lollipop):

enter image description here

Incorrect appearance on KitKat:

enter image description here

First the code I'm using to present the popup:

public void onShowPopup(View view) {
    View layout = getLayoutInflater().inflate(R.layout.popup_menu, null);

    PositionedPopupWindow.withLayout(layout).showRelativeToView(
            view,
            PositionedPopupWindow.AnchorPoint.values()[spinner.getSelectedItemPosition()],
            0, // getResources().getDimensionPixelSize(R.dimen.fab_hmargin),
            0  // getResources().getDimensionPixelSize(R.dimen.fab_vmargin)
    );
}

I should be able to pass 0 to both of the last two parameters and have the popup show adjacent to the FAB. It works fine on Lollipop and later, but on KitKat I have to pass in some magic numbers that depend on the elevation.

Here's the body of showRelativeToView It's most of the implementation of PositionedPopupWindow

public void showRelativeToView(View parent, AnchorPoint anchorPoint, int hMargin, int vMargin) {
    if(anchorPoint == AnchorPoint.Automatic) {
        anchorPoint = computeAutomaticAnchorPoint(parent);
    }

    // Get the size of the popup
    getContentView().measure(
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    );
    Point popupSize = new Point(getContentView().getMeasuredWidth(), getContentView().getMeasuredHeight());

    int gravity = 0, x = 0, y = 0;

    // Horizontally align the popup edge closest to the screen edge with the button edge
    switch(anchorPoint) {
        case TopLeft:
        case BottomLeft:
            gravity |= Gravity.LEFT;
            x += hMargin;
            break;

        case TopRight:
        case BottomRight:
            x = -popupSize.x;
            x -= hMargin;
            gravity |= Gravity.RIGHT;
            break;
    }

    // Vertically align the popup edge closest to the screen edge with the button edge
    switch(anchorPoint) {
        case TopLeft:
        case TopRight:
            y = -popupSize.y - parent.getMeasuredHeight();
            y += vMargin;
            gravity |= Gravity.TOP;
            break;

        case BottomLeft:
        case BottomRight:
            // align top of popover and bottom of button
            gravity |= Gravity.BOTTOM;
            y -= vMargin;
            break;
    }

    PopupWindowCompat.showAsDropDown(this, parent, x, y, gravity);
}

I've tried to setting the margins of the FAB to 0 within my onCreate method, as suggested elsewhere to get rid of the extra margin, but it doesn't seem to change anything.

My question is, how can I get rid of the extra spacing between the FAB and the PopupWindow without hardcoding magic numbers (which won't work reliably)


Solution

  • The problem seems to arise because KitKat puts an extra margin around FloatingActionButton to account for the elevation shadow, since this changes the size of the FAB, I can compare the actual button size to the expect size and compute the necessary offset:

    public void onShowPopup(View view) {
        View layout = getLayoutInflater().inflate(R.layout.popup_menu, null);
    
        int hMargin = 0;
        int vMargin = 0;
    
        // Pre-Lollipop the FAB shadow is factored into the button size, KitKat and on the
        //  shadow isn't part of the button size.  By comparing the actual width to the expected
        //  width we can compute an offset necessary to position the popup adjacent to the FAB
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            hMargin += (int) (view.getWidth() - Util.dpToPixels(this, 56)) / 2;
            vMargin += (int) (view.getHeight() - Util.dpToPixels(this, 56)) / 2;
        }
    
        PositionedPopupWindow.withLayout(layout).showRelativeToView(
                view,
                PositionedPopupWindow.AnchorPoint.Automatic,
                hMargin,
                vMargin
        );
    }