谷歌API这样描述 SpannableString 与 SpannableStringBuilder:
This is the class for text whose content and markup can both be changed.
(这是一个内容和标记都可以更改的文本类)
SpannableStringBuilder,SpannableString其实和String一样,都是用来存储字符串,并且多继承自 CharSequence,所以最终多可以通过 setText(CharSequence text)
方法,将字符设置给TextView。
将原有的 String 字符串,通过使用其方法setSpan( )方法实现字符串各种形式风格的显示。比如在 加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉…,这也是与 String 不同的地方。
SpannableString 与 SpannableStringBuilder区别,相信大家首先会想到String 与 StringBuilder的区别。其实类似的,如果传入一个字符串,前者必须一次传入,但后者可以通过append()
方法动态拼接多个SpannableString 。(后面例子会展示)
本文主要通过一个TextView,实现如下功能,并实现局部点击。
官方API 点这里 SpannableStringBuilder
更详细的API使用,可以自行到官方API查看,使用比较简单。这里,主要介绍setSpan( )
的使用,也是最重要的。
在指定的文本范围内,使用指定的 What 类型
参数:
start,end:字符的起始位置,和结束位置,形式是 : [ start , end )
what:是一个CharacterStyle对象,一般常用的有以下几种:
参数 what | 说明 |
---|---|
ForegroundColorSpan | 文本颜色 |
BackgroundColorSpan | 文本背景色 |
ClickableSpan | 点击事件 |
MaskFilterSpan | 修饰效果,如模糊(BlurMaskFilter)浮雕 |
RasterizerSpan | 光栅效果 |
StrikethroughSpan | 删除线 |
SuggestionSpan | 相当于占位符 |
UnderlineSpan | 下划线 |
AbsoluteSizeSpan | 文本字体(绝对大小) |
DynamicDrawableSpan | 设置图片,基于文本基线或底部对齐。 |
ImageSpan | 图片 |
RelativeSizeSpan | 相对大小(文本字体) |
ScaleXSpan | 基于x轴缩放 |
StyleSpan | 字体样式:粗体、斜体等 |
SubscriptSpan | 下标(数学公式会用到) |
SuperscriptSpan | 上标(数学公式会用到) |
TextAppearanceSpan | 文本外貌(包括字体、大小、样式和颜色) |
TypefaceSpan | 文本字体 |
URLSpan | 文本超链接 |
flags:作用域标识
参数 flags | 说明 |
---|---|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | 前后都不包括(在标志位 [start,end)前后添加文字,新添加的文字不会有任何设置的属性) |
Spannable.SPAN_EXCLUSIVE_INCLUSIVE | 前面不包括,后面包括。(在标志位 [start,end)前添加文字,新添加的文字不会有任何设置的属性,后边的添加的文字会带有设置的what属性) |
Spannable.SPAN_INCLUSIVE_EXCLUSIVE | 前面包括,后面不包括。(在标志位 [start,end)后添加文字,新添加的文字不会有任何设置的属性,前边边的添加的文字会带有设置的what属性) |
Spannable.SPAN_INCLUSIVE_INCLUSIVE | 前后都包括。前后都不包括(在标志位 [start,end)前后添加文字,新添加的文字会有设置的属性) |
知道这些参数之后,接下来就简单了,大家可以逐一测试下。
下面,围绕效果图,开始实现。
一般在评论中,需要将用户名的文字使用不同颜色,如下:
SpannableString spannableString;
mSpannableStringBuilder = new SpannableStringBuilder();
spannableString = new SpannableString("小哥哥");
spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)),0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableStringBuilder.append(spannableString);
mSpannableStringBuilder.append("回复");
spannableString = new SpannableString("小姐姐 : ");
spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)),0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableStringBuilder.append(spannableString);
mSpannableStringBuilder.append("在我心中,你最美~");
mContentTv.setText(mSpannableStringBuilder);
首先,添加点击事件,关联东西会稍微复杂一点,这里一步一步介绍。
在评论的中,点击用户名或者回复的信息,应该做不同的处理。首先,给用户名添加点击事件。
final SpannableString spannableString;
mSpannableStringBuilder = new SpannableStringBuilder();
spannableString = new SpannableString("小哥哥");
/**
* 添加监听事件
*/
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), spannableString.toString(),Toast.LENGTH_SHORT).show();
}
},0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
mSpannableStringBuilder.append(spannableString);
mSpannableStringBuilder.append("回复");
final SpannableString spannableString1;
spannableString1 = new SpannableString("小姐姐: ");
/**
* 添加监听事件
*/
spannableString1.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), spannableString1.toString(),Toast.LENGTH_SHORT).show();
}
},0, spannableString1.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
mSpannableStringBuilder.append(spannableString1);
mSpannableStringBuilder.append("在我心中,你最美~");
mContentTv.setText(mSpannableStringBuilder);
然后,运行代码之后,发现不能点击。因为,还需要添加这一行,其作用就是链接响应,简单来说在xml设置:android:autoLink 是一样的。
mContentTv.setMovementMethod(LinkMovementMethod.getInstance());
运行之后就可以了,点击:
这里有 4个问题 需要解决:
1、2 问题 解决:
方法可能会有多种,比如添加点击事件之后,再改变字体颜色。这里说下正常简单的操作是:自定义 ClickableSpan
public abstract class SpannableClickable extends ClickableSpan implements View.OnClickListener {
/**
* text颜色
*/
private int textColor;
public SpannableClickable(Context context) {
this.textColor = context.getResources().getColor(R.color.colorPrimary);
}
public SpannableClickable(int textColor) {
this.textColor = textColor;
}
/**
* 重写该方法
* 1.设置字体颜色,在构造函数可以自定义 传入
* 2.取消下划线
* 3.清除背景图层
*/
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(textColor);
ds.setUnderlineText(false);
// ds.bgColor = bgColor;
ds.clearShadowLayer();
}
}
然后设置这个自定义的监听即可:
spannableString.setSpan(new SpannableClickable(getApplicationContext()) {
@Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), spannableString.toString(),Toast.LENGTH_SHORT).show();
}
},0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
3 ,4 问题解决: :自定义 MovementMethod
首先说下2点需求:
1.改变监听, 一般需求是:点击span部分的时候,整体textView不再响应。
2.改变背景 , 一般需求是,点击的时候,显示背景色,按键起来的时候,恢复原来的背景(透明)。
改变监听,这个会比较好处理,重新修改textView 的onTouch方法就可以。
但是,再修改的过程,发现 在该方法中对span文本的背景色设置没有效果,所以放弃这种。
最后,使用自定义的 MovementMethod,如下:
public class MovementMethod extends BaseMovementMethod {
public final String TAG = MovementMethod.class.getSimpleName();
/**
* 整个textView的背景色
*/
private int textViewBgColor;
/**
* 点击部分文字时部分文字的背景色
*/
private int clickableSpanBgClor;
private BackgroundColorSpan mBgSpan;
private ClickableSpan[] mClickLinks;
/**
* true:响应textview的点击事件, false:响应设置的clickableSpan事件
*/
private boolean isPassToTv = true;
public boolean isPassToTv() {
return isPassToTv;
}
private void setPassToTv(boolean isPassToTv) {
this.isPassToTv = isPassToTv;
}
/**
* @param clickableSpanBgClor 点击选中部分时的背景色
* @param textViewBgColor 整个textView点击时的背景色
*/
public MovementMethod(int clickableSpanBgClor, int textViewBgColor) {
this.textViewBgColor = textViewBgColor;
this.clickableSpanBgClor = clickableSpanBgClor;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
/**
* 这些代码 直接从LinkMovementMethod 拿过来就可以了
*/
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
mClickLinks = buffer.getSpans(off, off, ClickableSpan.class);
if (mClickLinks.length > 0) {
// 点击的是Span区域,不要把点击事件传递
setPassToTv(false);
Selection.setSelection(buffer, buffer.getSpanStart(mClickLinks[0]), buffer.getSpanEnd(mClickLinks[0]));
// 设置点击区域的背景色
mBgSpan = new BackgroundColorSpan(clickableSpanBgClor);
buffer.setSpan(mBgSpan, buffer.getSpanStart(mClickLinks[0]), buffer.getSpanEnd(mClickLinks[0]), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
setPassToTv(true);
// textview选中效果
widget.setBackgroundColor(textViewBgColor);
}
} else if (action == MotionEvent.ACTION_UP) {
if (mClickLinks.length > 0) {
mClickLinks[0].onClick(widget);
if (mBgSpan != null) {// 移除点击时设置的背景span
buffer.removeSpan(mBgSpan);
}
} else {
}
Selection.removeSelection(buffer);
widget.setBackgroundResource(R.color.transparent);
} else if (action == MotionEvent.ACTION_MOVE) {
// 这种情况不用做处理
} else {
if (mBgSpan != null) {// 移除点击时设置的背景span
buffer.removeSpan(mBgSpan);
}
widget.setBackgroundResource(R.color.transparent);
}
return super.onTouchEvent(widget, buffer, event);
}
}
最后:
mContentTv.setMovementMethod(new MovementMethod(getResources().getColor(R.color.gray), getResources().getColor(R.color.gray)));
最后,问题多解决了,哈哈哈。。。。
这说的CharacterStyle对象,就是前面说的What对象。
在使用的时候,可能会有这种情况:一个CharacterStyle对象被使用多次,如下:
String content = "小姐姐回复小哥哥:在我心中,你最帅~";
mSpannableStringBuilder = new SpannableStringBuilder(content);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary));
mSpannableStringBuilder.setSpan(foregroundColorSpan,0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableStringBuilder.setSpan(foregroundColorSpan,5, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
本来是想2个用户名,多想改变成蓝色,但最终结果是:
只有最后一次使用的时候才有效果。如何解决,这里提供2种:
1.那就是再重新 new CharacterStyle对象;(废话!!!)
2.使用CharacterStyle类的静态方法wrap方法:
官方这样说:
public static CharacterStyle wrap (CharacterStyle cs)
A given CharacterStyle can only applied to a single region of a given Spanned. If you need to attach the same CharacterStyle to multiple regions, you can use this method to wrap it with a new object that will have the same effect but be a distinct object so that it can also be attached without conflict.
给定的字符样式只能应用于给定跨距的单个区域。如果需要将相同的CharacterStyle附加到多个区域,可以使用此方法用一个新对象对其进行包装,该对象具有相同的效果,但是是不同的对象,因此也可以不冲突地附加它。
String content = "小姐姐回复小哥哥:在我心中,你最帅~";
mSpannableStringBuilder = new SpannableStringBuilder(content);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary));
mSpannableStringBuilder.setSpan(CharacterStyle.wrap(foregroundColorSpan),0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableStringBuilder.setSpan(CharacterStyle.wrap(foregroundColorSpan),5, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mContent1Tv.setText(mSpannableStringBuilder);
SpannableStringBuilder 可以说是对TextView高级的用法,平时没怎么用,也不知道有这功能。目前项目有评论,于是就了解一下。
后面会写一个项目的实际用法,希望可以尽量完成。
如果议论,欢迎指出,谢谢!看完,麻烦点个赞呀!
参考:
https://developer.android.google.cn/reference/android/text/SpannableString
https://blog.csdn.net/baidu_31956557/article/details/78339071