7

I've dealt with RecyclerView a few times, but this time I made the height/width of items of the RecyclerView to wrap_content. What happened with this implementation is that whenever I scroll the RecyclerView, the layout and values of the items in it get messed up as well. E.g. the layout was too small to fit the values of the item, hence the values getting truncated, when I scroll up and down on the RecyclerView.

Recyclerview hiccup!

I understand that scrolling a RecyclerView will inevitably recycle the views, reusing the first displayed layout when the activity is created. And please do correct me if I'm wrong about this logic.

I hope to get some solutions or workarounds to this problem because I would really love to have the RecyclerView item's layout to nicely conform the value. Just like the Messenger app where the card view is nicely sized to its content. Can I achieve this without fixing the sizes of the view?

Thanks a lot!

Edit: I'll post the code here, if this might give you a better overview of my problem.

RecyclerView Adapter

public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ViewHolder> {

private static final String TAG = ChatAdapter.class.getSimpleName();

private Context context;
ArrayList<Conversation> conversations;
Conversation conversation;
private static final int TYPE_SENDER = 1;
private static final int TYPE_RECIPIENT = 2;

// Pass in the context and users array into the constructor
public ChatAdapter(Context context, ArrayList<Conversation> conversations) {
    super();
    this.context = context;
    this.conversations = conversations;
}

@Override
public int getItemViewType(int position) {
    conversation = conversations.get(position);
    if (conversation.getIsSender().equals("1")) {
        return TYPE_SENDER;
    } else {
        return TYPE_RECIPIENT;
    }
}

// Create a new ViewHolder to contain our text and image
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
    ViewHolder viewHolder;
    LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());

    switch (position) {
        case TYPE_SENDER:
            View v1 = inflater.inflate(R.layout.chat_item_sender, viewGroup, false);
            viewHolder = new ViewHolder(v1);
            break;
        case TYPE_RECIPIENT:
            View v2 = inflater.inflate(R.layout.chat_item_recipient, viewGroup, false);
            viewHolder = new ViewHolder(v2);
            break;
        default:
            View v = inflater.inflate(R.layout.chat_item_sender, viewGroup, false);
            viewHolder = new ViewHolder(v);
    }
    return viewHolder;
}

// Display our text and image at the specified position in the list
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
    conversation = conversations.get(position);

    // Feed the message, duration, must put in IF statement or data recycled will be buggy!
    if (!conversation.getMessage().isEmpty()) {
        viewHolder.message.setText(conversation.getMessage());
    } else {
        viewHolder.message.setTypeface(null, Typeface.ITALIC);
        viewHolder.message.setText("Message not retrievable at the moment!");
    }

    if (!conversation.getDate().toString().isEmpty()) {
        viewHolder.duration.setText(DateUtils.getRelativeDateTimeString(context,
                conversation.getDate().getTime(),
                DateUtils.SECOND_IN_MILLIS,
                DateUtils.DAY_IN_MILLIS, 0));
    } else {
        viewHolder.duration.setVisibility(View.GONE);
    }
}

// Get the number of items in the adapter
@Override
public int getItemCount() {
    return conversations.size();
}

public class ViewHolder extends RecyclerView.ViewHolder {

    public TextView message, duration;

    public ViewHolder(View itemView) {
        super(itemView);

        message = (TextView) itemView.findViewById(R.id.chat_message);
        duration = (TextView) itemView.findViewById(R.id.chat_duration);
    }

}
}

chat_item_sender.xml

Note: Same as chat_item_recipient.xml, which are the items for the RecyclerView; chat_item_sender.xml is the orange view on the right

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="1">

    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_marginRight="20dp"
        android:layout_weight="5"
        android:gravity="right|center_vertical">

        <android.support.v7.widget.CardView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            card_view:cardBackgroundColor="@color/primary"
            card_view:cardCornerRadius="10dp"
            card_view:cardPreventCornerOverlap="false"
            card_view:contentPadding="10dp">

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/chat_message"
                    android:layout_width="fill_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:textColor="@color/white"
                    android:text="@string/shorter_test" />

                <TextView
                    android:id="@+id/chat_duration"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:gravity="bottom|right"
                    android:text="Duration"
                    android:textColor="@color/secondary_white_text"
                    android:textSize="12sp" />

            </LinearLayout>

        </android.support.v7.widget.CardView>

    </LinearLayout>

