13

I have two text views side by side in my RecyclerView item. One of the views has layout_weight="1" to keep another view visible if the view is wide.

The problem is that the the view sizes are not updating when the views got recycled, so to textView id:name width is not correct when i scroll the list.

Should I somehow force the view to update the layout?

My RecyclerView item:

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

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        android:layout_weight="1"
        android:ellipsize="end"
        android:maxLines="1" />

    <LinearLayout
        android:id="@+id/code_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:background="@drawable/background_code"
        android:orientation="vertical">

        <TextView
            android:id="@+id/code"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:paddingBottom="1dp"
            android:paddingLeft="7dp"
            android:paddingRight="7dp"
            android:paddingTop="1dp"
            android:singleLine="true"/>

    </LinearLayout>

</LinearLayout>

Im using RecyclerView v. 23.4.0.

Update

1. I have tried to call my RecyclerView item layout root requestLayout at the end of the adapter onBindViewHolder().

This does not any have affect, my views are not updated.

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ...
    viewHolder.mLayoutRoot.requestLayout();
}

2. Maybe the problem is that the view is not yet completely drawn when I call requestLayout? So I tried to set GlobalLayoutListener and call requestLayout there. I think this helps little, but still if I have enough items, in my list not all views are updated. Owerall in my list I have around max 10 items.

ViewTreeObserver vto = viewHolder.mLayoutRoot.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        viewHolder.mLayoutRoot.requestLayout();
    }
});

3. I tried to use TableLayout with shrinkColumns property to approach similar layout, but the result is same. Name text view widht is not always correctly set. Also, it looks like requestLayout has no affect at all (or am I calling it in wrong place?)

4. (28/8/2016)

I stripped down my code to bare minimum to reproduce this.

I managed to fix this using setIsRecyclable(false), like dosssik suggests, but I think it is not very good solution in terms of performance?

Here is my full list item:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="150dp"
    android:layout_marginLeft="16dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        android:layout_weight="1"
        android:ellipsize="end"
        android:maxLines="1" />

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/background_stop_code_small"
        android:ellipsize="end"
        android:paddingBottom="1dp"
        android:paddingLeft="7dp"
        android:paddingRight="7dp"
        android:paddingTop="1dp"
        android:singleLine="true"
        android:textSize="11sp"/>

</LinearLayout>

My adapter code:

I also tried to call invalidate(), but can not see any affect.

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    Item item = getItem(position);
    NormalViewHolder viewHolder = (NormalViewHolder) holder;

    viewHolder.mName.setText(item.getName());
    //viewHolder.mName.invalidate(); // No help
    viewHolder.mCode.setText(item.getCode());
    //viewHolder.mCode.invalidate(); // No help
}

Expected behaviour:

+----------------------------------------+
|[Name][Code]                            |
+----------------------------------------+
|[Name qwertyuiopasdfghjklzxcvb...][Code]|
+----------------------------------------+

This is how works right now if I scroll list up/down. So the layout is not updated and code is not alignet right to name. Also name is not taking always enough space.

+----------------------------------------+
|[Name][Code]                            |
+----------------------------------------+
|[Name qwer...]                [Code]    |
+----------------------------------------+
|[Name qwertyuiopa][Code]                |
+----------------------------------------+
|[Name qwe]        [Code]                |
+----------------------------------------+
devha
  • 3,307
  • 4
  • 28
  • 52

14 Answers14

4

I share my solution which is not need to use

setIsRecyclable(false)

Use below custom TextView at your xml.

public class TextViewForRecyclerView extends android.support.v7.widget.AppCompatTextView {
    public TextViewForRecyclerView(Context context) {
        super(context);
    }

    public TextViewForRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TextViewForRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        super.setText(text, type);
        requestLayout();
    }
}
Komal12
  • 3,340
  • 4
  • 16
  • 25
하준영
  • 41
  • 1
  • 1
2
As it seems code is of constant length, you can use following UI deisgn;
<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="16dp"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:align_parentLeft="true"
        android:layout_toLeftOf="@+id/code"
        android:layout_marginRight="8dp"
        android:ellipsize="end"
        android:maxLines="1" />

        <TextView
            android:id="@+id/code"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/background_code"
            android:gravity="center"
            android:align_ParentRight="true"
            android:ellipsize="end"
            android:paddingBottom="1dp"
            android:paddingLeft="7dp"
            android:paddingRight="7dp"
            android:paddingTop="1dp"
            android:singleLine="true"/>

