Skip to content

TextView使用ClickableSpan崩溃问题

报错信息

android.view.InflateException: Binary XML file line #14: Error inflating class TextView
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:767)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:810)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:508)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:418)

bugly将它归类为 java.lang.ArrayIndexOutOfBoundsException

item的layout没有设置ellipsizemaxLine

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="12dp"
    android:layout_marginTop="2dp"
    android:layout_marginEnd="12dp"
    android:layout_marginBottom="4dp"
    android:background="@color/compPageBgColor"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <TextView
        android:id="@+id/en_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="start"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/cn_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="start"
        android:textSize="13sp" />

</LinearLayout>

在RecyclreView创建ViewHodler的时候就会崩溃。onBindViewHolder中,textview使用了ClickableSpanClickableSpan复写了updateDrawState方法。

int color = Color.RED;

    public CVH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new CVH(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull CVH holder, int position) {
        SpannableStringBuilder ssb = new SpannableStringBuilder("abcdefg");
        // ...
        ssb.setSpan(new ClickableSpan() {
                    @Override
                    public void onClick(@NonNull View widget) {
                        if (onItemClickListener != null) {
                            onItemClickListener.onClick(info);
                        }
                    }
                    @Override
                    public void updateDrawState(TextPaint ds) {
                        if (ds != null) {
                            ds.setColor(color);
                            ds.setUnderlineText(true);
                        }
                    }
                }, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        holder.contentEnTv.setMovementMethod(LinkMovementMethod.getInstance());
        holder.contentEnTv.setText(ssb);
    }

TextView换成androidx.appcompat.widget.AppCompatTextView,一样会崩溃

问题会出现在

  • 小米4c(Android5.1.1, API 22)
  • OPPO(Android 6.0.1, API 23)
  • 安卓平板(Android 7)

没有这个崩溃的机型,红米6A(Android8.1),1+5(Android 10)。

相关问题: https://stackoverflow.com/questions/46947850/textview-with-ellipsize-middle-casues-arrayindexoutofboundsexception-exception

Android系统(7及以下)的一个bug https://issuetracker.google.com/issues/36950033

绕过这个问题

Android7以上的系统不会出现此bug,我们尝试绕过这个问题。 textView设置span的代码需要修改。

新增一个抽象类ColorClickSpan。里面有一个颜色值。

    public abstract class ColorClickSpan extends ClickableSpan {
        private int color;

        public ColorClickSpan(int color) {
            this.color = color;
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            if (ds != null) {
                ds.setColor(color);
                ds.setUnderlineText(true);
            }
        }
    }

修改onBindViewHolder里的使用方式。

    ssb.setSpan(new ColorClickSpan(color) {
        @Override
        public void onClick(View widget) {
            // ...
        }
    }, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);

这样改,小米4c(5.1.1)和平板(7)可以正常使用了。

小结一下,最开始是为了解决ForegroundColorSpan失效的问题。想用ClickableSpan同时实现点击和显示颜色的功能。 却触发了低版本安卓系统TextView的bug。调整了实现方式后,绕开了这个低版本安卓系统崩溃问题。