Android中各种Span的用法

在安卓开发中,我们经常会遇到给一个TextView中的部分文字设置不同的样式的情况,这之后我们不可能总是用好几个TextView来拼出这样一个效果,此时,我们就需要用到'''SpannableString'''以及和各种'''Span'''的用法了,下面就让我们看一下Android给我们提供了多少种Span,以及各自的用法:

SpannableString

首先来介绍SpannableString,官方解释是:这是内容不可变但可以附加和分离标记对象的文本的类。所谓的标记对象就是我们接下来要介绍的Span。对于他的用法:

SpannableString string = new SpannableString(str);
//设置TextView,可以被当做字符串设置给TextView
textView.setText(string);

各种Span

  • BackgroundColorSpan:给部分文字设置背景颜色
  • ForegroundColorSpan:给部分文字设置前景色
  • ClickableSpan:设置点击事件
  • URLSpan:设置链接,相当于Html的标签
  • MaskFilterSpan:文字的装饰效果。分为两种:BlurMaskFilter(模糊效果) 和 EmbossMaskFilter (浮雕效果)
  • AbsoluteSizeSpan:设置字体大小
  • RelativeSizeSpan:设置字体的相对大小
  • ImageSpan:设置图片
  • ScaleXSpan:横向压缩
  • SubscriptSpan:设置脚注
  • SuperscriptSpan:上标,相当于数学中的平方样式
  • TextAppearanceSpan:使用style来定义文本样式
  • TypefaceSpan:设置字体
  • RasterizerSpan:设置光栅字样
  • StrikethroughSpan:删除线,相当于购物网站上的划掉的原价
  • UnderlineSpan:下划线。

以上都是Android源码提供的效果,下面来大概写一下用法,其实用法基本上都是一样的:

String str = "大家好,下面来演示一下Span的用法";
SpannableString string = new SpannableString(str);
BackgroundColorSpan span = new BackgroundColorSpan(getResources().getColor(R.color.red));
//最后一句就是给String设置效果的
//第2个参数表示从哪个字符开始设置背景色
//第3个参数表示到哪个字符结束背景色的设置
//最后一个参数表示是否包含所设置的第二、三参数所代表的位置
//本例中设置的是前后都不包括
//相同的还有:
//Spanned.SPAN_INCLUSIVE_EXCLUSIVE(前面包括,后面不包括)、
//Spanned.SPAN_EXCLUSIVE_INCLUSIVE(前面不包括,后面包括)、
//Spanned.SPAN_INCLUSIVE_INCLUSIVE(前后都包括)
string.setSpan(span,0,3,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

其实看着android的文档,或者上网上搜这些东西会有很多,大家可以跟着敲一遍代码,运行一下就能清楚地知道每个Span所代表的含义了。下面进入主题:

公司最近要做一个练习的需求,就相当于学生在App中做试卷似得,于是就有了下面的这个设计图:


QQ20170115-0@2x.png

大概就是这种样式的图片,一段文字里面会嵌入很多的空格,并且还有背景色,还有文字,而且点击的时候还有各种效果,为了实现这个功能,确实看了很多Span的样式。

刚开始的时候准备用BackgroundColorSpan+ClickableSpan来实现这样的功能,后来发现,BackgroundColorSpan不能完美的实现这个功能,于是在网上找了BackgroundImageSpan来实现的。

public class BackgroundImageSpan extends ReplacementSpan implements ParcelableSpan {
  private static final String TAG = "BackgroundImageSpan";
  private Drawable mDrawable;
  private int mImageId;
  private int mWidth = -1;
 /**
 * new BackgroundImageSpan use resource id and Drawable
 * @param id the drawable resource id
 * @param drawable Drawable related to the id
 * @internal
 * @hide
 */
 public BackgroundImageSpan(int id, Drawable drawable) {
   mImageId = id;
   mDrawable = drawable;
 }
 /**
 * @hide
 * @internal
 */
 public BackgroundImageSpan(Parcel src) {
   mImageId = src.readInt();
 }
 /**
 * @hide
 * @internal
 */
 public void draw(Canvas canvas, int width,float x,int top, int y, int bottom, Paint paint) {
  if (mDrawable == null) {//if no backgroundImage just don't do any draw
    Log.e(TAG, "mDrawable is null draw()");
    return;
 }
   Drawable drawable = mDrawable;
   canvas.save();
   canvas.translate(x, top); // translate to the left top point
   mDrawable.setBounds(0, 0, width, (bottom - top));
   drawable.draw(canvas);
   canvas.restore();
 }
 @Override
 public void updateDrawState(TextPaint tp) {
 }
 /**
 * return a special type identifier for this span class
 * @hide
 * @internal
 * @Override
 */
 public int getSpanTypeId() {
    return 0;
 }
 /**
 * describe the kinds of special objects contained in this Parcelable's marshalled representation
 * @hide
 * @internal
 * @Override
 */
 public int describeContents() {
   return 0;
 }
 /**
 * flatten this object in to a Parcel
 * @hide
 * @internal
 * @Override
 */
 public void writeToParcel(Parcel dest, int flags) {
   dest.writeInt(mImageId);
 }
 /**
 * @hide
 * @internal
 */
 public void convertToDrawable(Context context) {
   if (mDrawable == null) {
     mDrawable = context.getResources().getDrawable(mImageId);
   }
 }
 /**
 * convert a style text that contain BackgroundImageSpan, Parcek only pass resource id,
 * after Parcel, we need to convert resource id to Drawable.
 * @hide
 * @internal
 */
 public static void convert(CharSequence text , Context context) {
   if (!(text instanceof SpannableStringBuilder)) {
     return;
   }
   SpannableStringBuilder builder = (SpannableStringBuilder)text;
   BackgroundImageSpan[] spans = builder.getSpans(0, text.length(),     BackgroundImageSpan.class);
   if (spans == null || spans.length == 0) {
      return;
   }
   for (int i = 0; i < spans.length; i++) {
     spans[i].convertToDrawable(context);
   }
 }
 /**
 * draw the span
 * @hide
 * @internal
 * @Override
 */
 public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
   // draw image
   draw(canvas, mWidth,x,top, y, bottom, paint);
   // draw text
   // the paint is already updated
   canvas.drawText(text,start,end, x,y, paint);
 }
 /**
 * get size of the span
 * @hide
 * @internal
 * @Override
 */
 public int getSize(Paint paint, CharSequence text, int start, int end,
 FontMetricsInt fm) {
   float size = paint.measureText(text, start, end);
   if (fm != null && paint != null) {
     paint.getFontMetricsInt(fm);
   }
   mWidth = (int)size;
   return mWidth;
 }
}

