Android Spannable

最近在研究android中emoji的顯示問題,突然對(duì)spannable,特別好奇.
查了一圈資料發(fā)現(xiàn)spannable是Android中的一大殺器啊
本文基本上是類似直播的形式,全文大量的粘貼注釋...

Spannable是一個(gè)接口,我們先來看看它的繼承樹.

Spannable繼承樹.png

然后是Spannable,這個(gè)接口里面居然還有個(gè)類,是一個(gè)單例類.


Spannable.png

仔細(xì)看了一下,原來Spannable也不是頂層接口...CharSequence才是...

CharSequence.png

我們先看一下CharSequence,居然是java.lang包下的,String是它的實(shí)現(xiàn)類...以前學(xué)java的時(shí)候都沒見過...注釋上說這個(gè)接口代表了一個(gè)有序的字符集合,而且定義了幾個(gè)方法來操作這個(gè)字符集合.

package java.lang;

/**
 * This interface represents an ordered set of characters and defines the
 * methods to probe them.
 */
public interface CharSequence

里面有這幾個(gè)方法.


CharSequence.png

接下來我們看一下Spanned接口,發(fā)現(xiàn)有一大堆MARK,POINT之類的常量.

Spanned.png

看一下注釋

/**
 * This is the interface for text that has markup objects attached to
 * ranges of it.  Not all text classes have mutable markup or text;
 * see {@link Spannable} for mutable markup and {@link Editable} for
 * mutable text.
 */

再看一下關(guān)于MARK和POINT的注釋

     * MARK and POINT are conceptually located <i>between</i> two adjacent characters.
     * A MARK is "attached" to the character before, while a POINT will stick to the character
     * after. The insertion cursor is conceptually located between the MARK and the POINT.
     *
     * As a result, inserting a new character between a MARK and a POINT will leave the MARK
     * unchanged, while the POINT will be shifted, now located after the inserted character and
     * still glued to the same character after it.
     *
     * Depending on whether the insertion happens at the beginning or the end of a span, the span
     * will hence be expanded to <i>include</i> the new character (when the span is using a MARK at
     * its beginning or a POINT at its end) or it will be <i>excluded</i>.
     *
     * Note that <i>before</i> and <i>after</i> here refer to offsets in the String, which are
     * independent from the visual representation of the text (left-to-right or right-to-left).

不知道大家看懂了沒,反正我是沒看懂.
不過還好,最后在StackOverFlow上找到一個(gè)答案,認(rèn)認(rèn)真真的看了兩個(gè)答案好幾遍,總算是有點(diǎn)明白了.

原文地址:

http://stackoverflow.com/questions/16531555/what-is-the-difference-between-span-point-mark-and-span-mark-point/17846413#17846413

注釋上說,MARK和POINT在兩個(gè)相鄰的字符間,MARK貼在前面一個(gè)字符后面,POINT貼在后面一個(gè)字符前面,然后我們常用的光標(biāo)就是存在于MARK和POINT之間.

