Android 7.0以降で URLSpan と ForegroundColorSpan の描画が乱れる件について

# 概要

Android 7.0以降で TextView に SpannableStringBuilder でフォーマットした文字列を設定する際に、URLSpan と ForegroundColorSpan が混在すると描画が乱れるようです。N Preview1 から発生していたと記憶しています。

![2017-01-26 04.59.22trims.png](https://qiita-image-store.s3.amazonaws.com/0/39221/5a5aa7ca-3f91-45c1-a913-7698d6313999.png)

上記の例では分かりやすく URLSpan の長さを1文字減らしてるんだけど、「＃２月限りの」だけ色がおかしくなってる。

ForegroundColorSpan が無視されている感じ。

というわけで検証した結果のメモを残しておきます。


# 検証1

ごくごくシンプルなTextViewで試してみたらこうなった。

![2017-01-26 05.18.29minis.png](https://qiita-image-store.s3.amazonaws.com/0/39221/aaced888-6daf-2710-5914-6479701126eb.png)

下記のコードの通り、なぜか交互に実行すると色がおかしくなる。
3つ目と4つ目は実行する順序を変えただけなんだけどおかしくなる部分が変わった。
この場合、「3組目」で本現象が発生するらしい。

```java:MainActivity.java
        final String text = ".@bh_srvさんの「【 #執事の館 】 #名古屋の仮住まい は #２月限りの品 をご用意して、お嬢様、旦那様のお帰りをお待ちしております！」が伸びてるみたい。みんなに届けぇ〜 https://t.co/d9NL21NW2J";

        final TextView textView1 = (TextView) findViewById(R.id.textView1);
        textView1.setText(text);

        String url1 = "https://twitter.com/search?q=%23%E5%9F%B7%E4%BA%8B%E3%81%AE%E9%A4%A8";
        String url2 = "https://twitter.com/search?q=%23%E5%90%8D%E5%8F%A4%E5%B1%8B%E3%81%AE%E4%BB%AE%E4%BD%8F%E3%81%BE%E3%81%84";

        // URLSpan, ForegroundColorSpan を順番に実行
        {
            final TextView textView = (TextView) findViewById(R.id.textView2);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1), 14, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url2), 22, 30, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 34, 40, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url2), 1, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 14, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 22, 31, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 34, 41, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 1, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }

        // URLSpan, ForegroundColorSpan を交互に実行
        {
            final TextView textView = (TextView) findViewById(R.id.textView3);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1), 14, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 14, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url2), 22, 30, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 22, 31, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 34, 40, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 34, 41, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url2), 1, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 1, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }

        // URLSpan, ForegroundColorSpan を交互に実行(最後の２組を入れ替えてみた)
        {
            final TextView textView = (TextView) findViewById(R.id.textView4);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1), 14, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 14, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url2), 22, 30, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 22, 31, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url2), 1, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 1, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 34, 40, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(0xff4187aa), 34, 41, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }
```

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="jp.takke.myspansample.MainActivity">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="24dp"
        />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView2"
        android:layout_marginTop="24dp"
        />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView3"
        android:layout_marginTop="24dp"
        />

</RelativeLayout>
```

# 検証2

もっとシンプルな文字列で試してみた。

![2017-01-26 05.46.25minis.png](https://qiita-image-store.s3.amazonaws.com/0/39221/e7e37c11-958b-b051-970d-aa7cdc10dbb7.png)

今度は URLSpan... ForegroundColorSpan... の順番に呼び出してもおかしくなった。

```java
        String text = "";
        for (int i=0; i<=9; i++) {
            for (int j=0; j<9; j++) {
                text += "" + i;
            }
            text += " ";
        }

        {
            final TextView textView = (TextView) findViewById(R.id.textView1);
            textView.setTextColor(0xffaaaaaa);
            textView.setLinkTextColor(0xffff0000);
            textView.setText(text);
        }

        String url1 = "https://twitter.com/search?q=%23%E5%9F%B7%E4%BA%8B%E3%81%AE%E9%A4%A8";
        final int color = 0xff2020ff;

        // URLSpan, ForegroundColorSpan を順番に実行
        {
            final TextView textView = (TextView) findViewById(R.id.textView2);
            textView.setTextColor(0xffaaaaaa);
            textView.setLinkTextColor(0xffff0000);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 51, 53, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 61, 63, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 71, 73, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new ForegroundColorSpan(color),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 51, 53, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 61, 63, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 71, 73, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }

        // URLSpan, ForegroundColorSpan を順番に実行
        {
            final TextView textView = (TextView) findViewById(R.id.textView3);
            textView.setTextColor(0xffaaaaaa);
            textView.setLinkTextColor(0xffff0000);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new URLSpan(url1), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new ForegroundColorSpan(color),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }

        // URLSpan, ForegroundColorSpan を交互に実行
        {
            final TextView textView = (TextView) findViewById(R.id.textView4);
            textView.setTextColor(0xffaaaaaa);
            textView.setLinkTextColor(0xffff0000);
            final SpannableStringBuilder ssb = new SpannableStringBuilder(text);

            ssb.setSpan(new URLSpan(url1),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color),  1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 11, 13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 21, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 31, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 51, 53, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 51, 53, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 61, 63, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 61, 63, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            ssb.setSpan(new URLSpan(url1), 71, 73, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ssb.setSpan(new ForegroundColorSpan(color), 71, 73, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ssb);
        }

```

# まとめ

Android 7.0 以降(少なくとも Android 7.1.1/Nexus 5Xまで) では、URLSpan と ForegroundColorSpan を重ねると ForegroundColorSpan が無視される場合があるっぽい。

まあそもそも URLSpan と ForegroundColorSpan を重ねるのがおかしいといえばおかしいんで、アプリ側でこれを避けるように組むしかないかな。この場合は ForegroundColorSpan で指定する色を TextView.setLinkTextColor に指定すればいいだけだし。