Android富文本編輯器(三):文字樣式

對(duì)于富文本編輯器來(lái)說(shuō),除了插入圖片,最重要的功能之一應(yīng)該就是提供不同的文字樣式了。其中主要包括:加粗、斜體、切換文字顏色等。與圖片不同,一般來(lái)說(shuō)我們對(duì)于文字樣式的產(chǎn)品需求包括兩個(gè)方面:

  1. 設(shè)置文字樣式;
  2. 獲取某段文字的當(dāng)前樣式;

先說(shuō)設(shè)置,可以參考這篇文章:
http://hunankeda110.iteye.com/blog/1420470

//設(shè)置字體前景色  
msp.setSpan(new ForegroundColorSpan(Color.MAGENTA), 12, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  //設(shè)置前景色為洋紅色  
  
//設(shè)置字體背景色  
msp.setSpan(new BackgroundColorSpan(Color.CYAN), 15, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  //設(shè)置背景色為青色  

//設(shè)置字體樣式正常,粗體,斜體,粗斜體  
msp.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), 18, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  //正常  
msp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 20, 22, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);  //粗體  
msp.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 22, 24, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);  //斜體  
msp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC), 24, 27, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);  //粗斜體

這個(gè)其實(shí)在之前本系列的文章中就介紹過(guò),是spannable string的基本用法。但是為了寫出擴(kuò)展性好、更工程化的方法,滿足修改、查找、添加等不同的需求,只有這個(gè)還是不行的。在網(wǎng)上找了很多源碼和資料后,向大家推薦這個(gè)項(xiàng)目的寫法:
https://github.com/1gravity/Android-RTEditor
這個(gè)項(xiàng)目實(shí)現(xiàn)的富文本編輯器非常強(qiáng)大,幾乎可以完成所有富文本的功能,筆者也從中獲益良多。這個(gè)項(xiàng)目中采用了類似工廠方法的模式,提供了一個(gè)Effect抽象基類,所有的文字樣式都繼承自該基類,并負(fù)責(zé)構(gòu)造對(duì)應(yīng)的Span,在基類中實(shí)現(xiàn)了apply方法,應(yīng)用不同的文字樣式。
如下,是一個(gè)BoldEffect的例子:

public class BoldEffect extends Effect<Boolean> {
    @Override
    protected Class<? extends Span> getSpanClazz() {
        return BoldSpan.class;
    }

    @Override
    protected Span<Boolean> newSpan(Boolean value) {
        return value ? new BoldSpan() : null;
    }
}

BoldSpan的實(shí)現(xiàn)如下:

public class BoldSpan extends StyleSpan implements Span<Boolean> {
    public BoldSpan() {
        super(Typeface.BOLD);
    }

    @Override
    public Boolean getValue() {
        return Boolean.TRUE;
    }
}

下面我們看一下Effect的源碼,理解上面子類中g(shù)etSpanClazz和newSpan的應(yīng)用場(chǎng)景。首先是判斷當(dāng)前樣式在選定的文本中是否存在,代碼如下:

final public boolean existsInSelection(RichEditText editor, int spanType) {
    Selection expandedSelection = getExpandedSelection(editor, spanType);
    if (expandedSelection != null) {
        Span<V>[] spans = getSpans(editor.getText(), expandedSelection);
        return spans.length > 0;
    }

    return false;
}

final public Span<V>[] getSpans(Spannable str, Selection selection) {
    Class<? extends Span> spanClazz = getSpanClazz();
    Span<V>[] result = str.getSpans(selection.start(), selection.end(), spanClazz);
    return result != null ? result : (Span<V>[]) Array.newInstance(spanClazz);
}

這里使用的getSpans方法是在Spanned接口中定義的,并在SpannableString中提供了實(shí)現(xiàn)。接口定義如下:

/**
 * Return an array of the markup objects attached to the specified
 * slice of this CharSequence and whose type is the specified type
 * or a subclass of it.  Specify Object.class for the type if you
 * want all the objects regardless of type.
 */
public <T> T[] getSpans(int start, int end, Class<T> type);

上面調(diào)用過(guò)程中,使用了子類中重載的getSpanClazz方法。
下面再來(lái)看看apply方法的實(shí)現(xiàn):


