为什么对于某些 recyclerview 项目,textview ellipsize 在 recyclerview 滚动期间检查 returns true,即使它是 false?

Why for some recyclerview items, textview ellipsize check returns true during recyclerview scroll even if its false?

在我的 recyclerview 项中,我需要检查文本视图是否为椭圆形。基于这个条件,我需要做一些逻辑。这是我的项目布局 xml 代码:

<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="9.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>

布局如下所示:

现在,我想检查这个 textview activity_list_item_status_text 是否是椭圆形的,如果是椭圆形的,则在第三行显示 textview activity_list_item_status_spillover_text。如果没有,请隐藏它。我用 TreeObserver 来检查这个。这是 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);
           }
       }
 });

当我打开 recyclerview 时,这段代码工作正常。但是,当我滚动 recyclerview 时,对于 某些项目,即使文本未被省略并且 activity_list_item_status_spillover_text 变得可见 ,此条件 layout.getEllipsisCount(lines - 1) > 0 变为真。其类似的文本在滚动过程中变得椭圆形。我无法弄清楚为什么会这样。代码有问题吗?

编辑: 添加问题的屏幕录像: https://drive.google.com/file/d/1l-xUyBJzHy-7BScsbcJPpznhwVSN7Ah4/view?usp=sharing

我不知道为什么 getEllipsisCount() 在您的情况下不起作用(Android 充满魔力)但是,您可以不使用它,而是将 holder 中的文本与TextView 中显示的实际文本,然后根据结果执行您的逻辑。

这在 Recycler 视图中很常见,顾名思义,它会回收和重用视图,并且视图获得的 属性 不会失效。

对你不想回收的ViewHolder执行viewHolder.setIsRecyclable(false)

来自 ViewHolder#setIsRecyclable(boolean) 的文档:

通知回收商该物品是否可以回收。在

之前,不可回收的视图将不会被重新用于其他项目

setIsRecyclable()

稍后设置为 true。

这将导致只创建一个 ViewHolder

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

虽然在长列表的情况下制作这么多视图有点费力,但是这个问题还是会解决的。因为每个视图都有其单独的 viewholder

我得到了答案。我更改了查找 tetxview 是否为椭圆的逻辑。我将 textview 代码更改为:

    <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" />

从上面的代码可以看出,我在代码中加入了这两行:

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

并删除了 ellipsize="end"

然后我将检查省略号的逻辑更改为此代码:

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

这解决了我的问题。

这是因为 addOnDrawListener() 在我们滚动回收器视图时无法按预期工作。 onDraw()这里的lambda函数表示是异步的,它会反复调用自己,直到视图显示在屏幕上。

最初,函数会在使用 nullPointerException 向下滚动回收器视图时崩溃,如果 layout.getLines() 会调用外部 if(layout != null) 子句,因为 textViewHolder which负责创建布局只创建一次,之后它被绑定并一遍又一遍地重用。由于这个 textView.getLayout() 在滚动的确切时刻给出一个 null

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.

现在因为当我们向下滚动时布局为 null 这部分代码在滚动时从未真正起作用,它只有在视图绑定并显示后才起作用。

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

这就是为什么一旦向下或向上滚动回收器视图,它就会在某些地方产生异常。最好的方法是使用非生命周期相关的方式来定义文本视图是否为椭圆化。

你也可以参考这个document,最后说

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

我想你可以避免使用树观察器。

您可以在视图支架内测量文本宽度。

使用 textview 绘画和这些答案Measuring text width to be drawn on Canvas ( Android )

如果文本宽度超过文本视图宽度,这意味着文本视图将被椭圆化。