androidspannablespanned

Android check if Spanned objects are equal


How can I check if two Spanned objects are equal (they have the same content and spans applied)? I rather not implement the equals(Spanned span) method. :)


Solution

  • The span classes in Android lack equals and hashCode methods. I don't know why. Maybe it was just an oversight? There is also a bug in the SpannableStringBuilder.equals() method.

    The workaround is exactly what you feared. If you use, for example, AbsoluteSizeSpan you need to extend it and add equals and hashCode methods. Use your version instead of the framework's version when adding spans to the SpannableStringBuilder:

    import android.os.Parcel;
    
    
    public class AbsoluteSizeSpan extends android.text.style.AbsoluteSizeSpan {
    
    
        public AbsoluteSizeSpan(int size) {
            super(size);
        }
    
        public AbsoluteSizeSpan(int size, boolean dip) {
            super(size, dip);
        }
    
        public AbsoluteSizeSpan(Parcel src) {
            super(src);
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            AbsoluteSizeSpan that = (AbsoluteSizeSpan) o;
    
            if (getSize() != that.getSize()) return false;
            return getDip() == that.getDip();
        }
    
        @Override
        public int hashCode() {
            int result = getSize();
            result = 31 * result + (getDip() ? 1 : 0);
            return result;
        }
    }
    

    In the Android framework's SpannableStringBuilder.equals() method, a list of sorted spans in compared to a list of unsorted spans 🤦. To fix this, override SpannableStringBuilder.equals():

    public class SpannableStringBuilder extends android.text.SpannableStringBuilder {
    
        @Override
        public boolean equals(Object o) {
            if (o instanceof Spanned &&
                    toString().equals(o.toString())) {
                Spanned other = (Spanned) o;
                // Check span data
                Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
                Object[] spans = getSpans(0, length(), Object.class);
                if (spans.length == otherSpans.length) {
                    for (int i = 0; i < spans.length; ++i) {
                        Object thisSpan = spans[i];
                        Object otherSpan = otherSpans[i];
                        if (thisSpan == this) {
                            if (other != otherSpan ||
                                    getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
                                    getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
                                    getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
                                return false;
                            }
                        } else if (!thisSpan.equals(otherSpan) ||
                                getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
                                getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
                                getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }
    }
    

    Add your override versions of the span classes to your version of SpannableStringBuilder.