Jakub Mosakowski
Jakub Mosakowski

Reputation: 549

How to set recycler height to highest item in recyclerView?

I need to make sure that horizontal recyclerView height is the same as the height of the biggest item.

Items can have a different height (Item = always the same image + title + subtitle, title and subtitle could have infinite length). When I set wrap_content for my recyclerView it would resize, basing on the height of visible items which makes content below recyclerView jump, and that's something I want to avoid.

What I want to achieve: what I want to achive The gray area is visible viewport.

So basically I would like to get somehow hight of the biggest item, then put recyclerView height to that number.

What I already tried is approximation high of items based on length of title + subtitle but it's very inaccurate because for example even if two titles have the same text length they could have different width because of font that I use which is not a monospace font.

Upvotes: 25

Views: 19252

Answers (10)

Mykyta Skubak
Mykyta Skubak

Reputation: 3

I know that this thread is old but if somebody will face the same issue... You can achieve what you want by setting RecyclerView layout settings like below

<androidx.recyclerview.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layoutManagerOrientation="horizontal"
                app:layoutManagerType="linear" />

and when u create your ViewHolder, change your view layout params to

  override fun onCreateViewHolder(view: ViewGroup, parentType: Int): YourViewHolder {
    val binding = ItemBinding.inflate(LayoutInflater.from(view.context))
    binding.root.layoutParams = ViewGroup.LayoutParams((view.width).toInt(), ViewGroup.LayoutParams.MATCH_PARENT)
    return YourViewHolder(binding)
}

Upvotes: 0

Anonymous
Anonymous

Reputation: 361

Too late for an answer, but maybe this will help someone.

I struggled with the same issue and couldn't find an acceptable solution.

Solved by following:

  1. First, you need to override onMeasure from the RecyclerView to save the largest element height:
class CustomRecycleView(ctx: Context, attrs: AttributeSet) : RecyclerView(ctx, attrs) {

    private var biggestHeight: Int = 0

    override fun onMeasure(widthSpec: Int, heightSpec: Int) {
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            val h = child.measuredHeight
            if (h > biggestHeight) biggestHeight = h
        }

        super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(biggestHeight, MeasureSpec.EXACTLY))
    }
  
}
  1. In you layout replace RecycleView with this CustomRecycleView:

onMeasure is called when a new element in the list is visible, and if the element is the highest, then we save this value. For example: if the first element has lowest height but lates has highest then at start RecycleView will be have height match to first element but after scrolling it will stay match to highest. If you don't need to make RecycleView height match to highest item at start then you can stop here.

  1. To do this at the beginning, you must make a hack (based on @MidasLefko suggestion): To find out initially what the height of the highest element will be, you need to add a scroll mechanism to the end and the beginning. I did it as follows:
private fun initRecycleView(items: ArrayList<Object>) {
    val adapter = Adapter()
    rv.visibility = View.INVISIBLE
    rv.vadapter = adapter
    rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
    rv.setHasFixedSize(true)
    rv.smoothScrollToPosition(pinnedPosts.size)
    Handler().postDelayed({
        rv.smoothScrollToPosition(0)
    }, 300)
    Handler().postDelayed({
        rv.visibility = View.VISIBLE
    }, 700)
}

Set the visibility of Recycle view to INVISIBLE and after 700 milliseconds to VISIBLE to make this process invisible for user. Also, scrolling to start is performed with a delay of 300 milliseconds, because without some delay it can work incorrectly. In my case, this is needed for a list of 3 elements, and these delays is optimal for me.

Also remember to remove all Handler callbacks in onStop ()

Upvotes: 7

Akash Kumar
Akash Kumar

Reputation: 1

recyclerViewHorizontal.setMinimumHeight(maxItemHeight) has worked well for me.

Upvotes: 0

Dinkar Kumar
Dinkar Kumar

Reputation: 2250

Created a method to calculate the projected height of textView by trying all the description in the list to get the highest height.

 public static int getHeightOfLargestDescription(final Context context, final CharSequence text, TextView textView) {
    final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    final Point displaySize = new Point();
    wm.getDefaultDisplay().getSize(displaySize);
    final int deviceWidth = displaySize.x;
    textView.setTypeface(Typeface.DEFAULT);
    textView.setText(text, TextView.BufferType.SPANNABLE);
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();
}

