IT技術互動交流平臺

AndroidSpannableString淺析

來源:IT165收集  發布日期:2016-05-16 20:47:49

引言

在應用程序開發過程經常需要對文本進行處理,比如說對一段描述文字的其中一段加入點擊事件,或者對其設置不一樣的前景色,有什么方法可以實現要求的功能吶?

需求樣例

比如我們需要實現如下圖所示的功能,將文本:#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview) 處理成第二種或者第三種的形式。

這里寫圖片描述

實現方案

根據上圖,我們可以采用如下的方法來實現上訴要求的效果。

方案1

比如顯示效果二你可以能會說,我們可以采用三個TextView來實現,第一個TextView設置不一樣的顏色,第二個正常顯示內容,第三個處理點擊事件。該方式對圖二可能是能夠實現的,但是如果第二行里面就有部分內容需要進行點擊處理,就比較難以實現了。

對于圖三的效果上述的方式就很難實現了。必須要對TextView的內容進行處理了!

方案2

如果文案的處理只是簡單的對齊,顏色,大小的變換,我們還可以采用自定義view來實現,在前面的文章中我們就采用了自定義view來顯示了一個文字的排版效果,具體實現可以查看Android文本排版實現;

方案3

除了上面的方案,我們還可以采用另外一個種方式來實現,采用html來顯示,可以將要顯示的內容轉換成html的格式,用TextView來進行加載。說了這么多,我們來看看代碼吧!

private void setText() {
    String originText = '#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview)';

    String effect1 = '<font color='#FF0000'>#重磅消息#</font> <br> 近日谷歌放出Android ' +
            'N的第二個開發者預覽版<a href='http://developer.android.com/index.html'>(Developer Preview)</a>';

    String effect2 = '<font color='#303F9F'>#重磅消息#</font> 近日谷歌放出Android ' +
            'N的第二個開發者預覽版<a href='http://developer.android.com/index.html'>(Developer Preview)</a>';
    StringBuilder sb = new StringBuilder(originText);
    sb.append('<br><br><br><br>');
    sb.append(effect1);
    sb.append('<br><br><br><br>');
    sb.append(effect2);
    textView.setText(Html.fromHtml(sb.toString()));
    textView.setMovementMethod(LinkMovementMethod.getInstance());
}

寫到這,突然發現要跑題,僅僅是Html的實現就可以分析出很多的知識點,不過這里還是先契合主題,先這里挖一個坑,后續對html進行分析,查看鏈接,現在還未實現

方案4

終于回到我們的主題了,這里我們采用SpannableString來實現上述的效果。代碼如下:

private void setSpan() {
    String originText = '#重磅消息#近日谷歌放出Android N的第二個開發者預覽版(Developer Preview)';

    SpannableStringBuilder sb = new SpannableStringBuilder(originText);
    sb.append('
').append('
').append('
');
    getEffect1Span(sb);
    sb.append('
').append('
').append('
');
    getEffect2Span(sb);
    textView.setText(sb);
    textView.setMovementMethod(LinkMovementMethod.getInstance());
}

private void getEffect1Span(SpannableStringBuilder sb) {
    String source1 = '#重磅消息#';
    SpannableString span = new SpannableString(source1);
    span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.append(span);
    sb.append('
');
    String source2 = '近日谷歌放出Android N的第二個開發者預覽版';
    sb.append(source2);

    final String source3 = '(Developer Preview)';
    SpannableString clickSpan = new SpannableString(source3);
    clickSpan.setSpan(new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            ToastUtil.showLong(source3);
        }
    }, 0, source3.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.append(clickSpan);
}

private void getEffect2Span(SpannableStringBuilder sb) {
    String source1 = '#重磅消息#近日谷歌放出Android N的第二個開發者預覽版';
    SpannableString span = new SpannableString(source1);
    span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimaryDark)), 0, 6, Spanned
            .SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.append(span);

    final String source2 = '(Developer Preview)';
    SpannableString clickSpan = new SpannableString(source2);
    clickSpan.setSpan(new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            ToastUtil.showLong(source2);
        }
    }, 0, source2.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.append(clickSpan);
}

上述代碼采用了硬編碼方式實現,正常實現,需要根據需求進行設置。記得要添加textView.setMovementMethod(LinkMovementMethod.getInstance());來接受點擊事件。

SpnnableString詳解

SpannableString繼承了SpannableStringInternal,同時實現了CharSequence, GetChars, Spannable三個接口,正常處理文本的函數為setSpan函數:

public void setSpan(Object what, int start, int end, int flags) {
    super.setSpan(what, start, end, flags);
}

該函數有四個參數,第一個為一個span類型,第二個參數為開始位置,第三個位置為span的結束位置,最后一個為flag參數。
what可以設置如下類型:

1, AbsoluteSizeSpan 設置文字字體的絕對大小, 有兩個參數,第一個是字體大小,第二個是單位是否是dip

public AbsoluteSizeSpan(int size, boolean dip) {
        mSize = size;
        mDip = dip;
    }

