textview - SpannableString 文本的花式玩法

做 android 的 SpannableString 应该都知道,我想大家基本都用过,但是系统研究过的恐怕就不多了,一般大家都是用到时 baidu,google 一下的,哈哈,我也是这样的啦,这不占着今天有时间好好看一下,从各个方面再次体会一下优秀的 java API 用着是多么让人舒心,也是鞭策一下自己和优秀还有多少距离。

开头


SpannableString 从语义上是文本容器的意思,从效果看我们用 SpannableString 包裹指定文字之后可以是实现很多效果、样式,倒是没白搭起了这个名字,从这点也能再次体会到见名知意的境界

textview 可以接受 SpannableString 类型的文字,那么我们可以把 SpannableString 理解为字符串 String 的扩展类型

        var span10 = SpannableString(tx10.text)
        tx10.setText(span10)

典型的 SpannableString 应用逻辑为:


1. new 一个 SpannableString 对象,并传入初始文字
var span10 = SpannableString(tx10.text)

2. new 一个我们需要的文本效果对象,设置其中参数,这里是文字颜色效果
var backColorSpan = BackgroundColorSpan(Color.RED)

3. 通过 setSpan 方法把文本效果对象设置给 SpannableString  对象,指定起始结速位置
span2.setSpan(backColorSpan, tx01.text.indexOf("-") + 1, tx02.text.length, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)

5. 把 SpannableString  赋值给 textview
tx02.setText(span2)

好了,SpannableString 使用过程很好理解,重点是各种文本的 span 效果对用的写法,和 setSpan() 方法的理解

setSpan() 方法

setSpan() 方法有4个参数很重要

setSpan(Object what, int start, int end, int flags)
  • what - 文本效果
  • start - 文本效果起作用的起始下标
  • end - 结束下表
  • flags - 下标取值范围,有4个类型:
    • Spanned.SPAN_INCLUSIVE_EXCLUSIVE - 包前不包后
    • Spanned.SPAN_INCLUSIVE_INCLUSIVE - 包前也包后
    • Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - 不包前也不包后
    • Spanned.SPAN_EXCLUSIVE_INCLUSIVE - 不包前但包后

关于 flags 我发现 SPAN_INCLUSIVE_INCLUSIVE 不起作用,和 SPAN_INCLUSIVE_EXCLUSIVE 的效果一样,都是包前不包后,所以干脆我就都用 SPAN_INCLUSIVE_EXCLUSIVE 得了

SpannableString 样式详解


我把样式都放在一起了,看着清楚些


Snip20180910_4.png

1. 前景色

什么是前景色,说白了改变文字指定位置颜色,对象类型是 ForegroundColorSpan

