android – Horizontal RecyclerView with items that have dynamic height-ThrowExceptions

Exception or error:

So I need a horizontal RecyclerView. However the items in them have dynamic height based on what’s returned from the backend.

The RecyclerView and its items have height of “wrap_content”

The problem is if the first visible items in the RecyclerView are small, then when the user scrolls to the larger items, they appear cut off.

Ideally we want the RecyclerView to be large enough to hold the largest item in the group (but only the largest existing item… not the hypothetically largest item). The RecyclerView dynamically changing its height is also acceptable.

I wrote an example app to illustrate the problem. In this app, we have a horizontal list. The first 33 items should show 1 line of text, while the next should show 3 lines of text, and the last 34 should show 2 lines. However, they all show 1 line, because the height of the RecyclerView doesn’t change.

I’ve tried calling requestLayout() on the RecyclerView (and on the TextView) from onBindViewHolder(), but it doesn’t seem to do anything.

MainActivity:

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAdapter = createAdapter();

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        mRecyclerView.setAdapter(mAdapter);
    }

    private RecyclerView.Adapter<ViewHolder> createAdapter() {
        RecyclerView.Adapter adapter = new RecyclerView.Adapter<ViewHolder>() {
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
                return new ViewHolder(v);
            }

            @Override
            public void onBindViewHolder(ViewHolder holder, int position) {
                if (position < 33) {
                    holder.mText.setText("1 line");
                } else if (position > 66) {
                    holder.mText.setText("2 lines\n2 lines");
                } else {
                    holder.mText.setText("3 lines\n3 lines\n3 lines");
                }
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        };
        return adapter;
    }

    private static class ViewHolder extends RecyclerView.ViewHolder {
        TextView mText;
        public ViewHolder(View itemView) {
            super(itemView);
            mText = (TextView) itemView.findViewById(R.id.text);
        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#888888">
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hi"
        android:textSize="30sp" />

</LinearLayout>
How to solve:

Change your recyclerView height & width from wrap_content to match_parent or you can also give fixed height to your recyclerView like android:layout_height="150dp"

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#888888" />

</RelativeLayout>

or You can try

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:background="#888888" />

</RelativeLayout>

###

I wrote a class that can set max height of recyclerview based on your items.
Try this class:

public class DynamicRecyclerViewHeight extends RecyclerView {

    private int mMaxHeight;

    public DynamicRecyclerViewHeight(@NonNull Context context) {
        super(context);
    }

    public DynamicRecyclerViewHeight(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs);
    }

    public DynamicRecyclerViewHeight(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize(context, attrs);
    }

    private void initialize(Context context, AttributeSet attrs) {
        TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.CustomRecyclerView);
        mMaxHeight = arr.getLayoutDimension(R.styleable.CustomRecyclerView_maxHeight, mMaxHeight);
        arr.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mMaxHeight > 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public void setmMaxHeight(int mMaxHeight) {
        this.mMaxHeight = mMaxHeight;
        invalidate();
    }
}

You should add these line to attrs.xml file:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DynamicRecyclerViewHeight">
        <attr name="maxHeight" format="dimension" />
    </declare-styleable>
</resources>

Then you can use this method for set maxHeight toyour recyclerView:

yourRecyclerView.setMaxHeight(yourHeight);

Good Luck.

###

Just call itemAdapter.notifyDataSetChanged();

Leave a Reply

Your email address will not be published. Required fields are marked *