15

With version 23.2 we can now use WRAP_CONTENT for recyclerView height, which is great. I am doing this, however I want to recalculate the height after an item is added (or removed) to the adapter (thus increasing or decreasing the height).

My particular RecyclerView is starting with 1 item, and then adding items as the user makes selection. So I need the RecyclerView layout to increase in height, up to a point. Ideally this would happen with a smooth animation when the list increases or decreases.

How can we make it WRAP_CONTENT after it has been laid out?

Tried:

recyclerview.requestLayout();

recyclerview.invalidate();
F43nd1r
  • 7,690
  • 3
  • 24
  • 62
Patrick Jackson
  • 18,766
  • 22
  • 81
  • 141
  • Can you explain what you are trying to achieve with more details? adding the layout xml and some code will be great too... – crazyPixel Mar 25 '16 at 20:20
  • 4
    Make sure you don't call `setHasFixedSize` on your recycler view. – Wukash Mar 25 '16 at 21:18
  • I had this problem. You're going to need to call measure, but there is a bug in android M if you use multiple child layouts. To get around this add a GlobalLayoutListener and set the height in onGlobalLayout – zafrani Mar 25 '16 at 23:45
  • I think a bit unclear what you want. –  Mar 31 '16 at 05:19
  • please have a look into the following link http://stackoverflow.com/a/33542789/3257178 – Arun Antoney Mar 31 '16 at 10:15

4 Answers4

7

I would expect it to work with View.invalidate().

If that does not work, try to call either requestLayout or invalidate on the parent view.

F43nd1r
  • 7,690
  • 3
  • 24
  • 62
  • Calling invalidate on the parent view did the trick. Of course it does not animate the growing or shrinking. Animating is much more difficult problem, but this is the closest solution to my problem. – Patrick Jackson Apr 01 '16 at 21:05
4

How RecyclerView Resize itself based on with the new LayoutManger

The RecyclerView widget provides an advanced and flexible base for creating lists and grids as well as supporting animations. This release brings an exciting new feature to the LayoutManager API: auto-measurement! This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You’ll find all built in LayoutManagers now support auto-measurement.

Due to this change, make sure to double check the layout parameters of your item views: previously ignored layout parameters (such as MATCH_PARENT in the scroll direction) will now be fully respected.

If you have a custom LayoutManager that does not extend one of the built in LayoutManagers, this is an opt-in API - you’ll be required to call setAutoMeasureEnabled(true) as well as make some minor changes as detailed in the Javadoc of the method.

Note that although RecyclerView animates its children, it does not animate its own bounds changes. If you would like to animate the RecyclerView bounds as they change, you can use the Transition APIs.

Please read this

Carlos
  • 963
  • 9
  • 19
2

Option I

Did you see this answer?

It's not using the recyclerView's WRAP_CONTENT, but it might work.

You can also create your own custom recyclerView (extends RecyclerView) and override the onMeasure() method there instead using the layoutManager in the link.

Option II

Try to set the layout params before drawing the view. I haven't checked if it is called when recyclerView layout changes, but if it does, then it will work. Something like this (in your Activity/Fragment onCreate()/onCreateView() method:

recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() { 
                recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);

                YourParentLayoutType.LayoutParams params = (YourParentLayoutType.LayoutParams) recyclerView.getLayoutParams();
                params.height = YourParentLayoutType.LayoutParams.WRAP_CONTENT;
                recyclerView.setLayoutParams(params);

                return true;
            }
        });

Use your recyclerView's parent layout type instead of YourParentLayoutType in the code. I'm not sure this will work when layout refreshes, but maybe worth a try.

Community
  • 1
  • 1
e-shfiyut
  • 3,538
  • 2
  • 30
  • 31
  • 1
    What worked for me, was to re-set the layout params with wrap_content after the item was added every time. – password May 16 '18 at 23:20
1

Use this class:

Please use 23.2.1 as 23.2 was way buggy.

Also have you tried to call notifyDataSetChanged on the recyclerview adapter , as far as i think it should expand without problem if you have given wrap_content as height of recyclerview

else u can use this class:

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

public class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(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++) {
            if (getOrientation() == HORIZONTAL) {
                measureScrapChild(recycler, i,
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                        heightSpec,
                        mMeasuredDimension);

                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                measureScrapChild(recycler, i,
                        widthSpec,
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                        mMeasuredDimension);

                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:
        }

        int widthDesired = Math.min(widthSize,width);
        setMeasuredDimension(widthDesired, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        // For adding Item Decor Insets to view
        super.measureChildWithMargins(view, 0, 0);
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(
                widthSpec,
                getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view),
                p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(
                heightSpec,
                getPaddingTop() + getPaddingBottom() + getDecoratedTop(view) + getDecoratedBottom(view) ,
                p.height);
        view.measure(childWidthSpec, childHeightSpec);

        // Get decorated measurements
        measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
        measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
        }
    }
JAAD
  • 12,349
  • 7
  • 36
  • 57
  • 1
    You should be checking the loop counter against the state's itemCount `i < state.getItemCount()` in this one, otherwise it gives IndexOutOfBounds – AA_PV Jan 11 '17 at 07:24