</RelativeLayout>
  • Actually code can be variable length, my original question is little misleading about this. – devha Aug 29 '16 at 12:54
2

This xml works for me without using setIsRecyclable(false);
In some android versions (like kitkat) the code: android:ellipsize="end" only works with android:singleLine="true", so add this to the first TextView "@+id/name" and it will works!

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_marginLeft="16dp"
android:orientation="horizontal">
   <TextView
    android:id="@+id/name"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginRight="8dp"
    android:layout_weight="1"
    android:ellipsize="end"
    android:singleLine="true"
    android:maxLines="1"
    />
   <TextView
    android:id="@+id/code"
    android:layout_weight="0"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/background_stop_code_small"
    android:ellipsize="end"
    android:paddingBottom="1dp"
    android:paddingLeft="7dp"
    android:paddingRight="7dp"
    android:paddingTop="1dp"
    android:singleLine="true"
    android:textSize="11sp"/>
</LinearLayout>


The result:

enter image description here

Vinicius DSL
  • 1,839
  • 1
  • 18
  • 26
  • Unfortunately, this doesn't work for me. My problem is that I have 2 textviews next to each other (on left) and the first one has background. The background is not rendering properly when recycling. – Mike Keskinov Jul 27 '20 at 22:36
  • Are you using those textviews inside a ConstraintLayout? – Vinicius DSL Jul 30 '20 at 14:20
  • I overcome the problem by changing backgroundTint instead of background. (I have 2 different XML shapes only different by color). Regarding your question, I had tried Linear and Constrain layout with the same result. Now I'm using Constrain Layout. – Mike Keskinov Jul 30 '20 at 22:58
1

The RecyclerView will reuse ViewHolder objects so the layouts of those will be inflated once and then reused through the lifetime of the list. Try calling requestLayout on your root LinearLayout.

Usage of forceLayout(), requestLayout() and invalidate()

Community
  • 1
  • 1
CodyEngel
  • 1,501
  • 14
  • 22
  • 1
    I tried to add viewHolder.mLayoutRoot.requestLayout() call to end of my adapter onBindViewHolder, but still my layout is not updating correctly. LinearLayout code_container widht is not updating if the text name gets changed. – devha Aug 19 '16 at 07:29
1

You need to invalidate the two TextView views, and make sure you are doing it after you set the new text value to them:

@Override
public void onBindViewHolder(ItemHolder holder, int position)
{
    ...

    holder.nameTextView.invalidate();
    holder.codeTextView.invalidate();
}

As an alternative, if you don't mind the code view aligning to the right, you could replace the LinearLayout for a RelativeLayout and set the proper constraints between the name view and the code container:

<TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginRight="8dp"
    android:layout_centerVertical="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentLeft="true"
    android:layout_toLeftOf="@+id/code_container"
    android:layout_toStartOf="@+id/code_container"
    android:ellipsize="end"
    android:maxLines="1" />

<LinearLayout
    android:id="@+id/code_container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:background="@drawable/background_code"
    android:orientation="vertical">

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:paddingBottom="1dp"
        android:paddingLeft="7dp"
        android:paddingRight="7dp"
        android:paddingTop="1dp"
        android:singleLine="true"/>
</LinearLayout>

N.T.
  • 2,601
  • 1
  • 14
  • 20
1

I had very simillar problem with recyclerView. Sometimes it works not properly, when items recycling, unfortunatly.

for me helped turning off recyclable.

        setIsRecyclable(false);

you can do it in ViewHolder's constuctor like this:

class Holder extends RecyclerView.ViewHolder {

    public Holder(View itemView) {
        super(itemView);
        setIsRecyclable(false);

    }
}

Sure, that not awesome solution, but it's the only solution, that helped me.

Hope it will help you.

dosssik
  • 79
  • 1
  • 8
  • 2
    Thanks! So far this is only solution what works, but i hope there is some better way to refresh just the views I need? – devha Aug 28 '16 at 19:50
  • Unfortunately, this is only one working solution. In my case, the background of the first text view is looks cut otherwise (rounded corners background) – Mike Keskinov Jul 27 '20 at 22:43
1

I finally got the right answer. As my test, you can just add two lines in you method onBindViewHolder, as below:

@Override
public void onBindViewHolder(ItemHolder holder, int position)
{
    holder.mName.setSingleLine(false);    //first line
    holder.mName.setText("name");
    holder.mCode.setText("");
    holder.mName.setSingleLine(true);    //second line
}