then used this method to in onCreateViewHolder to get ready with the highest height to be used while binding the view.

        MyViewHolder myViewHolder = new MyViewHolder(itemView);
    for (Model m : modelList) {
        currentItemHeight = getHeightOfLargestDescription(context, m.description, myViewHolder.description);
        if (currentItemHeight > highestHeight) {
            highestHeight = currentItemHeight;
        }
    }

Then used this highestHeight in onBindViewHolder` to set the height of the description TexView, so that all the views always have the same height that is equal to the highest height.

 viewHolder.description.setHeight(highestHeight);

Code is committed in the https://github.com/dk19121991/HorizontalRecyclerWithDynamicHeight

enter image description here

Let me know if this solves your problem, if you have some more question feel free to ask.

Thanks

To view a full discussion on this solution please see below https://stackoverflow.com/a/67403898/4828650

Upvotes: 5

Rahul Tiwari
Rahul Tiwari

Reputation: 6978

I disabled the recycling in recycler view and it solved the issue.

recyclerView.getRecycledViewPool().setMaxRecycledViews(TYPE_CAROUSEL, 0);

this solution may have a performance issue if there are a lot of items but will work fine for a few items lets say 5 to 20 which was case for me.

Upvotes: 0

Elyes Mansour
Elyes Mansour

Reputation: 144

I just had this issue as well. My solution is:

  1. Wrap the RecyclerView inside a ConstraintLayout.
  2. Set the ConstraintLayout's layout_height to wrap_content.
  3. Add an item view to the ConstraintLayout and populate it with the data of the item you expect to be the highest based on the length of its title for example.
  4. Set the item view's visibility to invisible.
  5. Set the RecyclerView's layout_height to zero, and make its top and bottom constraints match that of the item view.

Upvotes: 9

january
january

Reputation: 59

You may try this:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            final int newHeight = recyclerView.getMeasuredHeight();
            if (0 != newHeight && minHeight < newHeight) {
            // keep track the height and prevent recycler view optimizing by resizing
                minHeight = newHeight;
                recyclerView.setMinimumHeight(minHeight);
            }
        }
    });

Upvotes: 1

MidasLefko
MidasLefko

Reputation: 4559

I don't think that this is possible out of the box.

Let's think for a minute about how a RecyclerView works. In order to save memory it reuses the same View objects and just binds them to new data from the list as the user scrolls. So, for example, if the user sees item's 0 and 1 then the system has only measured and laid out 2 items (and perhaps one or two more to help scroll performance).

But let's say that your tall item is number 50 in the list, when the RecyclerView binds the first few items it has no idea at all that item 50 even exists, let alone how tall it will be.

However, you can do something a bit hacky. For example, you can measure each items height after it is bound, keep track of the tallest, and then manually set the RecyclerView height to that size. With that mechanism in place you can make the RecyclerView be hidden, then manually scroll to the end of the list, scroll back to the beginning of the list, then show the RecyclerView.

Not the most elegant solution, but it should work.

Upvotes: 4

pratik vekariya
pratik vekariya

Reputation: 1125

Try this

    @Override
     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

     View itemView = mLayoutInflater.inflate(R.layout.view_item, parent, false);
    // work here if you need to control height of your items
    // keep in mind that parent is RecyclerView in this case
    int height = parent.getMeasuredHeight() / 4;
    itemView.setMinimumHeight(height);
    return new ItemViewHolder(itemView);        
    }

Or you can try this also

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View itemView = inflater.inflate(R.layout.itemview, parent, false);

    ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
    layoutParams.height = (int) (parent.getHeight() * 0.3);
    itemView.setLayoutParams(layoutParams);

    return new MyViewHolder(itemView);
   }

You can also set your itemView with fixed height.

Upvotes: 0

Govind Prajapati
Govind Prajapati

Reputation: 206

you should try with different item_view type

Upvotes: 0

Related Questions