In my recyclerview item, i need to check whether a textview is ellipsized or not. Based on this condition, I need to do some logic. This is my item layout xml code:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_list_item_root_view"
    style="@style/ClickableRectanglePrimary"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingHorizontal="16dp"
    android:paddingTop="13.5dp"
    android:paddingBottom="14.5dp">

    <ImageView
        android:id="@+id/activity_list_item_contact_image"
        android:layout_width="@dimen/ui_size_xl_2"
        android:layout_height="@dimen/ui_size_xl_2"
        tools:ignore="ContentDescription"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/activity_list_item_name"
        style="@style/activity_list_item_name_v2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text="ABC India Pvt. Ltd."
        tools:ignore="HardcodedText"
        app:layout_constraintStart_toEndOf="@+id/activity_list_item_contact_image"
        app:layout_constraintEnd_toStartOf="@+id/activity_list_item_amount"
        app:layout_constraintTop_toTopOf="@+id/activity_list_item_contact_image"
        android:layout_marginStart="12dp"
        android:layout_marginEnd="4dp"/>

    <TextView
        android:id="@+id/activity_list_item_date"
        style="@style/activity_list_item_details"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text="Yesterday"
        tools:ignore="HardcodedText"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_name"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_contact_image"
        android:layout_marginStart="12dp"
        android:layout_marginTop="4dp"/>

    <TextView
        android:id="@+id/activity_list_item_dot_text"
        style="@style/activity_list_item_details"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text=" \u00b7 "
        tools:ignore="HardcodedText"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_date"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_name"
        android:layout_marginTop="4dp"/>

    <ImageView
        android:id="@+id/activity_list_item_status_icon"
        android:layout_width="@dimen/ui_size_xs"
        android:layout_height="@dimen/ui_size_xs"
        android:layout_marginEnd="6dp"
        android:scaleType="fitXY"
        tools:ignore="ContentDescription"
        app:srcCompat="@drawable/ui_purchase_protection_alt"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_dot_text"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_name"
        android:layout_marginTop="4dp"/>

    <TextView
        android:id="@+id/activity_list_item_status_text"
        style="@style/activity_list_item_details"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text="Request Received"
        tools:ignore="HardcodedText"
        android:ellipsize="end"
        android:maxLines="1"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_status_icon"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_name"
        app:layout_constraintEnd_toStartOf="@id/activity_list_item_amount"
        android:layout_marginEnd="4dp"
        android:layout_marginTop="4dp"/>

    <TextView
        android:id="@+id/activity_list_item_status_spillover_text"
        style="@style/activity_list_item_details"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text="Request Received"
        tools:ignore="HardcodedText"
        android:visibility="gone"
        tools:visibility="visible"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_date"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_contact_image"
        android:layout_marginStart="12dp"
        android:layout_marginTop="4dp"/>
 
    <TextView
        android:id="@+id/activity_list_item_amount"
        style="@style/UiTextView.Xl.Regular"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center|end"
        android:text="$149.99"
        android:textColor="?attr/ui_v2_color_grey_600"
        android:textSize="18sp"
        tools:ignore="HardcodedText"
        app:layout_constraintTop_toTopOf="@id/activity_list_item_contact_image"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

The layout looks like this: enter image description here

Now, I want to check whether this textview activity_list_item_status_text is ellipsized or not, and if it is ellipsized, show the textview in the third line activity_list_item_status_spillover_text. If not, hide it. I used TreeObserver to check this. Here's the code inside the ViewHolder:

TextView statusTextView = binding.activityListItemStatusText;
statusTextView.getViewTreeObserver().addOnDrawListener(() -> {
      Layout layout = statusTextView.getLayout();
      if (layout != null) {
           int lines = layout.getLineCount();
           if (layout.getEllipsisCount(lines - 1) > 0) {
                binding.activityListItemStatusSpilloverText.setVisibility(View.VISIBLE);
           }else {
                binding.activityListItemStatusSpilloverText.setVisibility(View.GONE);
           }
       }
 });

