Search code examples
androidonmeasure

Android Swipe menu on list item can't handle parent width changes


I am trying to create a swipe menu for my items in recyclerview and ended up implemeting the library: https://github.com/chthai64/SwipeRevealLayout

At first look at it after implementing it, I thought it was working. But for some reason, it does not change/measure/layout the correct width of the item when the parent view/layout (framelayout for containing fragment) changes.

The item simply keep the same width, which is either too wide or too short, depending of which way the parent view scales.

I have included the onMeasure method from the custom view "SwipeRevealLayout" from the library.

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (getChildCount() < 2) {
            throw new RuntimeException("Layout must have two children");
        }

        final LayoutParams params = getLayoutParams();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int desiredWidth = 0;
        int desiredHeight = 0;

        // first find the largest child
        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth);
            desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight);
        }

        // create new measure spec using the largest child width
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, widthMode);
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, heightMode);

        final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);

        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            final LayoutParams childParams = child.getLayoutParams();

            if (childParams != null) {
                if (childParams.height == LayoutParams.MATCH_PARENT) {
                    child.setMinimumHeight(measuredHeight);
                }

                if (childParams.width == LayoutParams.MATCH_PARENT) {
                    child.setMinimumWidth(measuredWidth);
                }
            }

            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth);
            desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight);
        }

        // taking accounts of padding
        desiredWidth += getPaddingLeft() + getPaddingRight();
        desiredHeight += getPaddingTop() + getPaddingBottom();

        // adjust desired width
        if (widthMode == MeasureSpec.EXACTLY) {
            desiredWidth = measuredWidth;
        } else {
            if (params.width == LayoutParams.MATCH_PARENT) {
                desiredWidth = measuredWidth;
            }

            if (widthMode == MeasureSpec.AT_MOST) {
                desiredWidth = (desiredWidth > measuredWidth)? measuredWidth : desiredWidth;
            }
        }

        // adjust desired height
        if (heightMode == MeasureSpec.EXACTLY) {
            desiredHeight = measuredHeight;
        } else {
            if (params.height == LayoutParams.MATCH_PARENT) {
                desiredHeight = measuredHeight;
            }

            if (heightMode == MeasureSpec.AT_MOST) {
                desiredHeight = (desiredHeight > measuredHeight)? measuredHeight : desiredHeight;
            }
        }

        setMeasuredDimension(desiredWidth, desiredHeight);
    }

I found a part of the solution somewhere else, where all of the content of the item was scaled correctly. I replaced all the code in onMeasure withe the code below. However, this solution had a side effect, where the item is wiped all the way out of the screen instead of stopping just before the buttons of the swipe menu.

super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth() / 2);
        // this is required because the children keep the super class calculated dimensions (which will not work with the new MyFrameLayout sizes)
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);

            v.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                    MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                    getMeasuredHeight(), MeasureSpec.EXACTLY));
        }

Solution

  • Never mind :) I finally solved it by manipulating the width of the items in onMeasure. This is working perfectly for me. Maybe someone else can use it. What I did, was to check if the first child item is wider than I allow it to be (wider than the parent view) and set it to the max width if it is. if other child views (e.g. for the item in the list) have a width/measured width that IS NOT equal to the parent views width, I am set the childs width to this.

    Here is the top of onMeasure, which I have changed a bit.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (getChildCount() < 2) {
            throw new RuntimeException("Layout must have two children");
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        final LayoutParams params = getLayoutParams();
    
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
        int maxWidth        = getMeasuredWidth();
        int desiredWidth    = 0;
        int desiredHeight   = 0;
    
        // first find the largest child
        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            if (i == 0 && (child.getWidth() > maxWidth || child.getMeasuredWidth() > maxWidth)
                || i > 0 && (child.getWidth() != maxWidth || child.getMeasuredWidth() != maxWidth)) {
                LayoutParams lm = child.getLayoutParams();
                lm.width        = maxWidth;
                child.setLayoutParams(lm);
            }
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth);
            desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight);
        }
    
    ...