Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Horizontal RecyclerView with variable item heights not wrapping properly

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?

like image 919
AA_PV Avatar asked Jan 11 '17 07:01

AA_PV


2 Answers

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.

like image 170
AA_PV Avatar answered Nov 09 '22 10:11

AA_PV


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.

like image 26
Pradeep Kumar Kushwaha Avatar answered Nov 09 '22 12:11

Pradeep Kumar Kushwaha