</LinearLayout>

  • If it's a vertically Scrolling RecyclerView, why wouldn't you have the width of items set to match_parent? – Doug Stevenson Feb 13 '16 at 04:34
  • @DougStevenson Wouldn't that make the view use up all the horizotal space in a layout and leave plenty of empty spaces if the value is short? – Reynolds R. G. G. Feb 13 '16 at 04:52
  • Post your adapter code, and maybe a screenshot of the trouble you are having. – rothloup Feb 13 '16 at 05:16
  • 1
    I see. Without seeing your code, all I can suggest is calling `View.requestLayout()` in your adapter's `getView()` method.. It may be that the `wrap_content` flag is only applied during layout, and if the recycled view has no reason to lay itself out again (who knows if getting recycled triggers that or not), then it just stays at its current size. – rothloup Feb 13 '16 at 05:40
  • Please post the layout for your item views being created in the adapter for the RecyclerView. Also post the adapter. – Doug Stevenson Feb 13 '16 at 05:49
  • @rothloup Thanks for the suggestion. It's hard to find related problem to properly troubleshoot this. I've tried your suggestion, but it's still the same occurrence. – Reynolds R. G. G. Feb 13 '16 at 06:53
  • @DougStevenson Ok, posting it here. I didn't put it on because I thought there will be people encountering such a problem where RecyclerView keeps recycling items not in the way they wanted - that there will be a nice solution to this. – Reynolds R. G. G. Feb 13 '16 at 06:55
  • maybe this link could help https://github.com/madhur/android-chat-starter – Gujarat Santana Feb 13 '16 at 09:46
  • 1
    Your view's layout is confusing to me. You have a vertical linear layout that contains a single horizontal linear layout. The outer layout is not helping anything. Then that horizontal linear layout contains an empty linear layout - what does that do? Then there's another linear layout in there that contains only one CardView child. All this seems excessive. A linear layout is intended to contain multiple children. You might want to look into simplifying this and make it work well before it gets put into a recycler view. – Doug Stevenson Feb 13 '16 at 16:59
  • @DougStevenson It's a nested `LinearLayout` intended to carefully position the views. You're right about the first vertical `LinearLayout`, I've must omitted it when I was playing around with the layouts. The empty `LinearLayout` (in the horizontal) is intended to leave a small space, to limit the expansion of the next `LinearLayout` from the edge of the screen. I used `layout_weight` here in hope that it'll look uniform across all devices. I didn't remove the `LinearLayout` that's holding the `CardView` because the `CardView` can not get bigger than `LinearLayout` when the value gets bigger. – Reynolds R. G. G. Feb 15 '16 at 03:08
  • 1
    this was useful for me , may help you : http://stackoverflow.com/questions/30082000/recyclerview-messed-up-data-when-scrolling#answer-33836232 – Saeid Aug 06 '16 at 11:31

3 Answers3

4

Take the view that is causing the problem, for your case its the TextView that contains text of a chat. Make requestLayout method call of the view on binding.

viewHolder.message.requestLayout();

But remember to call this on your textView not on the layout with wrap_content above it!

Steve Moretz
  • 2,758
  • 1
  • 17
  • 31
Mussa
  • 1,463
  • 21
  • 25
  • 1
    Damn right!Spending the whole the changing everything I thought it's a constraintLayout problem so changed it to relative and ... But remember to call this on your textView not on the layout with wrap_content above it! – Steve Moretz Feb 08 '19 at 11:34
0

The problem lies in nesting the textview with parent views. While the recycler-view does its thing to optimize memory usage during binding it basically pulls out views from the pool that haven't changed and re-uses it. Since the parent views aren't being changed and just the textview is changed, it uses one of the views from the pool with the wrong width.

So the solution is don't wrap the textviews in parent views.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Tejas
  • 437
  • 5
  • 12
0

Don't use android:layout_weight in your layout file, manage your layout without weight. Problem will be fixed.

Divy Soni
  • 824
  • 9
  • 22