Android —— SpannableStringBuilder 源码

package com.editor.text;

import android.text.*;
import android.util.*;
import com.editor.text.base.*;
import java.lang.reflect.*;
import java.util.*;


/** 这是内容和标记都可以更改的文本类 */
public class SpannableStringBuilderTemplete implements CharSequence, GetChars, Spannable, Editable, Appendable 
{
    
    private final static String TAG = "SpannableStringBuilderTemplete";
    
    /** 创建一个包含空内容的新SpannableStringBuilder */
    public SpannableStringBuilderTemplete() {
        this("");
    }
    /** 创建一个包含指定文本的新SpannableStringBuilder,包括其范围(如果有) */
    public SpannableStringBuilderTemplete(CharSequence text) {
        this(text, 0, text.length());
    }
    /** 创建一个新的SpannableStringBuilder,其中包含指定文本的指定部分,包括其范围(如果有) */
    public SpannableStringBuilderTemplete(CharSequence text, int start, int end) 
    {
        int srclen = end - start;
        if (srclen < 0) throw new StringIndexOutOfBoundsException();
        //创建一个更大的文本数组来拷贝指定范围内的文本,将多余的空间作为间隙
        mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
        mGapStart = srclen;
        mGapLength = mText.length - srclen;
        TextUtils.getChars(text, start, end, mText, 0);
  
        mSpanCount = 0;
        mSpanInsertCount = 0;
        mSpans = EmptyArray.OBJECT;
        mSpanStarts = EmptyArray.INT;
        mSpanEnds = EmptyArray.INT;
        mSpanFlags = EmptyArray.INT;
        mSpanMax = EmptyArray.INT;
        mSpanOrder = EmptyArray.INT;
        
        if (text instanceof Spanned) 
        {
            //如果增加的文本是Spanned,需要获取范围内全部的span并附加到自身
            Spanned sp = (Spanned) text;
            Object[] spans = sp.getSpans(start, end, Object.class);
            for (int i = 0; i < spans.length; i++) 
            {
                if (spans[i] instanceof NoCopySpan) {
                    continue;
                }
                
                //将span在原字符串中较start的偏移量获取,并偏移到自身中的位置
                int st = sp.getSpanStart(spans[i]) - start;
                int en = sp.getSpanEnd(spans[i]) - start;
                int fl = sp.getSpanFlags(spans[i]);
               
                //范围不可超过自己的大小
                if (st < 0)
                    st = 0;
                if (st > end - start)
                    st = end - start;
                if (en < 0)
                    en = 0;
                if (en > end - start)
                    en = end - start;
                setSpan(false, spans[i], st, en, fl, false);
            }
            //设置完span后一并刷新
            restoreInvariants();
        }
    }
    public static SpannableStringBuilderTemplete valueOf(CharSequence source)
    {
        if (source instanceof SpannableStringBuilderTemplete) {
            return (SpannableStringBuilderTemplete) source;
        } else {
            return new SpannableStringBuilderTemplete(source);
        }
    }
    
    //编辑器通常会在连续的光标位置插入字符,因此为了提升文本插入效率,使用Gap Buffer(间隙缓冲区)
    //间隙缓冲区使用GapStart和GapLenght表示文本数组中空闲的间隙位置,当有文本插入间隙时,不用将整个数组扩展,而是将指针偏移缩小间隙缓冲区
    //为了达到这种效果,每次插入新的字符,就将间隙缓冲区移到光标位置,因此若之后也在连续位置插入字符,可以大大提升效率
    //间隙缓冲区中的内容总是无效的,它不被计入总文本之中(总文本实际上是处于间隙缓冲区之前和之后的文本),若间隙缓冲区长度不足,需要进行扩展

    /**返回文本中指定偏移量处的字符*/
    public char charAt(int where) 
    {
        int len = length();
        if (where < 0) {
            throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
        } else if (where >= len) {
            throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
        }
        //在间隙缓冲区之后的字符的真实位置总是加上一个间隙缓冲区长度
        if (where >= mGapStart)
            return mText[where + mGapLength];
        else
            return mText[where];
    }

    /**文本长度总是数组长度减去间隙缓冲区长度*/
    public int length() {
        return mText.length - mGapLength;
    }
    
    /*如果此偏移量在间隙缓冲区之后,那么它的原本位置应减去间隙缓冲区长度*/
    private int resolveGap(int i) {
        return i > mGapStart ? i - mGapLength : i;
    }
    
    /*修改数组的长度的同时修改间隙缓冲区的大小*/
    private void resizeFor(int size) 
    {
        final int oldLength = mText.length;
        if (size + 1 <= oldLength) {
            //size小于或等于原大小
            return;
        }

        //创建一个比size更大的数组,并将原数组中间隙缓冲区之前的字符拷贝到新数组开头
        char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
        System.arraycopy(mText, 0, newText, 0, mGapStart);
        final int newLength = newText.length;
        //新增的长度,可以是负数,负数代表缩小数组长度
        final int delta = newLength - oldLength;
        //原数组中间隙缓冲区的末尾
        final int after = oldLength - (mGapStart + mGapLength);
        //将原数组中间隙缓冲区之后的字符也拷贝到新数组末尾,中间多预留一些位置以扩展间隙缓冲区大小(请注意,间隙缓冲区的大小永远是数组中空闲的大小)
        System.arraycopy(mText, oldLength - after, newText, newLength - after, after);

        //轮替mText,间隙缓冲区长度增加
        mText = newText;
        mGapLength += delta;

        if (mGapLength < 1)
            new Exception("mGapLength < 1").printStackTrace();
        if (mSpanCount != 0) 
        {
            //遍历所有span,在间隙缓冲区之后的span的范围会增加delta(移到间隙缓冲区之后)
            for (int i = 0; i < mSpanCount; i++) {
                if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
                if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
            }
            //节点顺序不变,仅需重新计算节点最大范围
            calcMax(treeRoot());
        }
    }

    /*移动间隙缓冲区到指定位置*/
    private void moveGapTo(int where)
    {
        if (where == mGapStart)
            return;
        boolean atEnd = (where == length());
        if (where < mGapStart) {
            //如果要移动到的位置在当前间隙缓冲区之前,仅需将间隙缓冲区与前面的内容(where~mGapStart之间的内容)的位置置换
            int overlap = mGapStart - where;
            System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
        } else {
            //否则,和后面的内容的位置置换
            int overlap = where - mGapStart;
            System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
        }
        
        // 聪明一点(虽然赢的真的没那么大)
        if (mSpanCount != 0) 
        {
            //遍历所有的span,调整它们的位置
            for (int i = 0; i < mSpanCount; i++) 
            {
                int start = mSpanStarts[i];
                int end = mSpanEnds[i];
                
                //下面的代码分为两步理解
                //先认为我们把间隙缓冲区移除了,因此在间隙缓冲区之后的span的真实位置都前移mGapLength
                //之后又把间隙缓冲区插入到where的位置,因此在where之后的span的真实位置都后移mGapLength
                if (start > mGapStart)
                    start -= mGapLength;
                if (start > where)
                    start += mGapLength;
                else if (start == where) {
                    //当在span的一端插入缓冲区
                    int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
                    if (flag == POINT || (atEnd && flag == PARAGRAPH))
                        //POINT标志的端点应将缓冲区排除在前面(自己向后移)
                        //而MARK标志的端点应将缓冲区排除在后面(保持不变)
                        //如果缓冲区移动到全部文本末尾,PARAGRAPH标志的span移动到文本末尾
                        //这里为什么将POINT标志的端点移到start += mGapLength? 有三个原因:
                        //1、防止span在removeSpansForChange中被移除,请再看一下移除span的条件
                        //2、span端点在updateIntervalBound中不用再管了,请再看一下span端点修正的条件
                        //3、使刚好衔接在插入点的端点扩展,每次修改文本,就将光标移到插入点,刚好处于此位置的端点可以移到缓冲区之后,以此在插入文本时扩展
                        start += mGapLength;
                }
                
                if (end > mGapStart)
                    end -= mGapLength;
                if (end > where)
                    end += mGapLength;
                else if (end == where) {
                    int flag = (mSpanFlags[i] & END_MASK);
                    if (flag == POINT || (atEnd && flag == PARAGRAPH))
                        end += mGapLength;
                }
                
                mSpanStarts[i] = start;
                mSpanEnds[i] = end;
            }
            //节点顺序不变,仅需重新计算节点最大范围
            calcMax(treeRoot());
        }
        //最后才将mGapStart修改
        mGapStart = where;
    }
    
    public SpannableStringBuilderTemplete append(CharSequence text){
        int length = length();
        return replace(length, length, text, 0, text.length());
    }
    public SpannableStringBuilderTemplete append(CharSequence text, Object what, int flags){
        int start = length();
        append(text);
        setSpan(what, start, length(), flags);
        return this;
    }
    public SpannableStringBuilderTemplete append(CharSequence text, int start, int end) {
        int length = length();
        return replace(length, length, text, start, end);
    }
    public SpannableStringBuilderTemplete append(char text) {
        return append(String.valueOf(text));
    }
    