// 前景色
var span1 = SpannableString(tx01.text)
var forColorSpan = ForegroundColorSpan(Color.BLUE)
span1.setSpan(forColorSpan, tx01.text.indexOf("-") + 1, tx01.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx01.setText(span1)

2. 背景色

注意背景色和前景色的区别,前景色改的是文字颜色,背景色改的是背景颜色,不会改文字颜色的,队形类型是 BackgroundColorSpan

// 背景色
var span2 = SpannableString(tx02.text)
var backColorSpan = BackgroundColorSpan(Color.RED)
span2.setSpan(backColorSpan, tx01.text.indexOf("-") + 1, tx02.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx02.setText(span2)

这里有个自定义 BackgroundColorSpan 来实现效果的例子

3. 修改文字大小,使用相对数值

什么是相对,就是指定位置的文字是其他文字的几倍,比如例子中的 1.6F,注意可以改大也可以改小,对象类型是 RelativeSizeSpan

// 相对文字大小
var span3 = SpannableString(tx03.text)
var relaSzieSpan = RelativeSizeSpan(1.6F)
span3.setSpan(relaSzieSpan, tx03.text.indexOf("-") + 1, tx03.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx03.setText(span3)

4. 修改文字大小,使用绝对数值

绝对数值就是我们不再指定文字大小倍数了,而是用具体的数了,比如 26sp,当然我们需要转换单位,把 sp 转成 int,对象类型是 AbsoluteSizeSpan

// 绝对文字大小
var span31 = SpannableString(tx031.text)
val value: Int = (this@SpannableStringActivity.resources.displayMetrics.scaledDensity * 26 + 0.5f).toInt()
var absSizeSpan = AbsoluteSizeSpan(value)
span31.setSpan(absSizeSpan, tx031.text.indexOf("-") + 1, tx031.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx031.setText(span31)

5. 删除线

删除线也叫中划线,没什么特别的,对象类型是 StrikethroughSpan,就是这么名字真特么难记......

// 删除线
var span4 = SpannableString(tx04.text)
var strSpan = StrikethroughSpan()
span4.setSpan(strSpan, tx04.text.indexOf("-") + 1, tx04.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx04.setText(span4)

6. 下划线

下划线是很常见的需求了,对象类型是 UnderlineSpan,这个名字就比中划线的好记

// 下划线
var span5 = SpannableString(tx05.text)
var underSpan = UnderlineSpan()
span5.setSpan(underSpan, tx05.text.indexOf("-") + 1, tx05.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx05.setText(span5)

7. 上标

SpannableString 支持设置多个 span ,所以这里相应的缩小了上标的大小,对象类型是 SuperscriptSpan,又是一个不好记的名字,蛋疼......

// 上标
var span6 = SpannableString(tx06.text)
var supSpan = SuperscriptSpan()
span6.setSpan(supSpan, tx06.text.indexOf("-") + 1, tx06.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
var sizeSpan6 = RelativeSizeSpan(0.5f)
span6.setSpan(sizeSpan6, tx06.text.indexOf("-") + 1, tx06.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx06.setText(span6)

8. 上标

这里我遇到问题了,下标显示不全的问题,我就是缩小了下表相应的大小也没用,不知道为啥,好在平时下标也用不到,真要是碰到了,我缩小字体大小代替下,对象类型是 SubscriptSpan,继续吐槽,不说我不舒服斯基,上标下标,这2单词怎么这么难记

// 下标
var span7 = SpannableString(tx07.text)
var subSpan = SubscriptSpan()
span7.setSpan(subSpan, tx07.text.indexOf("-") + 1, tx07.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx07.setText(span7)

9. 斜体、粗体

这个没啥说的了,Typeface 里还有一个斜粗体效果,对象类型是 StyleSpan

// 斜体、粗体
var span8 = SpannableString(tx08.text)
var stypeSpan1 = StyleSpan(Typeface.BOLD)
var stypeSpan2 = StyleSpan(Typeface.ITALIC)
span8.setSpan(stypeSpan1, tx08.text.indexOf("-") + 1, tx08.text.indexOf("、"),SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
span8.setSpan(stypeSpan2, tx08.text.indexOf("、") + 1, tx08.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx08.setText(span8)

10. 图片

图片这里要重点说下,和我们预想的一样,图片不是插入进来的,和 textview 有本质区别。SpannableString 的图片是替代的指定位置,指定个数的问题,图片的宽要是超过替代文字的宽度,那么图片就会遮挡后面文字,所以这里必须注意,例子里面我临时插入了 4个k,才把图片显示全的

还有一点,不论图片资源是 vector,还是 drawable ,必须显式的指定图片大小才行,要不 SpannableString 显示不出图片来

// 图片
var value9 = StringBuilder(tx09.text)
val drawable = resources.getDrawable(R.mipmap.ic_launcher)
drawable.setBounds(0, 0, 80, 80)
var imageSpan = ImageSpan(drawable)

value9.insert(value9.indexOf("-") + 1, "kkkk")
var span9 = SpannableString(value9)
span9.setSpan(imageSpan, value9.indexOf("k"), value9.indexOf("k") + 4,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx09.setText(span9)

对于大图,我们会面临图片居中的问题,ImageSpan 是文字基线对齐的,所以要让图片居中我们需要继承 ImageSpan 自己去绘制图片实现居中效果,具体看:

android 传统实现自定义 Emoji 表情的方法:

11. 可点击区域

可点击区域默认带下划线,使用系统主题颜色。思路是让文本关联一个点击事件对象,注意必须写 setMovementMethod() 方法,要不不起作用

// 可点击区域
var span10 = SpannableString(tx10.text)
span10.setSpan(MyClickSpan(), tx10.text.indexOf("-") + 1, tx10.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx10.setMovementMethod(LinkMovementMethod.getInstance())
tx10.setText(span10)

    inner class MyClickSpan : ClickableSpan() {

        override fun updateDrawState(ds: TextPaint) {
            // 不显示下划线
            ds.isUnderlineText = false
        }

        override fun onClick(widget: View?) {
            var intent: Intent = Intent(this@SpannableStringActivity, ClickSpanActivity::class.java)
            this@SpannableStringActivity.startActivity(intent)
        }

    }

找到一篇使用正则找电话,改颜色,加拨打电话的例子,挺好的:

12. 超链接

看过上面的可点击区域,这个超链接就能理解了吧,注意 http:// 协议必须写啊,URLSpan 使用的系统自带的浏览器

// 超链接
var span11 = SpannableString(tx11.text)
var urlSpan = URLSpan("http://www.sina.com")
span11.setSpan(urlSpan, tx11.text.indexOf("-") + 1, tx11.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx11.setMovementMethod(LinkMovementMethod.getInstance())
tx11.setText(span11)

13 文字缩进

这个上面的图没有,大家看下面的图。这里用到的是 LeadingMarginSpan.Standard 这个类

SpannableString spannableString = new SpannableString("文字内容");
// 0 第一行缩进像素,30 非第一行缩进像素
LeadingMarginSpan.Standard span= new LeadingMarginSpan.Standard(0, 30);
spannableString.setSpan(span, 0, spannableString.length(),SpannableString.SPAN_INCLUSIVE_INCLUSIVE);
textview.setText(spannableString);

14 颜色选择器

Textview 上的颜色选择器我们不仅可以设置背景色的,文字颜色一样可以设置,这个就是:ColorStateList

4625917-0f99eec051983179.png
<?xml version="1.0" encoding="utf-8"?>
<selector 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:enterFadeDuration="300" 
    android:exitFadeDuration="300">

    <item android:state_pressed="true" android:color="#ffffff"/>
    <item android:color="#000000"/>
</selector>
ColorStateList csl = null;
try {
    =XmlResourceParser xrp = getResources().getXml(R.drawable.button_text);
    csl = ColorStateList.createFromXml(getResources(), xrp);
} catch (XmlPullParserException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
btn.setTextColor(csl);

参考资料:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357