LoginSignup
5
2

More than 5 years have passed since last update.

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

Posted at

概要

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

2017-01-26 04.59.22trims.png

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

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

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

検証1

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

2017-01-26 05.18.29minis.png

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

MainActivity.java
        final String text = ".@bh_srvさんの「【 #執事の館 】 #名古屋の仮住まい は #2月限りの品 をご用意して、お嬢様、旦那様のお帰りをお待ちしております!」が伸びてるみたい。みんなに届けぇ〜 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 を交互に実行(最後の2組を入れ替えてみた)
        {
            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);
        }
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

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

        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 に指定すればいいだけだし。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2