androidxmlkotlintextviewshared-element-transition

Multiline TextView Edge Clipped But Not During SharedElementTransition


I have an issue where my TextView has characters clipped on its right side only for certain pieces of text. To specify: I don't have a custom font or italic text; it's standard text. Here's an image, there should be an "i" where I have the circle, it's "nitrosoethane" not "ntrosoethane":

enter image description here

I have found multiple workarounds to fix the clipping, such as setting a text shadow or sub-classing TextView. They work, but they don't fix one particular issue. I have a SharedElementTransition with this TextView, and only when returning to the calling Activity, as part of the SharedElementReturnTransition animation, the TextView decides to wrap on a different character. Interestingly, this wrap is in the correct place to avoid any clipped text. Unfortunately, it ruins the animation effect, because the text jumps to its bugged-out position at the end. Here's a video, as you can see, the text above the line reads "nitrosoethane" for a split second before it switches to "trosoethane":

enter image description here

I have done a lot of investigation, but I'm really scratching my head on this one. I can neither fix the TextView so it wraps on the correct character or make the TextView in the animation portray the bug for animation continuity. I've measured the TextView before, during, and after the animation, and it's always the same width with the same text and the same font and the same size — same everything (except measuredWidth is larger on the SharedElementEnterTransition, but that shouldn't effect this, and regular width is the same anyway). I don't see why the word wrapping would change only on the return transition. Please help.

Here's the XML for the calling Activity's TextView:

<com.google.android.material.textview.MaterialTextView
    android:id="@+id/name"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="32dp"
    android:layout_marginEnd="8dp"
    android:elevation="4dp"
    android:ellipsize="end"
    android:textColor="@color/text_primary"
    android:textSize="18sp"
    app:layout_constraintEnd_toStartOf="@+id/image"
    app:layout_constraintStart_toStartOf="@+id/card"
    app:layout_constraintTop_toBottomOf="@+id/formula"
    tools:text="Magnesium Chloride" />

And here's the opening Activity:

<com.google.android.material.textview.MaterialTextView
    android:id="@+id/anim_name"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="32dp"
    android:layout_marginEnd="8dp"
    android:elevation="4dp"
    android:ellipsize="end"
    android:textColor="@color/text_primary"
    android:textSize="18sp"
    android:transitionName="@string/history_anim_name_tran"
    app:layout_constraintEnd_toStartOf="@+id/dummy_thumb"
    app:layout_constraintStart_toStartOf="@id/background"
    app:layout_constraintTop_toTopOf="@+id/name"
    tools:text="Magnesium Chloride" />

This issue doesn't happen for most text, just text like the one I've shown. Also, if you're wondering, I set the transitionName programmatically for the first TextView. In addition, these two TextViews are in RecyclerViews.


Solution

  • I believe I found the issue. It has to do with my TextView's width being dependent on the ImageView's width (which is not know on the first layout pass). For some reason, the TextView correctly changes its bounds after the ImageView width is updated when the image loads, but the TextView still sometimes incorrectly draws some text outside its bounds (which is usually clipped, but can be seen in my question due to a workaround). Calling invalidate() or requestLayout() doesn't do anything to fix it. But I found a workaround:

    If I set the TextView to fixed width, the text fits correctly. So as a workaround, I allow the ConstraintLayout to layout the TextView first, then I manually set the TextView width to itself.

    // Set "match constraints" to determine width
    textView.layoutParams.width = 0
    
    textView.text = "Some text"
    
    // Make the width a fixed number after OnLayout()
    textView.doOnLayout {
        textView.layoutParams.width = textView.width
    }
    

    This seems like a bug in ConstraintLayout, TextView, or their interaction. This fix works in most all cases, except mine, because the card itself is an itemView in my RecyclerView. I'm not sure why, but the solution doesn't work in my onBindViewHolder(). Instead, I simply set the ImageView to a fixed width and call it a day.