最近在研究android中emoji的显示问题,突然对spannable,特别好奇.
查了一圈资料发现spannable是Android中的一大杀器啊
本文基本上是类似直播的形式,全文大量的粘贴注释...
Spannable是一个接口,我们先来看看它的继承树.
然后是Spannable,这个接口里面居然还有个类,是一个单例类.
仔细看了一下,原来Spannable也不是顶层接口...CharSequence才是...
我们先看一下CharSequence,居然是java.lang包下的,String是它的实现类...以前学java的时候都没见过...注释上说这个接口代表了一个有序的字符集合,而且定义了几个方法来操作这个字符集合.
package java.lang;
/**
* This interface represents an ordered set of characters and defines the
* methods to probe them.
*/
public interface CharSequence
里面有这几个方法.
接下来我们看一下Spanned接口,发现有一大堆MARK,POINT之类的常量.
看一下注释
/**
* 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.
*/
再看一下关于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上找到一个答案,认认真真的看了两个答案好几遍,总算是有点明白了.
原文地址:
注释上说,MARK和POINT在两个相邻的字符间,MARK贴在前面一个字符后面,POINT贴在后面一个字符前面,然后我们常用的光标就是存在于MARK和POINT之间.
StackOverFlow的答案上用]
来表示MARK,用[
来表示POINT,我觉得是很形象的,开口的方向代表了他依附字符的方向.也就是说]
依附在前面一个字符上,[
依附在后面的字符上.
最后我推测,Spanned这个接口提出了SPAN这个概念,并且定义了许多类型的SPAN,每个SPAN都由MARK或SPAN包围,也就是Spanned接口中得那些静态常量:SPAN_MARK_MARK
,SPAN_MARK_POINT
,SPAN_POINT_MARK
,SPAN_POINT_POINT
我再举几个例子,首先SPAN是有长度的,
然后SPAN是不可见的,
然后Spanned这个接口还提出了Start和End的概念,不同类型的Span,Start和End的位置也不同.
一个长度为0的SPAN_MARK_MARK
相当于一个标签,
//一个SPAN_MARK_MARK的span
//这个span的Start和End的位置是这样的 ]]Start,End
"]]我是例句"
//无论我们是在Start还是在End出输入内容,span店铺不会变化
"]]hello,我是例句"
//一个SPAN_POINT_POINT的span
//这个span的Start和End的位置是这样的 Start,End[[
"[[我是例句"
//无论我们是在Start还是在End出输入内容,span店铺不会变化
"hello,[[我是例句"
//一个SPAN_MARK_POINT的span
//这个span的Start和End的位置是这样的 ]Start,End[
"][我是例句"
//SPAN_MARK_POINT又叫做SPAN_INCLUSIVE_INCLUSIVE,意思是在Start处和在End处添加内容都会包括在Span中
在Start处输入 "]hello,[我是例句"
在End处输入 "]hello,[我是例句"
//一个SPAN_POINT_MARK的span
//这个span的Start和End的位置是这样的 Start[]End
"[]我是例句"
//SPAN_MARK_POINT又叫做SPAN_EXCLUSIVE_EXCLUSIVE,意思是在Start处和在End处添加内容都不会包括在Span中
"hello,[]我是例句"
"[]hello,我是例句"
"he[]llo,我是例句"
当然,目前为止,我刚刚说的都是猜测.
后来发现SPAN_MARK_MARK
,SPAN_MARK_POINT
,SPAN_POINT_MARK
,SPAN_POINT_POINT
这些常量是作为flag来应用的,每个flag都可以设置成其中任意一种.
接下来我们回来看 Spannable
这个类,最常用的就是这个setSpan
了
接着贴注释...注释里说这个方法会把指定的标记对象贴到start
和end
之间,其中的第四个参数flag
我前文已经解释的很清楚了.
所以我们接着看第一个参数Object what
,注释里说这个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} ...
于是找到了这个类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
能够进行字符级别的格式化的类都集成自这个类.我们看下继承树,可以发现很多熟悉的Span,比如URLSpan
,StyleSpan
,ImageSpan
,我猜这些就是可选的what
吧.
种类繁多,我们先看些码一些代码试试效果,那个链接还不能点击,我们慢慢解决.
代码如下,试了BackgroundColorSpan
,StyleSpan
,URLSpan
,ImageSpan
四种,效果还可以
tvSpan = (TextView) findViewById(R.id.tv_span);
SpannableStringBuilder ssb= new SpannableStringBuilder("public static void main \n www.jianshu.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.jianshu.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这个类,看看他的setSpan方法是如何实现的.注释上说The flags determine how the span will behave when text is inserted at the start or end of the span's range.说明上面的内容还是靠谱的.这个方法调用了另外一个更多参数的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);
这个方法100多行,除了吧把span存到mSpans
,并没有找到更多线索,找了一圈,发现也许和SpanWatcher
及他的子类有关系,看的仔细的同学应该发现前面的注释里有提到过....
唔,这个接口看起来很像是span的处理者,第二个参数就是what
,不过,这个接口是观察者也说不定...
/**
* 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. */
果然是搞错了,这个是观察者...也许如何操纵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相关的类,看来相应的逻辑应该在这里了.
but...TextView的源码有10194行...怎么看啊...
找了半天,在第8061
行找到了一个看起来很关键的方法,但是没注释...
void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)
看完发现这个方法也不是我要找的....
既然自己找不到,还是去Google查一查
于是找到了这篇文章
开篇第一句
When you set text on a TextView, it uses the base class Layout to manage text rendering.
接下来看到这个类,不过这个类不在SDK里,而是在android源码里,也就是AOSP的代码里,
这个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
已经下到源码了= =\这个貌似已经超出了我的实力范围...接着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.
总结
最后也没能把每个过程都找清楚,这篇文章别看没什么质量,可使却也是花了我三天,15个小时左右的时间才写出来的,写完之后觉得并没有什么收获...
但是看到了许多酷酷的东西还是觉得很开心,感觉打开了一扇新的大门,以前认为不可能实现的功能真实的出现在眼前,感觉好爽.
blog还是要写的,越是怕文章没内容越是要写,因为努力的想要写出起码差强人意的文章,也是多努力了好多,多坚持了好久.所以也看到了许多实战几个月都接触不到的东西.
最后推荐这篇文章,刚刚没点开的同学一定不要错过啊!
你看,效果酷不酷!
还可以这样
还有这样