PPartisan PPartisan - 1 month ago 15
Android Question

Force RecyclerView to bottom of page in layout

I have a page that consists of an

ImageView
, and a
RecyclerView
. The
RecyclerView
contains a small number of items (currently three) and only takes up around a quarter of the screen on my test device. However, despite trying numerous layout options, I cannot get the
RecyclerView
to effectively wrap its content and take up just enough space required to contain these three rows, and leave the rest of the space for my
ImageView
.

To help illustrate what I mean, I have drawn two diagrams. The first shows what I would like to happen, and the second what is happening:

This is the Correct Layout This is the current (incorrect) layout

So far, I have tried several different combinations of
RelativeLayout
- for instance, I will set
RecyclerView
to
layout:align_ParentBottom
and the second
RelativeLayout
that contains the
ImageView
to
layout:alignComponent
so that its bottom matches the
RecyclerView
top (this would normally drag the
ImageView
layout so that it fills any reminaing space, which is what I would like to happen.)

The
RecyclerView
though just keeps occupying all the space it can, even though it only contains a few rows. The current "solution" I have is to place everything inside a
LinearLayout
and set less
gravity
to the
RecyclerView
, but it isn't ideal, because on different emulators it wont line up completely with the bottom of the screen, and in others there isn't enough room and the
RecyclerView
becomes scrollable.

Thanks in advance for any help and suggestions anyone can offer.

Answer

Many thanks to everyone who contributed, but I have found a programmatic solution outside of the layout files. In case anyone else is looking for a solution to this problem, I found one here.

It appears as if there is an issue with RecyclerView currently where it doesn't wrap content. The answer is to construct a custom class that extends LinearLayoutManager. I have posted the solution that worked for me below - most of it is copy and pasted from the answer given in the link I quoted. The only small issue is that it doesn't account for the extra space added by decorations, which is why I had to make a small tweak to the following line near the end of the code:

//I added the =2 at the end.    
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin + 2;

Here is the code in its entirety:

public class HomeLinearLayoutManager extends LinearLayoutManager{

    HomeLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin + 2;
            recycler.recycleView(view);
        }
    }
}

Thanks again.

Comments