以上这段代码是谷歌的Git上提供的,最后会放上链接,接着说一下我实现上图的过程,上代码比较快:

//前两行使用这则表达式来解析出一大段文本里面需要特殊设置的文本内容
Matcher textMatcher;
SpannableString str = new SpannableString(contentStr);
textMatcher = PATTERN_TEXT_SRC.matcher(content);
while (textMatcher.find()) {  
//然后开始循环所有的文本来进行设置。  
    final String blank = textMatcher.group().trim();
    int index = content.indexOf(blank);
    ClickableSpan clickableSpan = new MyClickableSpan();    
    BackgroundImageSpan backgroundColorSpan;
    //此处之所以有这个判断,完全是为了用来设置点击时候的样式,而currentStart和currentEnd也是点击的文字的第一字符的position和最后一个字符的position
    if (currentStart == index && currentEnd == index + blank.length()) {        
       backgroundColorSpan = new BackgroundImageSpan(R.drawable.bg_answer_wrong, getResources().getDrawable(R.drawable.bg_answer_wrong));
    } else { 
       backgroundColorSpan = new BackgroundImageSpan(R.drawable.bg_noanswer_unselected, getResources().getDrawable(R.drawable.bg_noanswer_unselected));    
}
    str.setSpan(backgroundColorSpan, index, index + blank.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);      
    str.setSpan(clickableSpan, index, index + blank.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);}
    //如果要设置点击事件,那么久一低昂要设置下面这一行,否则点击事件不起作用
    mTextView.setMovementMethod(LinkMovementMethod.getInstance());
    mTextView.setText(str);

//此处是定义的ClickableSpan
private class MyClickableSpan extends ClickableSpan {
    @Override
    public void onClick(View widget) {
        //这里面的代码主要是用来获取所点击的那一部分的text,也可以根据下面的方法来获取具体点击的那部分文字内容
        TextView tv = (TextView) widget;
        Spanned s = (Spanned) tv.getText();
        int start = s.getSpanStart(this);
        int end = s.getSpanEnd(this); 
        currentStart = start;
        currentEnd = end;
        updateBlank();
        Toast.makeText(MainActivity.this, tv.getText(), Toast.LENGTH_SHORT).show();    }}

基本上就是这些内容了,其实做起来不太难,主要是各种状态判断起来比较崩溃,下面一些就是收集个各种需要的效果:

给所设置的部分内容添加padding

BackgroundImageSpan

Clickable获取点击的文本内容

ClickableSpan的一些样式的设置

基本上就是以上这么多了,谢谢这些链接的文章作者所提供的优质内容,权当记录了吧。

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

推荐阅读更多精彩内容

  • 前言 工作找完了,已经干了两个星期。虽然经常加班,不过相比之前的工作,现在过得更加充实、更有意义。现在有点空闲时间...
    带心情去旅行阅读 71,892评论 42 237
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • CSS基础 本文包括CSS基础知识选择器(重要!!!)继承、特殊性、层叠、重要性CSS格式化排版单位和值盒模型浮动...
    廖少少阅读 3,043评论 0 40
  • 选择qi:是表达式 标签选择器 类选择器 属性选择器 继承属性: color,font,text-align,li...
    wzhiq896阅读 1,722评论 0 2
  • 两天的出差,有些累累了。突然的降温,外面冷,车里热,温度差总会让人身体起变化。天已黑看不出车窗外的景色,只有些琐碎...
    WoodSage阅读 149评论 2 1