    public SpannableStringBuilderTemplete insert(int where, CharSequence tb, int start, int end) {
        return replace(where, where, tb, start, end);
    }
    public SpannableStringBuilderTemplete insert(int where, CharSequence tb) {
        return replace(where, where, tb, 0, tb.length());
    }
    public SpannableStringBuilderTemplete delete(int start, int end) {
        SpannableStringBuilderTemplete ret = replace(start, end, "", 0, 0);
        //删除文本后,间隙缓冲区大小过大,重新调整大小
        if (mGapLength > 2 * length())
            resizeFor(length());
        return ret; 
    }
    public SpannableStringBuilderTemplete replace(int start, int end, CharSequence tb) {
        return replace(start, end, tb, 0, tb.length());
    }
    
    /** 
     * 替换start~end范围的文本为tb中的tbstart~tbend之间的文本,并改变span的位置
     * 若范围内设置了SpanWatcher和TextWatcher,则会连带发送事件
     * 若范围内设置了光标的Span,则连带改变光标位置
     */
    public SpannableStringBuilderTemplete replace(final int start, final int end, CharSequence tb, int tbstart, int tbend)
    {
        checkRange("replace", start, end);
        int filtercount = mFilters.length;
        //过滤文本
        for (int i = 0; i < filtercount; i++) 
        {
            CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
            if (repl != null) {
                tb = repl;
                tbstart = 0;
                tbend = repl.length();
            }
        }
        
        final int origLen = end - start;
        final int newLen = tbend - tbstart;
        if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
            //如果tb中没有要添加的跨度(长度为0),提前退出,以便文本观察器不会得到通知
            return this;
        }
        TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
        sendBeforeTextChanged(textWatchers, start, origLen, newLen);
        
        //在文本替换过程中,尽量将光标选择保持在相同的相对位置
        //如果replaced或replacement text length为0,则已经处理了这个
        boolean adjustSelection = origLen != 0 && newLen != 0;
        int selectionStart = 0;
        int selectionEnd = 0;
        if (adjustSelection) {
            //获取光标的Span在文本中的起始和末尾位置
            selectionStart = Selection.getSelectionStart(this);
            selectionEnd = Selection.getSelectionEnd(this);
        }
        
        //改变文本和span
        change(start, end, tb, tbstart, tbend);
        
