Cheok Yan Cheng Cheok Yan Cheng - 3 months ago 28
Android Question

Enlarge single character in SpannableString will have affect on line spacing (In Marshmallow only)

I have the following method, which enlarge the first character in


private void makeFirstLetterBig(TextView textView, String title) {
final SpannableString spannableString = new SpannableString(title);
int position = 0;
spannableString.setSpan(new RelativeSizeSpan(2.0f), position, position + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString, BufferType.SPANNABLE);

Here's the
being used.

android:text="TextView" />

Here's is the outcome before Marshmallow 6.

enter image description here

It looks pretty fine, as when the text content wraps to the 2nd line, the large first character doesn't affect 2nd line.

However, when running the same app in Marshmallow 6, I get the following outcome.

enter image description here

It seems that, the big character "I" is creating a large padding for the entire text content. This causes 2nd line (with Amazon) has a huge line spacing with 1st line.

May I know, how can I avoid such problem in marshmallow 6? I wish to have the same outcome as pre-marshmallow's.

p/s I filed a report at too.


On 9 December 2015, Google has fixed this issue and it will be released in Android 6.0.1 -


TL;DR The issue is caused by a bug in the method generate of StaticLayout.

The bug is a little different from what I thought initially, it doesn't affect all the rows but only those after the enlarged character, as you can see from the screenshot:

enter image description here

In Marshmallow a FontMetrics cache was added to avoid recomputing, as explained by a comment in the source code:

// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
// second time, so we cache those and then use those stored values

Unfortunately the fm.descent value available in the cache is discarded if it is lower than the previous cached value, as you can see in the snippet:

for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
    // retrieve end of span
    spanEnd = spanEndCache[spanEndCacheIndex++];

    // retrieve cached metrics, order matches above = fmCache[fmCacheIndex * 4 + 0];
    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
    fm.descent = fmCache[fmCacheIndex * 4 + 3];

    if ( < fmTop) {
        fmTop =;
    if (fm.ascent < fmAscent) {
        fmAscent = fm.ascent;
    if (fm.descent > fmDescent) {
        fmDescent = fm.descent;
    if (fm.bottom > fmBottom) {
        fmBottom = fm.bottom;


Once the value reach the max, it will never decrease and this is why every following line has an increased line space.