This code works fine when I open the recyclerview. But, when I scroll the recyclerview, for some items, this condition layout.getEllipsisCount(lines - 1) > 0 becomes true even though the text is not ellipsized and the activity_list_item_status_spillover_text is getting visible. Its like text is getting ellipsized during scroll. I am not able to figure out why this is happening. Is there any issue with the code?

EDIT: Adding the screen recording of the problem: https://drive.google.com/file/d/1l-xUyBJzHy-7BScsbcJPpznhwVSN7Ah4/view?usp=sharing

5

There are 5 best solutions below

0
On BEST ANSWER

I got the answer. I changed the logic of finding whether the tetxview is ellipsized or not. I changed the textview code to this:

    <TextView
        android:id="@+id/activity_list_item_status_text"
        style="@style/activity_list_item_details"
        android:layout_width="wrap_content"
        app:layout_constrainedWidth="true"
        app:layout_constraintHorizontal_bias="0"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|start"
        android:text="Request Received"
        tools:ignore="HardcodedText"
        app:layout_constraintStart_toEndOf="@id/activity_list_item_status_icon"
        app:layout_constraintTop_toBottomOf="@id/activity_list_item_name"
        app:layout_constraintEnd_toStartOf="@id/activity_list_item_amount"
        android:layout_marginEnd="4dp"
        android:layout_marginTop="4dp"
        android:maxLines="1" />

As you can see from the above code, I added these two lines to the code:

    app:layout_constrainedWidth="true"
    app:layout_constraintHorizontal_bias="0"

and removed ellipsize="end".

Then I changed the logic to check for ellipsis to this code:

if (statusTextView.getLineCount() > statusTextView.getMaxLines()) {
     //Ellipsized
} else {
     //Not ellipsized
}

This solved my issue.

3
On

This is quiet common in Recycler view As it's name suggest it recycle and reuses the views and the property attained by the view are not invalidated.

Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.

From docs of ViewHolder#setIsRecyclable(boolean):

Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until

setIsRecyclable()

is later set to true.

This will cause only one ViewHolder to be created.

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ...
    @Override
    public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
        if (holder instanceof VideoViewHolder) {
            holder.setIsRecyclable(false);
        }
        super.onViewAttachedToWindow(holder);
    }

    @Override
    public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
        if (holder instanceof VideoViewHolder){
            holder.setIsRecyclable(true);
        }
        super.onViewDetachedFromWindow(holder);
    }
    ...
}

although it would be bit taxing to make so many views in cases of long list but nevertheless this problem would be solved . as each view would have its seperate viewholder

1
On

I guess you can avoid using tree observer.

Instead inside view holder you can measure text width.

Using textview paint and these answers Measuring text width to be drawn on Canvas ( Android )

If text width exceeds textview width, this means textview will be ellipsized.

6
On

I don't know why getEllipsisCount() is not working in your case (Android is full of magic) but, instead of using it, you could compare the text in the holder against the actual text shown in the TextView and then, based on the result, execute your logic.

0
On

It's because addOnDrawListener() will not work as intended while we scroll recycler view. onDraw() which is denoted by the lambda function here is asynchronous by nature, it repeatedly calls itself until the view is displayed on the screen.

Initially, function would have crashed on Scrolling down the recycler View with a nullPointerException, if layout.getLines() would have called outside if(layout != null) clause because textViewHolder which is responsible for creating a layout is created only once, after that it's binded and reused over and over. Due to this textView.getLayout() give a null at the exact moment of scrolling

basically we are trying to call textLayout between the moments when one view is destroyed and another is about to be binded. this makes layout variable null for a brief period of time.

Now because layout is null when we scroll down this part of code never really works at the moment of scrolling, it only works after view is binded and already displayed.

if (layout != null) {
           int lines = layout.getLineCount();
           if (layout.getEllipsisCount(lines - 1) > 0) {
                binding.activityListItemStatusSpilloverText.setVisibility(View.VISIBLE);
           }else {
                binding.activityListItemStatusSpilloverText.setVisibility(View.GONE);
           }
       }

that's why it generates anomalies at certain places once scroll down or up the recycler View. The best way would be to use a non-lifecycle-dependent way of defining if the textview is ellispize or not.

you can also reffer this document, which in the end says

Callback method to be invoked when the view tree is about to be drawn. At this point, views cannot be modified in any way.