How to set recycler height to highest item in recyclerView?
Asked Answered
S

10

24

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.

Shuck answered 11/9, 2019 at 13:21 Comment(4)
Possible duplicate of How do I make WRAP_CONTENT work on a RecyclerViewEnrichetta
It is not, because in my case WRAP_CONTENT "works". I mean that it does what it supposed to do, but I want to achieve a different result than the one that WRAP_CONTENT supposed to give.Shuck
https://mcmap.net/q/103249/-how-do-i-make-wrap_content-work-on-a-recyclerview this worked for me,Farman
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
E
9

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.
Empanel answered 8/4, 2020 at 10:8 Comment(1)
I did something similar recently, and although it seems like a hacky solution, it allows you to not have to set a static height on the entire RecyclerView and always have a dynamic height based on the tallest ViewHolder.Acro
G
6

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 ()

Galenical answered 13/12, 2019 at 9:45 Comment(2)
This is a VERY good answer. Luckily for my use case I only needed steps 1 & 2, but this helped solve a problem we've had for multiple days!Proportionate
Again just in case it helps anyone else, I had to make a very small tweak in step 2 to replace this line: child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) with child.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) This was basically just because the ViewHolder width was getting messed up for the child intermittently, as it was using the widthSpec of the entire recyclerview, rather than just calculating the ViewHolder width.Proportionate
M
4

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.

Melisma answered 11/9, 2019 at 13:36 Comment(1)
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
D
4

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

Demetriusdemeyer answered 5/5, 2021 at 15:7 Comment(1)
Awesome dude, you saved me, this is just what I was looking for just bang on. great thanks a lot.Doubletree
D
1

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);
            }
        }
    });
Disini answered 5/12, 2019 at 7:50 Comment(2)
Welcome to Stackoverflow. Plase explain what your code does. So others can understand more easily.Speck
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
O
0

you should try with different item_view type

Orchard answered 11/9, 2019 at 13:23 Comment(4)
Could you explain a bit more what do you mean?Shuck
I mean to that you can use diffrent view in recyclerview. for example in any chatlist adapter we are having different views in single adapter like simple text , image , video. For this we are using different viewType as per required. for reference see this #26245639Orchard
Oh, I get you but that's not the case. There is only one type of items: CustomView which contains Image + title + subtitle.Shuck
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
S
0

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.

Sapienza answered 11/9, 2019 at 13:29 Comment(2)
I'm not sure if I explained it good enough, but I need to make my recyclerView to match its items (which I don't know how high they will be).Shuck
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
H
0

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.

Hinduism answered 25/3, 2021 at 15:54 Comment(1)
I have created a solution and committed code in GitHub as well, feel free to try that. stackoverflow.com/a/67403898/4828650Demetriusdemeyer
C
0

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

Callisto answered 20/9, 2022 at 19:25 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewEnyo
S
0

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)
}
Sheehan answered 2/5 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.