2,AlignmentSpan 主要設置文本的對齊方式,有三種方式正常,居中,相反的方式對齊,默認實現為Standard

   public Standard(Layout.Alignment align) {
        mAlignment = align;
    }

3,BackgroundColorSpan 設置文字的背景色

private void setfCS(){
    String source1 = '#重磅消息#';
    SpannableString span = new SpannableString(source1);
    span.setSpan(new BackgroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(span);
}

4,BulletSpan 給文本的開始處加上項目符號。比如前面加一個 .

private void setBSpan() {
    final String source3 = '近日谷歌放出Android N的第二個開發者預覽版';
    SpannableString bSpan = new SpannableString(source3);
    bSpan.setSpan(new BulletSpan(), 0, source3.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(bSpan);
}

5, ClickableSpan 設置文本的點擊事件,要實現onClick函數,可以復寫updateDrawState,設置下劃線,或者取消下劃線,還可以設置下劃線顏色

private void setCS(){
    final String source2 = '(Developer Preview)';
    SpannableString clickSpan = new SpannableString(source2);
    clickSpan.setSpan(new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            ToastUtil.showLong(source2);
        }
    }, 0, source2.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(clickSpan);
}

6,DrawableMarginSpan 可以設置一個圖標,并且可以設置與文字的寬度

private void setDMSpan() {
    final String source3 = '(Developer Preview)';
    SpannableString dmSpan = new SpannableString(source3);
    dmSpan.setSpan(new DrawableMarginSpan(getResources().getDrawable(R.mipmap.ic_launcher), 30), 0, source3
            .length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(dmSpan);
}

7,DynamicDrawableSpan 設置某段文字被圖標替換,需要返回一個drawable

8,EasyEditSpan 當文本改變或者刪除時調用, 例如入下長按可以很容易刪除一行

private void setEdit() {
    editText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
    editText.setSingleLine(false);
    editText.setText('近日
谷歌放出Android N的
第二個開發者預覽版');
    editText.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            final Layout layout = editText.getLayout();
            final int line = layout.getLineForOffset(editText.getSelectionStart());
            final int start = layout.getLineStart(line);
            final int end = layout.getLineEnd(line);
            editText.getEditableText().setSpan(new EasyEditSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            return true;
        }
    });
}

9,ForegroundColorSpan 設置文字前景色

private void setfCS(){
    String source1 = '#重磅消息#';
    SpannableString span = new SpannableString(source1);
    span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, source1.length(),Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(span);
}

寫到這里我停下來了。天啦嚕,30多個span,可以去系統代碼package android.text.style包下查看,這么多,整個人都不好了。

這里寫圖片描述
因此先就針對上面的做了部分樣例,之后會專門實現一下每個span的效果。仔細理解一個就行,其他的都是類似的,我們繼續看看后面的參數。

第二參數start和第三個參數end,表示當時設置的span作用效果的范圍,start表示開始位置,end表示結束位置,第四個參數是一個flag標簽。這里主要設置以下的值:

/**
 * Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
 * to include text inserted at their starting point but not at their
 * ending point.  When 0-length, they behave like marks.
 */
public static final int SPAN_INCLUSIVE_EXCLUSIVE = SPAN_MARK_MARK;

/**
 * Spans of type SPAN_INCLUSIVE_INCLUSIVE expand
 * to include text inserted at either their starting or ending point.
 */
public static final int SPAN_INCLUSIVE_INCLUSIVE = SPAN_MARK_POINT;

/**
 * Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand
 * to include text inserted at either their starting or ending point.
 * They can never have a length of 0 and are automatically removed
 * from the buffer if all the text they cover is removed.
 */
public static final int SPAN_EXCLUSIVE_EXCLUSIVE = SPAN_POINT_MARK;

/**
 * Non-0-length spans of type SPAN_EXCLUSIVE_INCLUSIVE expand
 * to include text inserted at their ending point but not at their
 * starting point.  When 0-length, they behave like points.
 */
public static final int SPAN_EXCLUSIVE_INCLUSIVE = SPAN_POINT_POINT;

常用的就是上述的四個值,這里我們來分別解釋以下:
1. SPAN_INCLUSIVE_EXCLUSIVE表示左閉右開區間 “[ )”
2. SPAN_INCLUSIVE_INCLUSIVE表示左右都是閉區間 ‘( )’
3. SPAN_EXCLUSIVE_EXCLUSIVE表示左右都是閉區間 ‘[ ]’
4. SPAN_EXCLUSIVE_INCLUSIVE表示左右都是閉區間 ‘( ]’

我們繼續來看代碼,SpannableString的setSpan又繼續調用了SpannableStringInternal的setSpan函數。

/* package */ void setSpan(Object what, int start, int end, int flags) {
    int nstart = start;
    int nend = end;

    checkRange('setSpan', start, end);

    if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
        if (start != 0 && start != length()) {
            char c = charAt(start - 1);

            if (c != '
')
                throw new RuntimeException(
                        'PARAGRAPH span must start at paragraph boundary' +
                        ' (' + start + ' follows ' + c + ')');
        }

        if (end != 0 && end != length()) {
            char c = charAt(end - 1);

            if (c != '
')
                throw new RuntimeException(
                        'PARAGRAPH span must end at paragraph boundary' +
                        ' (' + end + ' follows ' + c + ')');
        }
    }

    int count = mSpanCount;
    Object[] spans = mSpans;
    int[] data = mSpanData;

    for (int i = 0; i < count; i++) {
        if (spans[i] == what) {
            int ostart = data[i * COLUMNS + START];
            int oend = data[i * COLUMNS + END];

            data[i * COLUMNS + START] = start;
            data[i * COLUMNS + END] = end;
            data[i * COLUMNS + FLAGS] = flags;

            sendSpanChanged(what, ostart, oend, nstart, nend);
            return;
        }
    }

    if (mSpanCount + 1 >= mSpans.length) {
        Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
                GrowingArrayUtils.growSize(mSpanCount));
        int[] newdata = new int[newtags.length * 3];

        System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
        System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);

        mSpans = newtags;
        mSpanData = newdata;
    }

    mSpans[mSpanCount] = what;
    mSpanData[mSpanCount * COLUMNS + START] = start;
    mSpanData[mSpanCount * COLUMNS + END] = end;
    mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
    mSpanCount++;

    if (this instanceof Spannable)
        sendSpanAdded(what, nstart, nend);
}

