Home » Android » android – RecyclerView item layout_weight, sizes not updating

android – RecyclerView item layout_weight, sizes not updating

Posted by: admin June 16, 2020 Leave a comment

Questions:

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]                |
+----------------------------------------+
How to&Answers:

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

Answer:

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>

Answer:

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

Answer:

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>

Answer:

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.

Answer:

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

Answer:

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

Answer:

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.

Answer:

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

Answer:

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.

Answer:

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>

Answer:

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/

Answer:

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

Answer:

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>