I'm inserting ImageSpans inside TextViews, which are part of a RecyclerView. Most of the time it works fine, but as you scroll up and down sometimes the TextView height is wrong for no obvious reason - there's too much vertical whitespace.
In this layout on a specific device, the correct height for a TextView with 1 line of text is 48, and the wrong height is 80 (I got these values from HierarchyViewer). The interesting thing is that the correct height for a TextView with 1 line of text including an ImageSpan is 80. So what seems to be happening is as the TextView gets recycled by RecyclerView, it sometimes keeps the height representing its old content (which included an ImageSpan).
This screenshot shows the "Anore" and "Halal blahs" TextViews as being 80 pixels high, which is wrong. The "Hello" TextView is correct at 48 pixels.
While investigating this I opened DDMS and ran "Dump UI hierarchy" and something interesting happened: the TextView heights corrected themselves on the fly:
That's pretty compelling evidence that the problem is TextView isn't being laid out properly after updating the text, so I tried various ways of calling forceLayout() and invalidate() on the TextView and its parent views, but it didn't help.
I tried replacing RecyclerView with ListView, no luck. I tried having all ImageSpans use the same drawable, no luck.
I ran HierarchyViewer but it didn't "fix" the layouts like UI Automator did. Not even when I pressed the "invalidate layout" and "request layout" buttons.
I'm not doing anything fancy to set the ImageSpans:
SpannableStringBuilder stringBuilder = new SpannableStringBuilder( rawText );
for( int i = inlineImages.size() - 1; i >= 0; i-- ) {
InlineImage img = inlineImages.get( i );
stringBuilder.insert( img.idx, "x" );
ImageSpan span = new ImageSpan( context, img.drawable );
stringBuilder.setSpan( span, img.idx, img.idx + 1, 0 );
}
holder.mText.setText( stringBuilder );
// none of this helps
holder.mText.forceLayout();
holder.mText.requestLayout();
holder.mText.invalidate();
Here's the relevant part of the layout for each list item:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/speech_bubble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/speech_bubble_right"
android:layout_marginLeft="40dp"
>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_toRightOf="@id/timestamp"
/>
</LinearLayout>
</RelativeLayout>
I tried invalidating and forcing a layout on the TextView parent (LinearLayout) but it did nothing.
I tried editing the layout down to literally just this:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
.. and the problem still happens. It's definitely the fault of TextView itself.