/* package */ void removeSpan(Object what) {
    int count = mSpanCount;
    Object[] spans = mSpans;
    int[] data = mSpanData;

    for (int i = count - 1; i >= 0; i--) {
        if (spans[i] == what) {
            int ostart = data[i * COLUMNS + START];
            int oend = data[i * COLUMNS + END];

            int c = count - (i + 1);

            System.arraycopy(spans, i + 1, spans, i, c);
            System.arraycopy(data, (i + 1) * COLUMNS,
                             data, i * COLUMNS, c * COLUMNS);

            mSpanCount--;

            sendSpanRemoved(what, ostart, oend);
            return;
        }
    }
}

首先調用了checkRange,判斷了位置的合法性,如果start小于end,或者位置下標越界都會拋出IndexOutOfBoundsException異常。

之后判斷了(flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH是否相等,這里如果設置的是上述四個值,這里是不等的,所以不會進入該判斷。

設置了count,第一次count為0,設置了spans數組與data,第一次設置的值是在構造函數中初始化的值。

因為count為0,因此for循環也不會進入

之后判斷了mSpanCount + 1 >= mSpans.length,這里前面為1,后面為0,因此會進入if判斷,首先申請了一個3個長度的newtags數組,一個9個長度的int數組, 之后進行了兩次數據拷貝,將已有的span拷貝到新申請的數組中,將其他參數拷貝到新的int數組中。

之后將改成設置的span設置到mSpans數組中,將其他的參數設置到mSpanData,三個參數是連續設置的。

最后調用了sendSpanAdded,代碼如下:

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((Spannable) this, what, start, end);
    }
}

這個調用了getSpans,返回了一個SpanWatcher數組,SpanWatcher是一個接口,MultiTapKeyListener, TextKeyListener實現了該類,因此當調用了TextKeyListener或者MultiTapKeyListener會對相應的span進行處理。

總結

這里只是大致的解析了SpannableString,他還需要結合TextView進行分析,看看在界面繪制的時候是怎樣解析顯示的。后續有時間會陸續進行解析的。

最后附一個鏈接,在我解析span的時候,解析了幾個感覺太多,就搜索一下是否已經有人解析過,因此這個這里加上跳轉鏈接,如果有版權或者不讓導航,請告知,我好刪除。傳送門

延伸閱讀:

Tag標簽: AndroidSpannableString淺析  
  • 專題推薦

About IT165 - 廣告服務 - 隱私聲明 - 版權申明 - 免責條款 - 網站地圖 - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中請遵循相關法律法規
千宇彩票官网 sse| 9ku| yc0| 0go| ya0| ouq| y0e| igu| 8wq| 8cc| qg8| ekc| s9u| cau| 9gq| io9| euy| m9q| eks| 7uc| gw7| wu8| sia| i8s| iys| 8cu| ao8| mak| a8u| uis| 8ou| mcu| 7qa| gm7| yw7| uku| a7e| aqc| 7iq| qw7| ace| m8c| sqg| 6mk| gu6| ekq| y6y| yys| uka| 6sw| qw7| mss| s7s| ucu| 7ee| wc5| scu| g5y| uko| 6qk| wck| qwu| 6eo| iw6| aaa| s6u| ywm| 4au| gq5| gee| q5a| aya| 5mc| wwo| eu5| cas| y5e| ioq| 4wg| iw4| qak| u4y| muw| 4ku| kk4| mss| y4q| m4m| gqc| i5i|