Hope it help you.

Nick
  • 1,614
  • 1
  • 12
  • 10
1

you should add weight to inner linear layout also then it will be

<TextView
    android:id="@+id/name"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginRight="8dp"
    android:layout_weight="2"
    android:ellipsize="end"
    android:maxLines="1" />

<LinearLayout
    android:id="@+id/code_container"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:background="@drawable/background_code"
    android:orientation="vertical">

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:paddingBottom="1dp"
        android:paddingLeft="7dp"
        android:paddingRight="7dp"
        android:paddingTop="1dp"
        android:singleLine="true"/>

</LinearLayout>

try this will work for you

Uma Achanta
  • 3,669
  • 4
  • 22
  • 49
0

I have had layout refresh problems in a chat-like Activity using RecyclerView: I've called a method from onBindViewHolder to adapt any chat message's margins, below a code snippet: .. RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) this.mItemView.getLayoutParams(); lp.setMargins(0,0,50,0);

MBDevelop
  • 103
  • 1
  • 11
0

try this i am not use editor but it will auto-correct if any problem with xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" //i changed this
android:layout_height="150dp"
android:layout_marginLeft="16dp"
android:weightsum="1"//added this
android:orientation="horizontal">

<TextView
    android:id="@+id/name"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginRight="8dp"
    android:layout_weight="1"
    android:ellipsize="end"
    android:singleLine="true" />//added this

<TextView
    android:id="@+id/code"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/background_stop_code_small"
    android:ellipsize="end"
    android:paddingBottom="1dp"
    android:paddingLeft="7dp"
    android:paddingRight="7dp"
    android:paddingTop="1dp"
    android:singleLine="true"
    android:textSize="11sp"/>

</LinearLayout>

the above will work

if you still insist of updating layout, always know if the update you need is measurement then you need to call View.invalidate() along with View.requestLayout()

Elltz
  • 10,730
  • 4
  • 31
  • 59
  • I can not set root linearLayout width to match_parent, as then my code view is always on the right of the screen. Code needs to be after the name view. – devha Aug 28 '16 at 21:27
0

Try using this, I had same issue, i resolved it using this :

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View rootView = LayoutInflater.from(context).inflate(R.layout.review_data, null, false);
        RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        rootView.setLayoutParams(lp);
        return new ViewHolder(rootView);
    }

Set layout params in onCreateViewHolder like above.

SANAT
  • 8,489
  • 55
  • 66
0

setting weight on just one of them is wrong, because when the code is long the name will get disappeared. I don't know what are you trying to achieve, but even with a simple ListView and a simple layout like below for its items they will never get disappeared and I think they look better:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:gravity="left"
android:layout_marginLeft="16dp"
android:orientation="horizontal">

<TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight=".5"
    android:layout_marginRight="8dp"
    android:ellipsize="end"
    android:text="dfsdd45te5sf"
    android:maxLines="1" />

<TextView
    android:id="@+id/code"
    android:layout_weight=".5"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_green_dark"
    android:ellipsize="end"
    android:paddingBottom="1dp"
    android:paddingLeft="7dp"
    android:paddingRight="7dp"
    android:paddingTop="1dp"
    android:layout_marginRight="1dp"
    android:text="ertertete5tte5tte5e5te5t"
    android:singleLine="true"
    android:textSize="12sp"/>
</LinearLayout>
M D P
  • 4,525
  • 2
  • 23
  • 35
0

I think you are expecting Out put simile like this enter image description here

in the top container LinearLayout change "wrap_content" to "match_parent"

check this tutorial http://wiki.workassis.com/android-recyclerview-example/

Bikesh M
  • 8,163
  • 6
  • 40
  • 52
-1

Well, I am not getting exact problem what you are facing...but one problem i can see in your code is weight, you are using weight in a wrong way. weight will not effective if you use in one widget, at-least you have to use two widget to give proper weight between them, and use weight sum in their parent to work properly. for example see below your modified code:-

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="16dp"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:weightSum="3">

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        android:layout_weight="1"
        android:ellipsize="end"
        android:text="qwertyuioopasdfsd" />

    <LinearLayout
        android:id="@+id/code_container"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="2"
        android:background="@drawable/background_code"
        android:orientation="vertical">

        <TextView
            android:id="@+id/code"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:paddingBottom="1dp"
            android:paddingLeft="7dp"
            android:paddingRight="7dp"
            android:paddingTop="1dp" />

    </LinearLayout>

</LinearLayout>