public void apply(RichEditText editor, int start, int end, V value) {
    Selection selection = new Selection(start, end);
    Spannable str = editor.getText();

    // expand the selection to "catch" identical leading and trailing styles
    Selection expandedSelection = selection.expand(1, 1);
    for (Span<V> span : getSpans(str, expandedSelection)) {
        boolean equalSpan = span.getValue() == value;
        int spanStart = str.getSpanStart(span);
        if (spanStart < selection.start()) {
            if (equalSpan) {
                selection.offset(selection.start() - spanStart, 0);
            }
            else {
                str.setSpan(newSpan(span.getValue()), spanStart, selection.start(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
        int spanEnd = str.getSpanEnd(span);
        if (spanEnd > selection.end()) {
            if (equalSpan) {
                selection.offset(0, spanEnd - selection.end());
            }
            else {
                str.setSpan(newSpan(span.getValue()), selection.end(), spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
            }
        }
        str.removeSpan(span);
    }

    if (value != null) {
        Span<V> newSpan = newSpan(value);
        if (newSpan != null) {
            int flags = selection.isEmpty() ? Spanned.SPAN_INCLUSIVE_INCLUSIVE : Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
            str.setSpan(newSpan, selection.start(), selection.end(), flags);
        }
    }
}

這段最重要的代碼就是從if (value != null)開(kāi)始,設(shè)置樣式span,其中用到了子類的newSpan方法。我們注意到,如果selection為空,則設(shè)置成Spanned.SPAN_INCLUSIVE_INCLUSIVE,而不為空是Spanned.SPAN_EXCLUSIVE_INCLUSIVE。一般來(lái)說(shuō),文字樣式應(yīng)該設(shè)置成SPAN_EXCLUSIVE_INCLUSIVE,這樣后續(xù)添加的文字就可以采用相同樣式。但在selection為空時(shí),前后都需要采用同樣的樣式,這樣再插入新的文字就可以達(dá)到該效果。
而前面for循環(huán)的部分,其實(shí)是在檢測(cè)之前的這段文字中是否已經(jīng)設(shè)置了樣式span。舉例說(shuō)明,如果一段文字長(zhǎng)度為20,其中1-8個(gè)字符已經(jīng)被設(shè)置了A樣式,12-20個(gè)字符已經(jīng)被設(shè)置了B樣式,現(xiàn)在要給5-15個(gè)字符設(shè)置C樣式,那么我們進(jìn)行下面三個(gè)步驟:

  1. 將A樣式span調(diào)整為1-5個(gè)字符(原來(lái)是1-8);
  2. 將B樣式span調(diào)整為16-20個(gè)字符(原來(lái)是12-20);
  3. 再將C樣式span設(shè)置在5-15個(gè)字符上;

對(duì)照上述邏輯,再看for循環(huán)中的代碼,就可以很好地理解了。大家也可以再對(duì)照代碼來(lái)看,可以查看上面所貼鏈接。當(dāng)然也可以在我的工程代碼,其中只有BoldEffect,但是也有類似Effect的實(shí)現(xiàn),但是都是參考了Android-RTEditor的代碼。再貼一下自己工程的鏈接:
https://github.com/InnerNight/rich-edit-text

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,699評(píng)論 19 139
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 7,390評(píng)論 0 17
  • 在項(xiàng)目中很常用到Spannable,既豐富了文本又精簡(jiǎn)了布局。SpannableString、Spanna...
    尋味Android閱讀 1,509評(píng)論 0 2
  • 年前的這幾天,一直在看代碼,發(fā)現(xiàn)自己之前對(duì)很多技術(shù)點(diǎn)的理解都不全面,甚至根本就是想當(dāng)然。所以有這樣一個(gè)想法,要把之...
    幻海流心閱讀 16,232評(píng)論 4 22
  • 旗幟的傳說(shuō)2 攻略四 經(jīng)過(guò)了上面的戰(zhàn)斗,我們會(huì)解鎖訓(xùn)練功能。 進(jìn)入訓(xùn)練模式。左上方會(huì)有任務(wù),點(diǎn)擊準(zhǔn)備就緒。 第一個(gè)...
    英武的P哥閱讀 931評(píng)論 0 0

友情鏈接更多精彩內(nèi)容