Horizontal RecyclerView with variable item heights not wrapping properly
Asked Answered
P

3

13

What I intend to achieve

enter image description here

The item view should occupy the entire height of the item

enter image description here

It could be that the item height is lesser than the height of the tallest item in the recyclerview, in which case it should just stick to the top like in the screenshot above.

The bug I'm running into

enter image description here

As in the screenshot above, views are getting truncated.

What I've tried so far

Initially I went with wrap_content on the recyclerview, now that it is supported. It didn't work when none of the views visible on the screen at the time were the tallest. This makes sense in how the view hierarchy is laid out. How can the height of something which hasn't even been bound to any data yet be calculated if the height is dependent on that data?

Workaround time :S

Instead of trying a custom layoutmanager, I first went with what I felt needed to be done - laying out all item views at the beginning to figure out their height.

There's a progressbar and an animation playing in the upper part of the screen to catch the user's attention while all this happens with recyclerview visibility set to invisible. I use two things, one didn't suffice - I've attached an observer in the adapter's onViewAttached() call and I've used a scroll change listener as well. There's a LinearSnapHelper attached to the recycler view to snap to adjacent (next or previous, depending on the scroll direction) position on scroll.

In this setup,

  1. I'm going to each position in the recyclerview using layoutManager.smoothScrollToPosition()
  2. Getting the child view height using

            View currentChildView = binding.nextRv.getChildAt(layoutManager.findFirstCompletelyVisibleItemPosition());
            if (currentChildView != null) {
                currentChildHeight = currentChildView.getHeight();
            }
    

in scroll change listener on RecyclerView.SCROLL_STATE_IDLE or by passing the height to the view attached observer mentioned above in the adapter's onViewAttachedToWindow()

@Override
public void onViewAttachedToWindow(BindingViewHolder holder) {
    if (mObserver != null) {
        mObserver.onViewAttached(holder.binding.getRoot().getHeight());
    }
}
  1. Storing a maxHeight that changes to the max of maxHeight and new child's height.

As is evident, this is ugly. Plus it doesn't give me the current view's height - onAttached means it's only just attached, not measured and laid out. It is the recycled view, not the view bound to current data item. Which presents problems like the truncation of view illustrated above.


I've also tried wrap_content height on the recycler view and invalidating from recycler's parent till the recycler and the child on scroll coming to SCROLL_STATE_IDLE. Doesn't work.


I'm not sure how a custom layoutmanager can help here.

Can someone guide me in the right direction?

Pomcroy answered 11/1, 2017 at 7:44 Comment(3)
can you add your xml fileHeuser
Please find it herePomcroy
The recyclerview in question is the one with id "next_rv". Actually for the solution detailed above, initially I've set it to height match_parent and later reset the height to the maxHeight obtainedPomcroy
P
8

I could not accept @Pradeep Kumar Kushwaha's answer because against one solution, I do not want different font sizes in the list. Consistency is a key element in design. Second alternative he gave couldn't work because with ellipsize I would need to give a "more" button of some sort for user to read the entire content and my text view is already taking a click action. Putting more some place else would again not be good design.

Changing the design with the simple compromise of resizing the recyclerview when the tallest, truncated item comes into focus, it turns into the simple use case of notifyItemChanged(). Even for the attempt I made using the view attached observer and scroll state listener, notifyItemChanged could be used but that approach is just too hacky. This I can live with in both code and design. Here goes the code required.

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
        int position = ((LinearLayoutManager) binding.nextRv.getLayoutManager())
                .findFirstVisibleItemPosition();
        if (position != nextSnippetAdapter.getItemCount() - 1) {
            binding.nextRv.getAdapter().notifyItemRangeChanged(position, 2);
        } else {
            binding.nextRv.getAdapter().notifyItemChanged(position);
        }
    }
}

For my particular setup, calling for just these two elements works. It can further be optimized so as to call for single element at position + 1 in most cases, and checking and calling for the appropriate one in corner (literal) cases.

Pomcroy answered 12/1, 2017 at 9:1 Comment(5)
It's a design compromise since the things on screen will have to move to bring the entire item into focus (using binding.contentScrollView.fullScroll(ScrollView.FOCUS_DOWN);)Pomcroy
Actually its not that big a design compromise. If I don't scroll to scroll view bottom, things look fine. It's perceptible that the view has increased and overflowing the screen so a user can be expected to scroll themselves to view the entire thing.Pomcroy
Do you have a code sample for this? I'm confused about the context in which this method is used.Realistic
Hi, I used it to readjust the height of the horizontal recyclerview with wraps to content height (i.e. list item height) where the list item height is variablePomcroy
In my case it doesn't work. It refreshes 2 items in horizontal RV every time (some flickering appears), but heights of items don't change.Strickland
C
1

Inside your adapter where I can find two cards one on top and another on bottom

How I would have defined my layout is like this:

Cardview1

    LinearLayout1 --> orientation vertical

    cardview2 (Top card where text is written)

    Linearlayout2 (where I can see icons such as like etc)-->orientation horizontal

Now fix the height of Linearlayout2 by setting it to wrap content.

And the height of cardview2 should be 0dp and add weight = 1

Now inside cardview2 add a TextView1 to matchparent in height and width.

Better inside textview1 add ellipsize to end and add max lines

If you want to show all lines try to find autoresizetextview library it can be founded here --> AutoResizeTextView

Hope it helps.

Crisper answered 11/1, 2017 at 8:0 Comment(1)
Thanks for answering. Ellipsize is a workaround to it, it does not resolve my query. I know about the autoresizetextview :) but I'm not looking for variable font sizes on my list.Pomcroy
H
1

I think the recyclerview can be set to height wrap_content. And the items can be make like height to match_parent.

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layput_height="wrap_content"/>

Item as:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   // your coode

</androidx.constraintlayout.widget.ConstraintLayout>

I had little more requirement than the question. Even my problem solved in the way.

Remember I am using:

androidx.recyclerview:recyclerview:1.0.0-beta01

dependency for the project

Haversack answered 16/10, 2019 at 9:9 Comment(2)
haven't been on this code base in a long while, I hope someone who stumbles into the problem can tell better if this worked for themPomcroy
it worked for me, seems new androidx recyclerviews already handling the situationGunnar

© 2022 - 2024 — McMap. All rights reserved.