StackOverFlow的答案上用]來表示MARK,用[來表示POINT,我覺得是很形象的,開口的方向代表了他依附字符的方向.也就是說]依附在前面一個(gè)字符上,[依附在后面的字符上.

最后我推測,Spanned這個(gè)接口提出了SPAN這個(gè)概念,并且定義了許多類型的SPAN,每個(gè)SPAN都由MARK或SPAN包圍,也就是Spanned接口中得那些靜態(tài)常量:SPAN_MARK_MARK,SPAN_MARK_POINT,SPAN_POINT_MARK,SPAN_POINT_POINT

我再舉幾個(gè)例子,首先SPAN是有長度的,
然后SPAN是不可見的,
然后Spanned這個(gè)接口還提出了Start和End的概念,不同類型的Span,Start和End的位置也不同.
一個(gè)長度為0的SPAN_MARK_MARK相當(dāng)于一個(gè)標(biāo)簽,

//一個(gè)SPAN_MARK_MARK的span
//這個(gè)span的Start和End的位置是這樣的 ]]Start,End
"]]我是例句"
//無論我們是在Start還是在End出輸入內(nèi)容,span店鋪不會(huì)變化
"]]hello,我是例句"

//一個(gè)SPAN_POINT_POINT的span
//這個(gè)span的Start和End的位置是這樣的 Start,End[[
"[[我是例句"
//無論我們是在Start還是在End出輸入內(nèi)容,span店鋪不會(huì)變化
"hello,[[我是例句"

//一個(gè)SPAN_MARK_POINT的span
//這個(gè)span的Start和End的位置是這樣的 ]Start,End[
"][我是例句"
//SPAN_MARK_POINT又叫做SPAN_INCLUSIVE_INCLUSIVE,意思是在Start處和在End處添加內(nèi)容都會(huì)包括在Span中
在Start處輸入 "]hello,[我是例句"
在End處輸入 "]hello,[我是例句" 

//一個(gè)SPAN_POINT_MARK的span
//這個(gè)span的Start和End的位置是這樣的 Start[]End
"[]我是例句"
//SPAN_MARK_POINT又叫做SPAN_EXCLUSIVE_EXCLUSIVE,意思是在Start處和在End處添加內(nèi)容都不會(huì)包括在Span中
"hello,[]我是例句"
"[]hello,我是例句"
"he[]llo,我是例句"

當(dāng)然,目前為止,我剛剛說的都是猜測.

后來發(fā)現(xiàn)SPAN_MARK_MARK,SPAN_MARK_POINT,SPAN_POINT_MARK,SPAN_POINT_POINT這些常量是作為flag來應(yīng)用的,每個(gè)flag都可以設(shè)置成其中任意一種.

接下來我們回來看 Spannable 這個(gè)類,最常用的就是這個(gè)setSpan

Spannable

接著貼注釋...注釋里說這個(gè)方法會(huì)把指定的標(biāo)記對(duì)象貼到startend之間,其中的第四個(gè)參數(shù)flag我前文已經(jīng)解釋的很清楚了.
所以我們接著看第一個(gè)參數(shù)Object what,注釋里說這個(gè)what可以給文字加特技,加功能.

/**
     * Attach the specified markup object to the range <code>start…end</code>
     * of the text, or move the object to that range if it was already
     * attached elsewhere.  See {@link Spanned} for an explanation of
     * what the flags mean.  The object can be one that has meaning only
     * within your application, or it can be one that the text system will
     * use to affect text display or behavior.  Some noteworthy ones are
     * the subclasses of {@link android.text.style.CharacterStyle} and
     * {@link android.text.style.ParagraphStyle}, and
     * {@link android.text.TextWatcher} and
     * {@link android.text.SpanWatcher}.
     */
    public void setSpan(Object what, int start, int end, int flags);

注釋里還說Some noteworthy ones are the subclasses of {@link android.text.style.CharacterStyle} ...于是找到了這個(gè)類CharacterStyle,我們接著看注釋...

/**
 * The classes that affect character-level text formatting extend this
 * class.  Most extend its subclass {@link MetricAffectingSpan}, but simple
 * ones may just implement {@link UpdateAppearance}.
 */

CharacterStyle能夠進(jìn)行字符級(jí)別的格式化的類都集成自這個(gè)類.我們看下繼承樹,可以發(fā)現(xiàn)很多熟悉的Span,比如URLSpan,StyleSpan,ImageSpan,我猜這些就是可選的what吧.

CharacterStyle繼承樹.png

種類繁多,我們先看些碼一些代碼試試效果,那個(gè)鏈接還不能點(diǎn)擊,我們慢慢解決.

span效果圖.png

代碼如下,試了BackgroundColorSpan,StyleSpan,URLSpan,ImageSpan四種,效果還可以

tvSpan = (TextView) findViewById(R.id.tv_span);
SpannableStringBuilder ssb= new SpannableStringBuilder("public static void main \n www.reibang.com");

ssb.setSpan(new BackgroundColorSpan(0x88ff0000),0,6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new StyleSpan(Typeface.BOLD_ITALIC),0,6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new URLSpan("www.reibang.com"),26,41,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

Drawable drawable = getResources().getDrawable(R.drawable.p1);
drawable.setBounds(0,0,tvSpan.getLineHeight(),tvSpan.getLineHeight());
ssb.setSpan(new ImageSpan(drawable,1),7,13,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

tvSpan.setText(ssb);

我們先看BackgroundColorSpan這個(gè)類,看看他的setSpan方法是如何實(shí)現(xiàn)的.注釋上說The flags determine how the span will behave when text is inserted at the start or end of the span's range.說明上面的內(nèi)容還是靠譜的.這個(gè)方法調(diào)用了另外一個(gè)更多參數(shù)的setSpan.

    /**
     * Mark the specified range of text with the specified object.
     * The flags determine how the span will behave when text is
     * inserted at the start or end of the span's range.
     */
    public void setSpan(Object what, int start, int end, int flags) {
        setSpan(true, what, start, end, flags);
    }

setSpan(true, what, start, end, flags);這個(gè)方法100多行,除了吧把span存到mSpans,并沒有找到更多線索,找了一圈,發(fā)現(xiàn)也許和SpanWatcher及他的子類有關(guān)系,看的仔細(xì)的同學(xué)應(yīng)該發(fā)現(xiàn)前面的注釋里有提到過....

唔,這個(gè)接口看起來很像是span的處理者,第二個(gè)參數(shù)就是what,不過,這個(gè)接口是觀察者也說不定...

SpanWatcher.png
/** 
    * When an object of this type is attached to a Spannable, its methods 
    * will be called to notify it that other markup objects have been * added, changed, or removed. */

果然是搞錯(cuò)了,這個(gè)是觀察者...也許如何操縱Span的代碼在TextView里?

打開TextView的源碼,先看看import信息

import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;

好多span相關(guān)的類,看來相應(yīng)的邏輯應(yīng)該在這里了.
but...TextView的源碼有10194行...怎么看啊...

找了半天,在第8061行找到了一個(gè)看起來很關(guān)鍵的方法,但是沒注釋...

 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)

看完發(fā)現(xiàn)這個(gè)方法也不是我要找的....

既然自己找不到,還是去Google查一查
于是找到了這篇文章

http://flavienlaurent.com/blog/2014/01/31/spans/

開篇第一句

When you set text on a TextView, it uses the base class Layout to manage text rendering.

接下來看到這個(gè)類,不過這個(gè)類不在SDK里,而是在android源碼里,也就是AOSP的代碼里,
這個(gè)java文件的路徑是frameworks/base/core/java/android/text/TextLine.java

/**
 * Represents a line of styled text, for measuring in visual order and
 * for rendering.
 *
 * <p>Get a new instance using obtain(), and when finished with it, return it
 * to the pool using recycle().
 *
 * <p>Call set to prepare the instance for use, then either draw, measure,
 * metrics, or caretToLeftRightOf.
 *
 * @hide
 */
class TextLine

已經(jīng)下到源碼了= =\這個(gè)貌似已經(jīng)超出了我的實(shí)力范圍...接著ctrl+V


android.text.TextLine documentation says: Represents a line of styled text, for measuring in visual order and for rendering.

TextLine class contains 3 sets of Spans:

MetricAffectingSpan set
CharacterStyle set
ReplacementSpan set
The interesting method is TextLine#handleRun. It’s where all Spans are used to render the text. Relative to the type of Span, TextLine calls:

CharacterStyle#updateDrawState to change the TextPaint configuration for MetricAffectingSpan and CharacterStyle Spans.
TextLine#handleReplacement for ReplacementSpan. It calls Replacement#getSize to get the replacement width, update the font metrics if it’s needed and finally call Replacement#draw.


更多的Span.png

總結(jié)

最后也沒能把每個(gè)過程都找清楚,這篇文章別看沒什么質(zhì)量,可使卻也是花了我三天,15個(gè)小時(shí)左右的時(shí)間才寫出來的,寫完之后覺得并沒有什么收獲...
但是看到了許多酷酷的東西還是覺得很開心,感覺打開了一扇新的大門,以前認(rèn)為不可能實(shí)現(xiàn)的功能真實(shí)的出現(xiàn)在眼前,感覺好爽.

blog還是要寫的,越是怕文章沒內(nèi)容越是要寫,因?yàn)榕Φ南胍獙懗銎鸫a差強(qiáng)人意的文章,也是多努力了好多,多堅(jiān)持了好久.所以也看到了許多實(shí)戰(zhàn)幾個(gè)月都接觸不到的東西.

最后推薦這篇文章,剛剛沒點(diǎn)開的同學(xué)一定不要錯(cuò)過啊!

http://flavienlaurent.com/blog/2014/01/31/spans/

你看,效果酷不酷!

Fireworks

還可以這樣


animateblur

還有這樣


animatetypewriter
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赠橙,一起剝皮案震驚了整個(gè)濱河市蚊惯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖速兔,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纽窟,居然都是意外死亡填具,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門艰额,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澄港,“玉大人,你說我怎么就攤上這事柄沮』匚啵” “怎么了废岂?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狱意。 經(jīng)常有香客問我湖苞,道長,這世上最難降的妖魔是什么详囤? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任财骨,我火速辦了婚禮,結(jié)果婚禮上藏姐,老公的妹妹穿的比我還像新娘隆箩。我一直安慰自己,他們只是感情好羔杨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布捌臊。 她就那樣靜靜地躺著,像睡著了一般兜材。 火紅的嫁衣襯著肌膚如雪理澎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天曙寡,我揣著相機(jī)與錄音糠爬,去河邊找鬼。 笑死卵皂,一個(gè)胖子當(dāng)著我的面吹牛秩铆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播灯变,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼殴玛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了添祸?” 一聲冷哼從身側(cè)響起滚粟,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刃泌,沒想到半個(gè)月后凡壤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耙替,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年亚侠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俗扇。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硝烂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铜幽,到底是詐尸還是另有隱情滞谢,我是刑警寧澤串稀,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站狮杨,受9級(jí)特大地震影響母截,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜橄教,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一清寇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颤陶,春花似錦颗管、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帽馋。三九已至搅方,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绽族,已是汗流浹背姨涡。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吧慢,地道東北人涛漂。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像检诗,于是被迫代替她去往敵國和親匈仗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理逢慌,服務(wù)發(fā)現(xiàn)悠轩,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 在項(xiàng)目中很常用到Spannable攻泼,既豐富了文本又精簡了布局火架。SpannableString、Spanna...
    尋味Android閱讀 1,362評(píng)論 0 2
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程忙菠,因...
    小菜c閱讀 6,358評(píng)論 0 17
  • 前言 工作找完了何鸡,已經(jīng)干了兩個(gè)星期。雖然經(jīng)常加班牛欢,不過相比之前的工作骡男,現(xiàn)在過得更加充實(shí)、更有意義∏馔铮現(xiàn)在有點(diǎn)空閑時(shí)間...
    帶心情去旅行閱讀 72,252評(píng)論 42 237
  • 【向前一步】 女性獲取成功一般會(huì)比男性更艱難洞翩, 我們應(yīng)該從下面三點(diǎn)去突破稽犁。 1.勇敢點(diǎn) 女性和男性相比總是膽子不夠...
    K王之姐閱讀 100評(píng)論 0 0