        if (adjustSelection)
        {
            boolean changed = false;
            if (selectionStart > start && selectionStart < end) 
            {
                //如果当前的光标位置正好在删除的文本之间,我们需要保留光标的Span
                //光标要变化的offset是新添加文本相对于原文本的倍数
                //原光标位置是 start + diff
                //新光标位置是 start + diff * (newLen/origLen)
                final long diff = selectionStart - start;
                final int offset = Math.toIntExact(diff * newLen / origLen);
                selectionStart = start + offset;
                changed = true;
                setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
                        Spanned.SPAN_POINT_POINT, true/*强制执行段落*/);
            }
            if (selectionEnd > start && selectionEnd < end) 
            {
                final long diff = selectionEnd - start;
                final int offset = Math.toIntExact(diff * newLen / origLen);
                selectionEnd = start + offset;
                changed = true;
                setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
                        Spanned.SPAN_POINT_POINT, true/*强制执行段落*/);
            }
            if (changed) {
                //光标位置变化了,刷新
                restoreInvariants();
            }
        }
        
        sendTextChanged(textWatchers, start, origLen, newLen);
        sendAfterTextChanged(textWatchers);
        // Span观察器需要在文本观察器之后调用,这可能会更新布局
        sendToSpanWatchers(start, end, newLen - origLen);
        return this;
    }
    
    /* 替换start~end范围的文本为tb中的tbstart~tbend之间的文本,并改变span的位置 */
    private void change(int start, int end, CharSequence cs, int csStart, int csEnd) 
    {
        //删除文本的长度,插入的文本长度,溢出文本的长度(可以是负数)
        final int replacedLength = end - start;
        final int replacementLength = csEnd - csStart;
        final int nbNewChars = replacementLength - replacedLength;
        boolean changed = false;
        
        //遍历所有的span,修正它们的flags,如果flags是SPAN_PARAGRAPH,则还会修正范围
        for (int i = mSpanCount - 1; i >= 0; i--)
        {
            //获取span位置,并移动到原本的位置(而不是附加上间隙缓冲区之后的真实位置)
            int spanStart = mSpanStarts[i];
            if (spanStart > mGapStart){
                spanStart -= mGapLength;
            }
            int spanEnd = mSpanEnds[i];
            if (spanEnd > mGapStart){
                spanEnd -= mGapLength;
            }
            
            //如果flags是段落的标志,则还会修正范围
            if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) 
            {
                int ost = spanStart;
                int oen = spanEnd;
                int clen = length();
                //如果span位置在删除范围内,它需要跳至end之后的下一行
                if (spanStart > start && spanStart <= end) {
                    for (spanStart = end; spanStart < clen; spanStart++)
                        if (spanStart > end && charAt(spanStart - 1) == '\n')
                            break;
                }
                if (spanEnd > start && spanEnd <= end) {
                    for (spanEnd = end; spanEnd < clen; spanEnd++)
                        if (spanEnd > end && charAt(spanEnd - 1) == '\n')
                            break;
                }
                //如果span的范围变化了,就重新设置它的位置,但暂时不用刷新所有span,而是等之后刷新
                if (spanStart != ost || spanEnd != oen) {
                    setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i],
                            true);
                    changed = true;
                }
            }
            
            //无论怎样,都修正span的flags,以等待之后的sendToSpanWatchers来处理它们
            int flags = 0;
            if (spanStart == start) flags |= SPAN_START_AT_START;
            else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
            if (spanEnd == start) flags |= SPAN_END_AT_START;
            else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
            mSpanFlags[i] |= flags;
        }
        if (changed) {
            //如果span的范围变化了,需要刷新
            restoreInvariants();
        }
        
        //将间隙缓冲区移动到删除文本的末尾
        moveGapTo(end);
        if (nbNewChars >= mGapLength) {
            //间隙缓冲区不足以容纳溢出的文本,需要扩展间隙缓冲区
            resizeFor(mText.length + nbNewChars - mGapLength);
        }
        
        final boolean textIsRemoved = replacementLength == 0;
        //需要在间隙缓冲区位置更新之前完成移除过程,以便将正确的先前位置传递给正确的相交跨度观察器
        if (replacedLength > 0){ 
            //纯插入时不需要span移除
            while (mSpanCount > 0 && removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
                  //根据需要不断删除范围内的spans,每次删除后从根重新开始,因为删除会使索引失效
            }
        }
        
        //插入文本并不需要扩展数组,仅需将间隙缓冲区缩小
        //另一个情况是,nbNewChars是负数,说明要插入的文本太短,此时等同于扩展间隙缓冲区
        //无论怎样,都将间隙缓冲区对齐到插入文本的末尾
        mGapStart += nbNewChars;
        mGapLength -= nbNewChars;
        if (mGapLength < 1)
            new Exception("mGapLength < 1").printStackTrace();
        TextUtils.getChars(cs, csStart, csEnd, mText, start);
        //然后插入文本,注意文本是从start开始插入的,所以start~end之间的内容已经被覆盖了,因此间隙缓冲区只用管溢出文本
        
        if (replacedLength > 0)
        { 
            //修正所有在删除文本范围内的span的位置,范围之前或之后的span不修正,纯插入时不需要span修正
            //我们一般认为当插入文本后,插入位置之前的span位置不变,之后的span的位置应该往后挪,这个想法很单纯
            //但由于我们引入了间隙缓冲区,所以每次获取间隙缓冲区之后的span的真实位置后,会减去GapLength得到原本的位置
            //因此,若将GapStart移动到前面并将GapLength缩小,实际等同于间隙缓冲区之后的span的位置增大
            //同理,若将GapStart移动到前面并将GapLength增大,实际等同于间隙缓冲区之后的span的位置缩小
            //另外,若将GapStart移到某个span后面,该span会立刻转换为当前的原本位置
            //即使reSizeFor时GapLength增大,但也连带之后的span增大,它们是同步的
            //另外一个很重要的概念,每次修正后,span的真实位置(start和end)不可能在间隙缓冲区中
            //潜在优化:仅更新范围之内的span的位置
            final boolean atEnd = (mGapStart + mGapLength == mText.length);
            for (int i = 0; i < mSpanCount; i++)
            {
                final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
                mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
                                                      atEnd, textIsRemoved);
                final int endFlag = (mSpanFlags[i] & END_MASK);
                mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
                                                    atEnd, textIsRemoved);
            }
            //潜在优化:仅在范围实际更改时刷新
            restoreInvariants();
        }
            
        if (cs instanceof Spanned) 
        {
            //如果增加的文本是Spanned,需要获取范围内全部的span并附加到自身
            Spanned sp = (Spanned) cs;
            Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
            for (int i = 0; i < spans.length; i++) 
            {
                int st = sp.getSpanStart(spans[i]);
                int en = sp.getSpanEnd(spans[i]);
                //span的位置不可超过截取的范围
                if (st < csStart) st = csStart;
                if (en > csEnd) en = csEnd;
                
                //已有的span不会重复添加
                if (getSpanStart(spans[i]) < 0)
                {
                    //将span在原字符串中较csStart的偏移量获取,并加上start偏移到新字符串中的位置
                    int copySpanStart = st - csStart + start;
                    int copySpanEnd = en - csStart + start;
                    int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED;
                    //刚刚添加的span附加上SPAN_ADDED标志
                    setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags, false);
                }
            }
            //添加span之后一并刷新
            restoreInvariants();
        }
    }
    
    /* 文本变化后,在start~end范围内的文本中,下标为i的节点及其子节点是否要删除,删除了一个就立即返回true
       注意,一旦任意一个节点被移除,函数直接返回,因为删除之后的节点下标都将是错误的
       因此函数本质上只是从节点i开始向下找一个节点并移除,因此需要循环调用,以移除范围内的所有节点
    */
    private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i)
    {
        if ((i & 1) != 0) {
            //节点i不是叶子节点,若它的最大边界在start之后,则至少有一个左子节点可能在范围内,处理左子节点
            //在getSpansRec和nextSpanTransitionRec中都是直接判断左子节点是否在范围内,从而决定是否遍历左子节点
            //而这里却是用节点i的值判断是否需要遍历左子节点,是因为该函数对于节点的判断条件很复杂,无法直接决定吗?(疑惑)
            if (resolveGap(mSpanMax[i]) >= start &&
                removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
                return true;
            }
        }
        if (i < mSpanCount) 
        {
            //此时mGapStart实际上是删除范围的end
            //整个节点原本在start~end之间,但可能两端衔接在start和end上(spanEnd可以在mGapStart上,但不在mGapStart + mGapLength上)
            //要替换的文本长度为0就直接移除此节点,否则节点的两端都要衔接在start和end上才不会被移除(实际是为了等之后在updatedIntervalBound中,对span进行扩展)
            //实际上如果满足了mSpanEnds[i] < mGapStart这个条件,就意味着spanEnd在缓冲区之前,它必然是其原本的位置,而由于之前spanEnd在删除范围内,所以可以移除
            //为什么要把Point标志的端点移到mGapStart+mGapLength? 因为可以防止被删除
            if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
                mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
                mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
                (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)){
                //满足条件的节点i会被移除
                mIndexOfSpan.remove(mSpans[i]);
                removeSpan(i, 0);
                return true;
            }
            //若节点i的spanStart在end之前,并且有右子节点,处理右子节点(右子节点spanStart>=节点i的spanStart)
            return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
                removeSpansForChange(start, end, textIsRemoved, rightChild(i));
        }
        return false;
    }
    
    /* 文本修改后,在删除范围内的span端点应该移动到哪里,在删除范围外的span端点实际不变 */
    private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, boolean textIsRemoved)
    {
        //此时mGapStart实际上是插入文本的end,若offset的原本位置处于删除范围内,才需要计算位置
        //而删除范围此时已不知道,但其实它必然在start ~ mGapStart+mGapLength之中(由于mGapStart >= start,所以这里的start亦可以是真实和原本的位置,而end也不超过mGapStart + mGapLength)
        //由于在删除后,间隙缓冲区移动了,因此此时可能还有一部分端点错误地分布在间隙缓冲区中,此函数正是应该将它们都移动到正确的位置(也就是间隙缓冲区两端)
        //还要注意,resolveGap的使用时机是,保证所有节点的端点都不在间隙缓冲区中,此函数是不可能使用resolveGap的
        if (offset >= start && offset < mGapStart + mGapLength) 
        {
            if (flag == POINT) {
                //若span的端点为POINT标志,该端点应将插入文本排除在前面。也就是说,位于删除范围内的端点应移动到插入文本的末尾,即mGapStart
                //另一个情况是当端点位于start并且我们正在进行文本替换(而不是删除)时,该端点保持在start(意为将span之内的内容替换为另一个内容,span要包含替换的内容)
                if (textIsRemoved || offset > start) {
                    return mGapStart + mGapLength;
                }
            } 
            else 
            {
                if (flag == PARAGRAPH) {
                    //如果删除范围在全部文本最后,在其中的段落标志的span端点应该保持在文本末尾
                    if (atEnd) {
                        return mGapStart + mGapLength;
                    }
                }
                else
                { 
                    if (textIsRemoved || offset < mGapStart - nbNewChars) {
                        //如果span端点为MARK标志(无标志的端点默认按MARK处理),该端点应将插入文本排除在后面。所以应该将删除范围内的端点移动到开头(mGapStart - nbNewChars实际等于删除文本的end)
                        return start;
                    } else {
                        //若offset刚好是位于范围结尾的端点,它应该包含替换的文本。因此移动到插入文本的末尾,即mGapStart
                        return mGapStart;
                    }
                }
            }
        }
        return offset;
    }
    
    /* 在修改文本后,发送span的改变事件 */
    private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) 
    {
        //此循环仅处理修改的(非添加的)跨度
        for (int i = 0; i < mSpanCount; i++) 
        {
            int spanFlags = mSpanFlags[i];
            //刚刚添加的span不处理
            if ((spanFlags & SPAN_ADDED) != 0) continue;
            int spanStart = mSpanStarts[i];
            int spanEnd = mSpanEnds[i];

            if (spanStart > mGapStart) spanStart -= mGapLength;
            if (spanEnd > mGapStart) spanEnd -= mGapLength;
            //替换文本的末尾位置就是删除文本的末尾加上较原来增加的字符数,nbNewChars可能是负数
            int newReplaceEnd = replaceEnd + nbNewChars;
            boolean spanChanged = false;
            int previousSpanStart = spanStart;

            if (spanStart > newReplaceEnd) {
                //如果spanStart在替换文本之后,旧的spanStart则应该减去新增的字符数
                if (nbNewChars != 0) {
                    previousSpanStart -= nbNewChars;
                    spanChanged = true;
                }
            }
            else if (spanStart >= replaceStart) {
                //如果spanStart在替换之前已经位于替换间隔边界,则不改变
                if ((spanStart != replaceStart ||
                    ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
                    (spanStart != newReplaceEnd ||
                    ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
                    //此时无法计算正确的previousSpanStart
                    //在替换之前需要保存所有先前跨度的位置
                    //使用无效的-1值来传达这将破坏broacast范围
                    spanChanged = true;
                }
            }
            int previousSpanEnd = spanEnd;
            if (spanEnd > newReplaceEnd) {
                if (nbNewChars != 0) {
                    previousSpanEnd -= nbNewChars;
                    spanChanged = true;
                }
            } 
            else if (spanEnd >= replaceStart) {
                //如果span start在替换之前已经位于替换间隔边界,则不改变
                if ((spanEnd != replaceStart ||
                    ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
                    (spanEnd != newReplaceEnd ||
                    ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
                    //与上面的previousSpanEnd相同
                    spanChanged = true;
                }
            }
            if (spanChanged) {
                //发送span改变事件
                sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
            }
            mSpanFlags[i] &= ~SPAN_START_END_MASK;
        }
        //此循环仅处理添加的跨度
        for (int i = 0; i < mSpanCount; i++) 
        {
            int spanFlags = mSpanFlags[i];
            if ((spanFlags & SPAN_ADDED) != 0)
            {
                //如果是新添加的span,处理后需要清除SPAN_ADDED标志
                mSpanFlags[i] &= ~SPAN_ADDED;
                int spanStart = mSpanStarts[i];
                int spanEnd = mSpanEnds[i];
                if (spanStart > mGapStart) spanStart -= mGapLength;
                if (spanEnd > mGapStart) spanEnd -= mGapLength;
                //发送span添加事件
                sendSpanAdded(mSpans[i], spanStart, spanEnd);
            }
        }
    }
    
    public void clear() 
    {
        //删除所有的文本,并发送文本事件
        replace(0, length(), "", 0, 0);
        mSpanInsertCount = 0;
    }
    
    public void clearSpans() 
    {
        //遍历所有的span并删除,每删除一个发送一次事件
        for (int i = mSpanCount - 1; i >= 0; i--) 
        {
            Object what = mSpans[i];
            int ostart = mSpanStarts[i];
            int oend = mSpanEnds[i];
            //span的原本位置是真实位置减去缓冲区大小
            if (ostart > mGapStart)
                ostart -= mGapLength;
            if (oend > mGapStart)
                oend -= mGapLength;
            mSpanCount = i;
            mSpans[i] = null;
            sendSpanRemoved(what, ostart, oend);
        }
        if (mIndexOfSpan != null) {
            mIndexOfSpan.clear();
        }
        mSpanInsertCount = 0;
    }
    
    /* 检查文本中的指定位置是否没有SPAN_EXCLUSIVE_EXCLUSIVE标志的span,没有就返回true */
    private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) 
    {
        if (text instanceof Spanned)
        {
            Spanned spanned = (Spanned) text;
            Object[] spans = spanned.getSpans(offset, offset, Object.class);
            final int length = spans.length;
            for (int i = 0; i < length; i++) 
            {
                Object span = spans[i];
                int flags = spanned.getSpanFlags(span);
                if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 
                    return true;
            }
        }
        return false;
    }
    
    /** 用指定对象标记指定范围的文本 */
    public void setSpan(Object what, int start, int end, int flags) {
        setSpan(true, what, start, end, flags, true);
    }
    
    //注意:如果send为false,那么恢复不变量就是调用者的责任(如果send为false,并且跨度已经存在,则此方法不会更改任何跨度的索引)
    //因为新增的span默认在最后,不影响前面节点的顺序,所以可以暂时不刷新
    private void setSpan(boolean send, Object what, int start, int end, int flags, boolean enforceParagraph)
    {
        checkRange("setSpan", start, end);
        int flagsStart = (flags & START_MASK) >> START_SHIFT;
        if (isInvalidParagraph(start, flagsStart)) {
            if (!enforceParagraph) {
                //不要设置跨度
                return;
            }
            throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"
                                       + " (" + start + " follows " + charAt(start - 1) + ")");
        }
        int flagsEnd = flags & END_MASK;
        if (isInvalidParagraph(end, flagsEnd)) {
            if (!enforceParagraph) {
                //不要设置跨度
                return;
            }
            throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"
                                       + " (" + end + " follows " + charAt(end - 1) + ")");
        }
        //0-长度跨度。SPAN_EXCLUSIVE_EXCLUSIVE
        if (flagsStart == POINT && flagsEnd == MARK && start == end) {
            if (send) {
                Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
            }
            //从该类创建无效跨度时,自动忽略无效跨度。
            //这避免了在该类中完成对setSpan的所有调用之前重复上面的测试代码
            return;
        }
        
        int nstart = start;
        int nend = end;
        //如果设置span的位置在缓冲区之后,它的真实位置应加上mGapLength
        if (start > mGapStart) {
            start += mGapLength;
        } else if (start == mGapStart) {
            if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
                start += mGapLength;
        }
        if (end > mGapStart) {
            end += mGapLength;
        } else if (end == mGapStart) {
            if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
                end += mGapLength;
        }
        
        if (mIndexOfSpan != null) 
        {
            //如果已有该span,则修改它的范围和flags
            Integer index = mIndexOfSpan.get(what);
            if (index != null)
            {
                int i = index;
                int ostart = mSpanStarts[i];
                int oend = mSpanEnds[i];
                if (ostart > mGapStart)
                    ostart -= mGapLength;
                if (oend > mGapStart)
                    oend -= mGapLength;
                mSpanStarts[i] = start;
                mSpanEnds[i] = end;
                mSpanFlags[i] = flags;
                if (send) {
                    //是否要立刻修正span的位置错误,或等待以后一并修正
                    restoreInvariants();
                    sendSpanChanged(what, ostart, oend, nstart, nend);
                }
                return;
            }
        }
        
        //如果没有该span,就添加一个span
        mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
        mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
        mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
        mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
        mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
        invalidateIndex(mSpanCount);
        mSpanCount++;
        mSpanInsertCount++;
     
        //确保有足够的空间容纳空的内部节点
        //这个神奇的公式计算出最小的完美二叉树的大小,可能大于mSpanCount
        int sizeOfMax = 2 * treeRoot() + 1;
        if (mSpanMax.length < sizeOfMax) {
            mSpanMax = new int[sizeOfMax];
        }
        if (send) {
            //需要发送事件,则更新数据,使用原本的start和end传递事件
            restoreInvariants();
            sendSpanAdded(what, nstart, nend);
        }
    }
    //检查是否是无效段落
    private boolean isInvalidParagraph(int index, int flag) {
        return flag == PARAGRAPH && index != 0 && index != length() && charAt(index - 1) != '\n';
    }
    
    /**从文本中移除指定的标记对象*/
    public void removeSpan(Object what) {
        removeSpan(what, 0);
    }
    public void removeSpan(Object what, int flags)
    {
        if (mIndexOfSpan == null) return;
        //获取span的下标,并移除它
        Integer i = mIndexOfSpan.remove(what);
        if (i != null) {
            removeSpan(i.intValue(), flags);
        }
    }
    //注意:调用者负责删除mIndexOfSpan条目
    //与setSpan相反,移除span会打乱前面的索引,因此需要立刻刷新
    private void removeSpan(int i, int flags) 
    {
        Object object = mSpans[i];
        int start = mSpanStarts[i];
        int end = mSpanEnds[i];
        //如果span位置在间隙缓冲区之后,原本的位置应减去一个mGapLength
        if (start > mGapStart) start -= mGapLength;
        if (end > mGapStart) end -= mGapLength;

        //要移除此span,其实就是把此span之后的span全部往前挪一位
        int count = mSpanCount - (i + 1);
        System.arraycopy(mSpans, i + 1, mSpans, i, count);
        System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
        System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
        System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
        System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);

        mSpanCount--;
        invalidateIndex(i);
        mSpans[mSpanCount] = null;
        //在发送span removed通知之前,必须恢复不变量,再用原本的数据发送事件
        restoreInvariants();
        if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
            sendSpanRemoved(object, start, end);
        }
    }
    
    /**返回指定标记对象开头在文本中的偏移量,如果该对象未附加到文本,则返回-1*/
    public int getSpanStart(Object what) 
    {
        if (mIndexOfSpan == null) return -1;
        Integer i = mIndexOfSpan.get(what);
        return i == null ? -1 : resolveGap(mSpanStarts[i]);
    }
    /**返回指定标记对象末尾在文本中的偏移量,如果该对象未附加到文本,则返回-1*/
    public int getSpanEnd(Object what)
    {
        if (mIndexOfSpan == null) return -1;
        Integer i = mIndexOfSpan.get(what);
        return i == null ? -1 : resolveGap(mSpanEnds[i]);
    }
    /**返回指定标记对象结尾的标志,如果它没有附加到此文本,则返回0*/
    public int getSpanFlags(Object what) 
    {
        if (mIndexOfSpan == null) return 0;
        Integer i = mIndexOfSpan.get(what);
        return i == null ? 0 : mSpanFlags[i];
    }
    
    /**返回指定类型的范围的数组,这些范围与指定的文本范围重叠。
      种类可以是Object.class,以获得所有跨度的列表,而不考虑类型。
     */
    @SuppressWarnings("unchecked")
    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
        return getSpans(queryStart, queryEnd, kind, true);
    }
    
    /*** 返回指定类型跨度的数组,这些范围与指定的文本范围重叠。 
       种类可能是 Object.class 以获取无论类型如何的所有跨度的列表。
       * * @param querystart 开始索引。 
       * @Param QueryEnd 结束索引。 
       * @param kind 类类型进行搜索。
       * @Param SortByInsertionOrder 如果为 true 结果按插入顺序排序。
       * @param <t> * @return 跨度数组。
       如果找不到结果,则为空数组。 
     **/
    public <T> T[] getSpans(int queryStart, int queryEnd,  Class<T> kind, boolean sortByInsertionOrder) 
    {
        if (kind == null) return (T[])EmptyArray.emptyArray(Object.class);
        if (mSpanCount == 0) return EmptyArray.emptyArray(kind);
        
        //统计范围内节点个数,并创建指定大小的数组
        int count = countSpans(queryStart, queryEnd, kind, treeRoot());
        if (count == 0) {
            return EmptyArray.emptyArray(kind);
        }
        T[] ret = (T[]) Array.newInstance(kind, count);
        final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
        final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
        
        //从根节点开始,找到范围内的所有节点
        getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer,
                    orderSortBuffer, 0, sortByInsertionOrder);
       
        //如果需要排序,则按插入顺序排序
        if (sortByInsertionOrder) {
            sort(ret, prioSortBuffer, orderSortBuffer);
            recycle(prioSortBuffer);
            recycle(orderSortBuffer);
        }
        return ret;
    }
    
    //从节点i开始,向下遍历子节点,统计所有在范围内的节点个数
    private int countSpans(int queryStart, int queryEnd, Class kind, int i) 
    {
        int count = 0;
        if ((i & 1) != 0) 
        {
            //若节点i不是叶子节点,先遍历其左子节点
            int left = leftChild(i);
            int spanMax = mSpanMax[left];
            if (spanMax > mGapStart) {
                spanMax -= mGapLength;
            }
            //若左子节点的spanMax >= queryStart,则左子节点中有至少一个在范围内的节点
            if (spanMax >= queryStart) {
                count = countSpans(queryStart, queryEnd, kind, left);
            }
        }
        if (i < mSpanCount)
        {
            //若节点i自己在范围内,count++
            int spanStart = mSpanStarts[i];
            if (spanStart > mGapStart) {
                spanStart -= mGapLength;
            }
            if (spanStart <= queryEnd)
            {
                int spanEnd = mSpanEnds[i];
                if (spanEnd > mGapStart) {
                    spanEnd -= mGapLength;
                }
                if (spanEnd >= queryStart &&
                    (spanStart == spanEnd || queryStart == queryEnd ||
                    (spanStart != queryEnd && spanEnd != queryStart)) &&
                    (Object.class == kind || kind.isInstance(mSpans[i]))) {
                    count++;
                }
                //若节点i有右子节点,则从右子节点开始找(因为右子节点spanStart大于或等于节点i的spanStart)
                if ((i & 1) != 0) {
                    count += countSpans(queryStart, queryEnd, kind, rightChild(i));
                }
            }
        }
        return count;
    }
    
    /** * 使用当前区间树节点下找到的跨度填充结果数组。 * 
    * @param querystart 间隔查询的起始索引。 
    * @Param QueryEnd 间隔查询的结束索引。
    * @param kind 类类型进行搜索。
    * @param i 当前树节点的索引。 
    * @param ret 数组将被填充结果。
    * @param priority buffer 记录找到的跨度优先级。
    * @param insertionOrder 记录找到的跨度的插入顺序。
    * @param count 找到的跨度数。
    * @param sort flag 填充优先级和插入顺序。 
    如果 false 则 * 具有优先级标志的跨度将在结果数组中进行排序。 
    * @param <t> * @return 找到的跨度总数。 
    */
    @SuppressWarnings("unchecked")
    private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort)
    {
        if ((i & 1) != 0) 
        {
            //若节点i不是叶子节点,先遍历其左子节点
            int left = leftChild(i);
            int spanMax = mSpanMax[left];
            if (spanMax > mGapStart) {
                spanMax -= mGapLength;
            }
            //若左子节点的spanMax >= queryStart,则左子节点中有至少一个在范围内的节点
            if (spanMax >= queryStart) {
                count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
                                    insertionOrder, count, sort);
            }
        }
        if (i >= mSpanCount) return count;
        //i已经在有效元素之后,其右子节点的下标更大,因此不用找了
        
        int spanStart = mSpanStarts[i];
        if (spanStart > mGapStart) {
            spanStart -= mGapLength;
        }
        if (spanStart <= queryEnd) 
        {
            //若节点i自己在范围内,将自己添加到数组中
            int spanEnd = mSpanEnds[i];
            if (spanEnd > mGapStart) {
                spanEnd -= mGapLength;
            }
            if (spanEnd >= queryStart &&
                (spanStart == spanEnd || queryStart == queryEnd ||
                (spanStart != queryEnd && spanEnd != queryStart)) &&
                (Object.class == kind || kind.isInstance(mSpans[i]))) 
            {
                int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
                int target = count;
                if (sort) {
                    //如果需要排序,我们还要添加该节点的优先级和插入顺序
                    priority[target] = spanPriority;
                    insertionOrder[target] = mSpanOrder[i];
                } 
                else if (spanPriority != 0) 
                {
                    //对具有优先级的元素进行插入排序,实际上是为即将添加的元素计算并留出一个位置
                    int j = 0;
                    for (; j < count; j++) {
                        int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
                        if (spanPriority > p) break;
                    }
                    System.arraycopy(ret, j, ret, j + 1, count - j);
                    target = j;
                }
                //将自己放入指定位置,但count每次指向最后的下一个元素
                ret[target] = (T) mSpans[i];
                count++;
            }
            //若节点i有右子节点,则还可以从右子节点开始找(因为右子节点及其子节点spanStart大于或等于节点i的spanStart)
            if (count < ret.length && (i & 1) != 0) {
                //为什么count直接被赋值? 因为count是递归累加的,它从传递的count开始,再次加上自己找到的个数后返回
                //这样做的原因是让count保持在数组当前最后一个元素的位置,以此按顺序放入元素
                count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
                                    insertionOrder, count, sort);
            }
        }
        return count;
    }
    
    /** 获取临时排序数组
    * @param elementCount要返回的int[]的大小
    * @返回一个长度至少为elementCount的int[]
    */
    private static int[] obtain(final int elementCount)
    {
        int[] result = null;
        synchronized (sCachedIntBuffer)
        {
            //如果找不到第一个可用的tmp数组,请尝试查找长度至少为elementCount的tmp数组
            int candidateIndex = -1;
            for (int i = sCachedIntBuffer.length - 1; i >= 0; i--)
            {
                if (sCachedIntBuffer[i] != null)
                {
                    if (sCachedIntBuffer[i].length >= elementCount) {
                        candidateIndex = i;
                        break;
                    } else if (candidateIndex == -1) {
                        candidateIndex = i;
                    }
                }
            }
            if (candidateIndex != -1) {
                result = sCachedIntBuffer[candidateIndex];
                sCachedIntBuffer[candidateIndex] = null;
            }
        }
        result = checkSortBuffer(result, elementCount);
        return result;
    }
    
    /** 
    * 回收排序数组
    * @param buffer要回收的数组
    */
    private static void recycle(int[] buffer)
    {
        synchronized (sCachedIntBuffer)
        {
            for (int i = 0; i < sCachedIntBuffer.length; i++) 
            {
                if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) {
                    sCachedIntBuffer[i] = buffer;
                    break;
                }
            }
        }
    }
    
    /** 检查数组的大小,并根据需要进行扩展
    * @param buffer要检查的数组。
    * @param size所需的大小。
    * @如果当前大小大于所需大小,则返回相同的数组实例。
      否则,将创建并返回一个*新实例。
    */
    private static int[] checkSortBuffer(int[] buffer, int size)
    {
        if (buffer == null || size > buffer.length) {
            return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
        }
        return buffer;
    }
    
    //将数组表示为堆,堆的每个节点最多有两个子节点,节点从按层次从上至下,从左至右,按数组中的顺序排列
    //将下标为0的元素作为根节点,而根节点的左右子节点下标分别为1,2,并且下层的子节点下标为3,4,5,6,一直这样排列下去
    /*例如一列数 0,1,2,3,4,5,6
      若表示为堆则是如下的结果:
              0
            ↙  ↘
          1        2
        ↙  ↘    ↙  ↘
       3     4   5     6
    */
    //一般地,堆中的任意节点i的父节点下标为i/2-1,而任意节点i的左子节点下标为i*2+1,而任意节点i的右子节点下标为i*2+2

    //大顶堆的性质: 大顶堆中,任意一个节点的子节点都小于它
    //要使用堆排序,需要将乱序的堆的路径全部按照大顶堆的方式排序,即从最后一个节点开始向上排序,最后到第一个节点时,所有路径都排好序,并且第一个节点最大
    //然后将第一个节点与最后一个节点交换位置,也就是将最大的节点踢出堆(实际放到数组最后),并且需要维护刚刚交换的根节点,使其之下的路径顺序排列
    //之后再从剩下的最后一个节点开始,按相同的方式排序,每次都将剩下的最大节点移至末尾,最后所有节点按升序排列
    
    /** 迭代堆排序实现。它将首先按照优先级,然后按照插入顺序对跨度进行排序
    优先级较高的范围将在优先级较低的范围之前。
    如果优先级相同,跨度将按照插入顺序排序。
    具有较低插入顺序的*范围将在具有较高插入顺序的范围之前。*
    * @param array跨度要排序的数组。
    * @ param priority的优先级
    * @ param insertionOrder对象类型的插入顺序。
    * @param <T> 
    */
    private static final <T> void sort(T[] array, int[] priority, int[] insertionOrder) 
    {
        int size = array.length;
        //从最后一个节点的父节点开始,向前将所有节点排序,构建一个大顶堆
        for (int i = size / 2 - 1; i >= 0; i--) {
            siftDown(i, array, size, priority, insertionOrder);
        }
        //从最后一个节点开始,向前一个个交换位置来排序
        for (int i = size - 1; i > 0; i--) 
        {
            //每次将节点i与根节点交换位置,使得最大的值移至末尾,末尾的值移至开头
            final T tmpSpan =  array[0];
            array[0] = array[i];
            array[i] = tmpSpan;
            final int tmpPriority =  priority[0];
            priority[0] = priority[i];
            priority[i] = tmpPriority;
            final int tmpOrder =  insertionOrder[0];
            insertionOrder[0] = insertionOrder[i];
            insertionOrder[i] = tmpOrder;
            
            //交换完成后,需要维护堆的顺序,仅需维护根节点的路径,并且堆的节点个数少1
            siftDown(0, array, i, priority, insertionOrder);
        }
    }
    
    /** 堆的维护函数
    * @param index 要维护的元素的索引。
    * @param array 要排序的数组。
    * @param size当前堆大小。
    * @ param priority 数组元素的优先级。
    * @ param insertionOrder 数组元素的插入顺序。
    */
    private static final <T> void siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder) 
    {
        //从index的左子节点开始
        int left = 2 * index + 1;
        while (left < size)
        {
            if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
                //如果左子节点小于右子节点,右子节点是最大的(left++),否则左子节点最大(left)
                left++;
            }
            if (compareSpans(index, left, priority, insertionOrder) >= 0) {
                //将index节点与其左右子节点中最大的节点比较,若此节点比它的子节点都大,则路径下的顺序已经正确了,不用比较了
                break;
            }
            
            //将index指向的节点与其左右子节点中最大的节点交换位置
            final T tmpSpan =  array[index];
            array[index] = array[left];
            array[left] = tmpSpan;
            final int tmpPriority =  priority[index];
            priority[index] = priority[left];
            priority[left] = tmpPriority;
            final int tmpOrder =  insertionOrder[index];
            insertionOrder[index] = insertionOrder[left];
            insertionOrder[left] = tmpOrder;
            
            //向下走到最大的子节点,并在之后与其左右子节点比较
            index = left;
            left = 2 * index + 1;
        }
    }
    
    /** *比较数组中的两个span元素。比较首先基于区间的优先级标志,然后是区间的插入顺序。*
    * @param left 要比较的元素的左索引。
    * @param right 要比较的其他元素的右索引。
    * @param priority span优先级
    * @param insertionOrder span插入顺序
    * @return 0代表两元素相等,-1代表左元素小于右元素,1代表左元素大于右元素
    */
    private static final int compareSpans(int left, int right, int[] priority, int[] insertionOrder)
    {
        int priority1 = priority[left];
        int priority2 = priority[right];
        if (priority1 == priority2) {
            return Integer.compare(insertionOrder[left], insertionOrder[right]);
        }
        //因为高优先级必须在低优先级之前,所以要比较的参数与插入顺序检查相反
        return Integer.compare(priority2, priority1);
    }
    
    
    /**返回start之后但小于或等于limit的下一个偏移量,其中指定节点的类型和范围*/
    public int nextSpanTransition(int start, int limit, Class kind)
    {
        if (mSpanCount == 0) return limit;
        if (kind == null) {
            kind = Object.class;
        }
        //从根节点开始找
        return nextSpanTransitionRec(start, limit, kind, treeRoot());
    }
    
    //此函数递归遍历节点i之下的节点并寻找在指定范围内的节点偏移量
    //由于二叉树是用数组表示的,因此对树的遍历类似于递归二分数组
    //我更愿意称nextSpanTransitionRec是二分查找法的升级版,原二分查找法是找数组中指定的值,这个函数就是在指定范围内找数组中的值
    //注意,每个节点包含st和en,虽然mSpanStarts可以这样找,但mSpanEnds是未预料的,因此无论如何仍要遍历所有节点
    
    //可以理解为它就是将数组分为一个个的二分范围,然后从最大的范围开始,遍历之下的范围(及范围内的节点)
    //由于先遍历左子节点,再遍历右子节点,并且是先分下去,然后返回,遍历顺序实际是按数组顺序进行的
    /*  
      例如一列数 0,1,2,3,4,5,6
      若表示为二叉树则是如下的结果:
              3
            ↙  ↘
          1        5
        ↙  ↘    ↙  ↘
       0     2   4     6
       
      1、找到整个数组中点3,从3开始向左分发
      2、找到0~3区间内的中点1,从1开始向左分发
      3、找到0~1区间内的中点0,0被执行!
      4、从0返回到1,1被执行!
      5、1继续向右分发到2,2被执行!
      6、从2返回到1返回到3,3被执行!
      7、从3开始向右分发,找到3~6区间的中点5,从5开始向左分发
      8、找到3~5区间的中点4,4被执行!
      9、从4返回到5,5被执行!
      10、5继续向右分发到6,6被执行!
      11、最后从6返回到5返回到3,递归遍历完成
    */
    
    //虽然函数相当于顺序遍历节点i之下的所有节点,但会利用已有条件来判断并舍弃遍历某部分的节点,并把st或en在范围内的节点的limit边界记录下来
    //每次遍历一个节点,就返回它的limit边界,此limit边界可以是自己的st或en,但大于start并且不超过上个节点的limit,并且此limit边界会限制之后的节点的limit边界
    //每次limit边界都随着返回可能缩小,最后必然是所有节点在此范围内最小的偏移量
    
    //从索引为i的节点开始,向下遍历其子节点,找到一个在start~limit之内且离start最近的偏移量,此偏移量可以是某个节点的起始或末尾位置
    private int nextSpanTransitionRec(int start, int limit, Class kind, int i) 
    {
        if ((i & 1) != 0) 
        {
            //若i不是叶子节点,则先遍历左子节点
            int left = leftChild(i);
            if (resolveGap(mSpanMax[left]) > start){
                //左子节点之下的最大区间在start之后,说明左子节点中有至少一个节点的spanEnd>start
                //因此可以继续遍历左子节点,找到一个spanEnd大于start但小于limit的左子节点的最小limit边界
                limit = nextSpanTransitionRec(start, limit, kind, left);
            }
        }
        //所有左子节点遍历完成,现在遍历自己和所有右子节点
        if (i < mSpanCount) 
        {
            //若节点i在有效节点范围内,看看它在不在start~limit之内,是则返回其在start~limit之内的最大的位置,否则返回limit
            int st = resolveGap(mSpanStarts[i]);
            int en = resolveGap(mSpanEnds[i]);
            if (st > start && st < limit && kind.isInstance(mSpans[i]))
                limit = st;
            if (en > start && en < limit && kind.isInstance(mSpans[i]))
                limit = en;
            if (st < limit && (i & 1) != 0) {
                //若节点i的起始位置在limit之前,则可能从i之后找一个小于limit边界的节点,从右子节点开始(因为右子节点的spanStart大于或等于节点i的spanStart)
                //(着重强调!)与遍历左子节点不同,注意这里为什么不用右子节点的spanStart判断是否需要遍历右子节点呢,因为右子节点的左子节点的spanStart可能小于右子节点的spanStart,但一定大于或等于节点i的spanStart
                limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
            }
        }
        return limit;
    }
    
    /** 返回一个新的CharSequence,它包含本对象的字符数组指定范围的字符,包括重叠范围 */
    public CharSequence subSequence(int start, int end) {
        return new SpannableStringBuilderTemplete(this, start, end);
    }
    /** 将范围内的字符复制到指定数组中,从指定的偏移量开始 */
    public void getChars(int start, int end, char[] dest, int destoff)
    {
        checkRange("getChars", start, end);
        //若范围完全在间隙缓冲区左边或右边,只获取一次,否则分两次获取
        if (end <= mGapStart) {
            System.arraycopy(mText, start, dest, destoff, end - start);
        } else if (start >= mGapStart) {
            System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
        } else {
            System.arraycopy(mText, start, dest, destoff, mGapStart - start);
            System.arraycopy(mText, mGapStart + mGapLength,
                             dest, destoff + (mGapStart - start),
                             end - mGapStart);
        }
    }
    /** 返回一个包含自己全部文本的字符串 */
    @Override
    public String toString()
    {
        int len = length();
        char[] buf = new char[len];
        getChars(0, len, buf, 0);
        return new String(buf);
    }
    /** 返回包含自身中指定范围的字符的字符串 */
    public String substring(int start, int end)
    {
        char[] buf = new char[end - start];
        getChars(start, end, buf, 0);
        return new String(buf);
    }
    
    /** 返回TextWatcher回调的深度,当对象没有处理TextWatchers时返回0,大于1的返回值意味着TextWatcher导致了递归触发TextWatcher的更改 */
    public int getTextWatcherDepth() {
        return mTextWatcherDepth;
    }
    /* 发送文本事件 */
    private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
        int n = watchers.length;
        mTextWatcherDepth++;
        for (int i = 0; i < n; i++) {
            watchers[i].beforeTextChanged(this, start, before, after);
        }
        mTextWatcherDepth--;
    }
    private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
        int n = watchers.length;
        mTextWatcherDepth++;
        for (int i = 0; i < n; i++) {
            watchers[i].onTextChanged(this, start, before, after);
        }
        mTextWatcherDepth--;
    }
    private void sendAfterTextChanged(TextWatcher[] watchers) {
        int n = watchers.length;
        mTextWatcherDepth++;
        for (int i = 0; i < n; i++) {
            watchers[i].afterTextChanged(this);
        }
        mTextWatcherDepth--;
    }
    
    /* 要发送span事件,会获取范围内的所有SpanWatcher并发送事件 */
    private void sendSpanAdded(Object what, int start, int end) {
        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
        int n = recip.length;
        for (int i = 0; i < n; i++) {
            recip[i].onSpanAdded(this, what, start, end);
        }
    }
    private void sendSpanRemoved(Object what, int start, int end) {
        //在调用此方法之前,保证设置了可能的SpanWatcher的边界,以便span的顺序不会影响此事件
        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
        int n = recip.length;
        for (int i = 0; i < n; i++) {
            recip[i].onSpanRemoved(this, what, start, end);
        }
    }
    private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
       SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
                                              Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
        int n = spanWatchers.length;
        for (int i = 0; i < n; i++) {
            spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
        }
    }
    
    /* 检查范围,并抛出异常 */
    private static String region(int start, int end) {
        return "(" + start + " ... " + end + ")";
    }
    private void checkRange(final String operation, int start, int end) {
        if (end < start) {
            throw new IndexOutOfBoundsException(operation + " " +
                                                region(start, end) + " has end before start");
        }
        int len = length();
        if (start > len || end > len) {
            throw new IndexOutOfBoundsException(operation + " " +
                                                region(start, end) + " ends beyond length " + len);
        }
    }
    
    /* 设置文本过滤器,它在文本改变前被调用,过滤器返回的文本将真正被插入 */
    public void setFilters(InputFilter[] filters)
    {
        if (filters == null) {
            throw new IllegalArgumentException();
        }
        mFilters = filters;
    }
    public InputFilter[] getFilters() {
        return mFilters;
    }
  
    
    //树的基本术语:
    //Tree 树是由节点和边组成的且不存在着任何环的一种数据结构
    //Node 节点,节点是组成树的每一个元素
    //Root 根,树的顶端节点
    //Child 孩子,一个节点直接指向的下一个节点称为该结点的孩子
    //Parent 父亲,若一个节点被指向,那么直接指向它的上个节点被称为它的父亲
    //Siblings 兄弟,具有同一个父亲(Parent)的孩子(Child)之间互称为兄弟(Sibling)
    //Ancestor 祖先,节点的祖先(Ancestor)是从根(Root)到该节点所经分支(Branch)上的所有节点
    //Descendant 子孙,反之,以某节点为根的子树中的任一节点都称为该节点的子孙(Ancestor)
    //Leaf 叶子(终端节点)没有孩子的节点(也就是度为0的节点)称为叶子(Leaf)或终端节点
    //Branch 分支(非终端节点) 至少有一个孩子的节点称为分支(Branch)或非终端节点
    //Degree 度,节点所拥有的子树个数称为节点的度(Degree)
    //Edge 边,一个节点和另一个节点之间的连接被称之为边
    //Path 路径,连接节点和其后代的节点之间的(节点,边)的序列
    //Level 层次,节点的层次(Level)从根(Root)开始定义起,根为第0层,根的孩子为第1层。以此类推,若某节点在第i层,那么其子树的根就在第i+1层。
    //Height of node 节点的高度是该节点和某个叶子之间存在的最长路径上的边的个数
    //Height of tree 树的高度是其根节点的高度
    //Depth of node 节点的 深度 是从树的根节点到该节点的边的个数。(注:树的深度指的是树中节点的最大层次。)
    //Forest 森林是n(>=0)棵互不相交的树的集合
    
    //若将顺序排列的一组数无限二分,可构成一颗二叉树,而二叉树的根节点必然在数组中间,从根节点分发出来的左子节点和右子节点便是二分的结果
    /*
      例如一列数 0,1,2,3,4,5,6
      若表示为二叉树则是如下的结果:
                3
              ↙  ↘
            1        5
          ↙  ↘    ↙  ↘
         0     2   4     6
    */
    
    //这里将跨度列表视为二叉树的跨度(以及开始和结束偏移量和标志)存储在按开始偏移量排序的线性数组中
    //为了快速搜索,这些数组采用了二分搜索法结构,这种结构是对一棵完美二叉树的有序遍历,这是一种有点不寻常但很有利的方法
    //包含值的节点的索引为0 <= i < n(其中n = mSpanCount),从而将访问值的逻辑保留为连续数组
    //其他平衡二叉树方法(例如完全二叉树)需要对节点索引进行一些洗牌
    
    //这个结构的基本性质:
    //整颗树像一个等腰三角形,所有内部节点都有两个子节点
    //对于一棵高度为m的完美二叉树,树有2^(m+1) - 1个总节点,树根的索引是2^m - 1
    //所有叶子节点的索引都是偶数,所有内部节点的索引都是奇数,因此(i & 1) != 0可判断其是不是叶子节点
    //索引i的一个节点的高度是i的二进制表示中尾部的连续的1的个数
    //高度为h的节点i的左子节点是i - 2^(h - 1)
    //高度为h的节点i的右子节点是i + 2^(h - 1)
    //任意节点的所有左子节点(及其索引)都小于它,所有右子节点都大于它
    
    //获取数组中间数,此下标是二叉树根节点的下标
    private int treeRoot() {
        //使用mSpanCount而不是arr.len,目的是让根节点保持在有效范围的中间位置
        return Integer.highestOneBit(mSpanCount) - 1;
    }
    //获取下标为i的节点的左子节点在数组中的下标
    private static int leftChild(int i) {
        // (i+1) & ~i 等同于 2^(i中尾随1的个数),即2^h
        // 则(((i + 1) & ~i) >> 1) 等同于 (2^h)/2 = 2^(h-1)
        return i - (((i + 1) & ~i) >> 1);
    }
    //获取下标为i的节点的右子节点在数组中的下标
    private static int rightChild(int i) {
        return i + (((i + 1) & ~i) >> 1);
    }
    
    //span数组还增加了mSpanMax[]数组,该数组表示上述二叉树结构上的区间树
    //对于每个节点,mSpanMax[]数组包含该节点及其后代的mSpanEnds的最大值
    //因此,遍历可以轻松地拒绝不包含与感兴趣区域重叠的跨度的子树
    //请注意,mSpanMax[]对于索引 >=n 的内部节点也有有效值,但这些节点有索引 <n 的后代
    //在这些情况下,它只表示其后代的最大跨度端,这是完美二叉树结构的结果,你也可以称它为二分区间树
    
    //这个完美二叉树的遍历也是绝了
    //对于左子节点,使用mSpanMax数组判断,因为它可能超出mSpanCount(也就是大于mSpanCount的内部节点的左子节点)
    //对于右子节点,使用mSpanStarts数组判断,由于mSpanStarts长度不够,因此只要它超出mSpanCount,就不再遍历了(也没有必要)
    //这样做的结果是,将只遍历mSpanCount内的节点,超出范围的节点只是作为一个搭手,用于遍历还在范围内的节点
    //这也是为什么左子节点不用判断是否超出mSpanCount的原因,因为它必须这样才能遍历完成
    
    //注意,此函数总是递归更新节点i及其之下的所有子节点的mSpanMax值
    //注意,对于任意n,此树的内部节点可能 >= n,因此,节点i的递归遍历的一般结构是:
    //若i不是叶子节点,则计算所有左侧子节点的最大值
    //若i在有效范围内,则计算自己的最大值
    //若i在有效范围内且有右子节点,则计算所有右侧子节点的最大值,右侧子节点的下标大于i
    private int calcMax(int i)
    {
        int max = 0;
        if ((i & 1) != 0) {
            //若i不是叶子节点,则计算左子节点的最大值
            max = calcMax(leftChild(i));
        }
        if (i < mSpanCount) 
        {
            //若i在有效的节点范围内,则计算自己的最大值
            max = Math.max(max, mSpanEnds[i]);
            if ((i & 1) != 0){
                //若i不是叶子节点,则计算右子节点的最大值,右侧子节点的下标必然大于i,因此若不在有效范围内可以不计算
                //但是,若i刚好是临近mSpanCount的下标(如mSpanCount-1),则其右子节点下标将超出mSpanCount
                //为什么不制止遍历呢,因为对于索引 >=n 的内部节点有索引 <n 的后代,这意味着,此右子节点虽无效,但它有在有效范围内的左子节点
                //因此,我们只是借右子节点之手,遍历还处于范围内的左子节点,由于右子节点本身>n,因此它自己和自己的右子节点不会遍历
                //但是,在最后它仍会设置自己的最大值,为了防止下标超出,所以mSpanMax数组的大小应大于mSpanCount,并且至少包含临近mSpanCount的最小右子节点的位置
                //根据完美二叉树的特性,根节点在treeRoot处,因此treeRoot应该二分了整个mSpanMax数组,所以mSpanMax的最小大小为(2*treeRoot)+1
                max = Math.max(max, calcMax(rightChild(i)));
            }
        }
        //设置自己的最大值,并返回
        mSpanMax[i] = max;
        return max;
    }
    
    //在跨度结构的任何突变后恢复二元区间树不变量(修改的内容越少恢复会越快)
    private void restoreInvariants() 
    {
        if (mSpanCount == 0) return;
        
        //不变量1: span starts按顺序排列
        //这是一个简单的插入排序,因为我们希望它大部分已被排序
        //每次向后从数组中拿出一个元素,并与其之前的元素比较,直到找到一个正确的位置,将其插入这里,这样每次排序之后,在i之前的内容都是排好序的    
        for (int i = 1; i < mSpanCount; i++) 
        {
            //如果当前元素比前面的元素更小,主动进行本次排序
            //注意,i之前的元素必然按顺序排列,因此只用与i-1比较就知道需不需要排序
            if (mSpanStarts[i] < mSpanStarts[i - 1])
            {
                Object span = mSpans[i];
                int start = mSpanStarts[i];
                int end = mSpanEnds[i];
                int flags = mSpanFlags[i];
                int insertionOrder = mSpanOrder[i];
                int j = i;
             
                //将j之前的元素与其比较,直到找到一个比它还小的元素才停止,并将其插入到这里
                do {
                    //由于此span必然在j之前,所以最后需要将其插入到正确位置,并还需要之前的元素顺序不变
                    //因此只要还没有找到位置,那么j-1位置的元素就要向后挪一位,空出位置
                    mSpans[j] = mSpans[j - 1];
                    mSpanStarts[j] = mSpanStarts[j - 1];
                    mSpanEnds[j] = mSpanEnds[j - 1];
                    mSpanFlags[j] = mSpanFlags[j - 1];
                    mSpanOrder[j] = mSpanOrder[j - 1];
                    j--;
                    //之后判断j-2位置的元素是否小于start,如果是就break
                } while (j > 0 && start < mSpanStarts[j - 1]);
                
                //最后将span插入这里(也就是j-1的位置)
                mSpans[j] = span;
                mSpanStarts[j] = start;
                mSpanEnds[j] = end;
                mSpanFlags[j] = flags;
                mSpanOrder[j] = insertionOrder;
                invalidateIndex(j);
            }
        }
        
        //不变量2: 使max是每个节点及其后代的最大跨度端点
        //从根节点开始,修正所有子节点的max值
        calcMax(treeRoot());
       
        //不变量3: mIndexOfSpan映射的索引修正
        if (mIndexOfSpan == null) {
            mIndexOfSpan = new IdentityHashMap<Object, Integer>();
        }
        //从被修改的下标开始,遍历之后的span,将span与正确的index重新绑定
        for (int i = mLowWaterMark; i < mSpanCount; i++) 
        {
            //每次从mIndexOfSpan拿出当前span的index,若没有此span或index是错误的,重新放入正确的span和index
            Integer existing = mIndexOfSpan.get(mSpans[i]);
            if (existing == null || existing != i) {
                mIndexOfSpan.put(mSpans[i], i);
            }
        }
        //修改完后mLowWaterMark置为无限大,意为所有的span都刷新了
        mLowWaterMark = Integer.MAX_VALUE;
    }
    
    //对mSpans的任何更新调用此函数,以便mIndexOfSpan可以被更新
    private void invalidateIndex(int i) {
        //更新mLowWaterMark的值,表示此之前的span没有刷新
        mLowWaterMark = Math.min(i, mLowWaterMark);
    }
    
    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
    private static final int[][] sCachedIntBuffer = new int[6][0]; //存放和回收用于排序的数组
    private InputFilter[] mFilters = NO_FILTERS; //过滤器列表
    
    private char[] mText; //文本数组
    private int mGapStart; //数组中空闲间隙的起始位置
    private int mGapLength; //空闲间隙的长度
  
    //用数组表示二叉树,所有数组中相同下标的内容代表同一节点的数据
    //mSpanStarts是最重要的,其总是正序排列,而节点之下的最大区间是用mSpanMax表示的
    //虽然如此,但遍历时仍只管mSpanCount之前的内容,所有的数组中,实际也只有前mSpanCount个元素有效,之后的空间预留用于添加新元素
    //另外二叉树的顺序是从mSpanCount/2这个下标开始二分得到的
    
    private Object[] mSpans; //存储每个节点所代表的span
    private int[] mSpanStarts; //存储每个节点在文本中的起始位置
    private int[] mSpanEnds; //存储每个节点在文本中的末尾位置
    private int[] mSpanMax;  //存储此节点及其子节点最大的范围(也就是最大的mSpanEnd)
    private int[] mSpanFlags; //存储每个节点的flags
    private int[] mSpanOrder;  //存储节点插入的顺序
    
    private int mSpanInsertCount;  //节点插入计数器
    private int mSpanCount;  //节点个数
    private IdentityHashMap<Object, Integer> mIndexOfSpan; //存储节点在数组中的下标
    private int mLowWaterMark;  //在此之前的索引没有被触及
    
    //TextWatcher回调可能会触发更改,从而触发更多回调,这记录了回调的深度,以防死循环
    private int mTextWatcherDepth;

    //这些值与Spanned中的公共SPAN_MARK/POINT值紧密相关
    //每个span有spanStart和spanEnd,而我们可以给两端点各设置MARK,POINT,PARAGRAPH其中的一个标志
    //spanFlags中,用1~4位存储spanEnd的标志,用5~8位存储spanStart的标志

    //我喜欢解释MARK 和 POINT的方式是将它们表示为方括号,以及它们在一系列文本中存在的任何偏移量,括号指向的方向显示标记或端点“附加”到的字符
    //因此对于POINT,您将使用开括号 - 它附加到它后面的字符上  hello[world!
    //对于MARK,您可以使用闭括号 - 它附加在它前面的字符上  hello]world!
    
    //一般地,在任意span之内(不包含两端)插入字符时,span都会包含插入的字符(由间隙缓冲区管理)
    //而当为其一端设置MARK标志,若正好在span的一端插入字符时(例如where == spanEnd),那么这一端的位置将跟随原文本之前的内容(保持不变),而新插入的文本被端点排除在后面
    //而POINT标志正好相反,若正好在span的一端插入字符时,那么这一端会随着插入字符而向后移动(跟随原文本之后的内容),而新插入的文本被端点排除在前面
    //PARAGRAPH标志更特殊,它永远保证span的一端是一个段落的结尾,即这一端永远在换行符的位置或文本末尾
    //下面的例子,我们给不同的字符串插入字符,注意端点变化
    
    /*
      SPAN_MARK_MARK 在0长度范围(spanStart == spanEnd)的偏移处插入"INSERT":标记保持固定(也就是被spanStart和spanEnd排除在后面)
      Before: Lorem ]]ipsum dolor sit.
      After:  Lorem ]]INSERTipsum dolor sit.

      在非0长度范围(spanStart和spanEnd包含着"ipsum")的开头插入:插入的文本包含在span的范围内(也就是被spanStart排除在后面)
      Before: Lorem ]ipsum] dolor sit.
      After:  Lorem ]INSERTipsum] dolor sit.

      在非0长度范围的末尾插入:插入的文本从span的范围中排除(也就是被spanEnd排除在后面)
      Before: Lorem ]ipsum] dolor sit.
      After:  Lorem ]ipsum]INSERT dolor sit.

      您可以从最后两个示例中看到,为什么SPAN_MARK_MARK标志与SPAN_INCLUSIVE_EXCLUSIVE标志同义
      在范围开始处插入的文本包含在范围内,而排除在末尾插入的文本
    */
    /*
      SPAN_POINT_POINT 在0长度范围的偏移处插入"INSERT":向前推动点(也就是被spanStart和spanEnd排除在前面)
      Before: Lorem [[ipsum dolor sit.
      After:  Lorem INSERT[[ipsum dolor sit.

      在非0长度范围的开头插入:插入的文本从span的范围中排除(也就是被spanStart排除在前面)
      Before: Lorem [ipsum[ dolor sit.
      After:  Lorem INSERT[ipsum[ dolor sit.

      在非0长度范围的末尾插入:插入的文本包含在span的范围内(也就是被spanEnd排除在前面)
      Before: Lorem [ipsum[ dolor sit.
      After:  Lorem [ipsumINSERT[ dolor sit.

      您可以从最后两个示例中看到为什么SPAN_POINT_POINT标志与SPAN_EXCLUSIVE_INCLUSIVE标志同义
      在范围开始处插入的文本将从范围中排除,而包含在末尾插入的文本
    */
    private static final int MARK = 1;
    private static final int POINT = 2;
    private static final int PARAGRAPH = 3;
    private static final int START_MASK = 0xF0;
    private static final int END_MASK = 0x0F;
    private static final int START_SHIFT = 4;
 
    //这些位(当前)没有被跨越标志使用
    //它们仅在发送span改变事件时使用,以判断该span的状态
    private static final int SPAN_ADDED = 0x800;
    private static final int SPAN_START_AT_START = 0x1000;
    private static final int SPAN_START_AT_END = 0x2000;
    private static final int SPAN_END_AT_START = 0x4000;
    private static final int SPAN_END_AT_END = 0x8000;
    private static final int SPAN_START_END_MASK = 0xF000;
    
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值