最近项目中需要实现一个文章跟读效果的显示,还要能够点击文章中的单词能够弹出对话框显示单词的英美发音,那么如何实现这样的需求呢?当然是利用SpannableString啦,下面就结合项目中使用到的和参考其他博客的成果,整理一下常用的用法吧。
SpannableString其实很多方法和属性与String类似,只不过它比普通的字符串多了能够带有一些富文本属性,比如显示不同颜色、带下划线删除线等等。有心的读者一定想到了那是否有和StringBuilder对应的呢?答案是肯定的,那就是SpannableStringBuilder。显示富文本最重要的一个方法就是
setSpan(Object what, int start, int end, int flags)方法需要用户输入四个参数,what表示设置的格式是什么,可以是前景色、背景色也可以是可点击的文本等等,显示和交互的效果就由此决定;start表示需要设置格式的子字符串的起始下标(包括),同理end表示终了下标(不包括),flags属性共四种:
Spanned.SPAN_INCLUSIVE_EXCLUSIVE 前面应用,后面不应用(即在文本前插入新的文本会应用该样式,而在文本后插入新文本不会应用该样式)
Spanned.SPAN_INCLUSIVE_INCLUSIVE 前面应用,后面也应用
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 前后都不应用
Spanned.SPAN_EXCLUSIVE_INCLUSIVE 前面不应用,后面应用
1.ForegroundColorSpan,为文本设置前景色,效果和setTextColor()类似
SpannableString spannableString = new SpannableString("前景色为蓝色");
spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#0000FF")), 0 , spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
2.BackgroundColorSpan,为文本设置背景色,效果和TextView的setBackground()类似
new BackgroundColorSpan(Color.parseColor("#0000FF"))
3.StrikethroughSpan,为文本设置中划线,也就是常说的删除线
new StrikethroughSpan()
4.UnderlineSpan,为文本设置下划线,但线的大小和颜色能否设置呢?我没找到具体的方法,还请知道的小伙伴指点一二。
new UnderlineSpan()
5.SuperscriptSpan,设置上标,即类似数学公式中或标注的上标效果
new SuperscriptSpan()
但这样直接设会使得上标的文字大小和正常文本大小一样,如果要使上标文字稍微小一点怎么办呢?这就需要下面即将讲到的 RelativeSizeSpan实现相对字体大小。
6.SubscriptSpan,设置下标,功能与设置上标类似
new SubscriptSpan()
7.StyleSpan, 设置文字的显示风格(粗体、斜体),和TextView属性textStyle类似
new StyleSpan(Typeface.ITALIC) //设置斜体
new StyleSpan(Typeface.BOLD) //设置粗体
8.RelativeSizeSpan,设置文字相对大小,在TextView原有的文字大小的基础上,相对设置文字大小
new RelativeSizeSpan(1.2f) //相对正常大小的1.2倍
9.ImageSpan,设置文本图片。没错,我们可以利用这个属性在一段文字中的任何位置插入图片,是不是想想都激动哈哈
SpannableString spannableString = new SpannableString("在文本中添加表情(表情)");
Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
ImageSpan imageSpan = new ImageSpan(drawable);
spannableString.setSpan(imageSpan, 6, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv_text4.setText(spannableString);
10.ClickableSpan,设置可点击的文本,设置这个属性的文本可以相应用户点击事件,至于点击事件用户可以自定义。这样就可以实现一段文字中,设置某些特定的文字能够点击,就像一开始说到的点击弹出对话框那样的效果了。
SpannableString spannableString = new SpannableString("为文字设置点击事件");
MyClickableSpan clickableSpan = new MyClickableSpan("点击了");
spannableString.setSpan(clickableSpan, 5, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setMovementMethod(LinkMovementMethod.getInstance()); //点击事件才能起效
textView.setHighlightColor(Color.parseColor("#36969696")); //点击背景色,默认淡蓝色
textView.setText(spannableString);
class MyClickableSpan extends ClickableSpan {
private String content;
public MyClickableSpan(String content) {
this.content = content;
}
@Override
public void updateDrawState(TextPaint ds) { //设置显示样式
ds.setUnderlineText(false); //不要默认下划线
}
@Override
public void onClick(View widget) { //点击事件的响应方法
Toast.makeText(getApplicationContext(),"点击事件"+content, Toast.LENGTH_SHORT).show();
}
}
11.URLSpan,设置超链接文本,能够调起系统自带的浏览器、邮件、电话等。其实ClickableSpan的也能实现超链接文本的效果,重写onClick点击事件就行,URLSpan内部就是这样实现的。
SpannableString ss5 = new SpannableString("超链接to百度");
ss5.setSpan( new URLSpan("http://www.baidu.com"), 0 ,ss5.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv_text5.setMovementMethod(LinkMovementMethod.getInstance());
tv_text5.setHighlightColor(Color.parseColor("#36969696"));
tv_text5.setText(ss5);
1.简化需求
我们需要实现这样的功能:一个TextView中,一部分文字设置了富文本显示,一部分还是普通的文字,点击设置了富文本的文字弹出相应对话框,二点击普通文字弹出另外相应的对话框。
2.问题
当点击富文本文字时,普通文本的点击事件也得到了相应。如下例子中,点击”富文本文字”时,先后弹出“点击富文本文字”和“点击普通文字”
SpannableString ss2 = new SpannableString("普通文字,富文本文字");
ss2.setSpan(new MaskFilterSpan(new MaskFilter()), 0 ,2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss2.setSpan(new RelativeSizeSpan(2.0f), 5,
10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss2.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
//super.updateDrawState(ds);
ds.setUnderlineText(false);
}
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(),"点击富文本文字", Toast.LENGTH_SHORT).show();
}
}, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv_text2.setMovementMethod(MyTextView.CustomLinkMovementMethod.getInstance());
tv_text2.setHighlightColor(Color.TRANSPARENT); //背景色透明
tv_text2.setText(ss2);
tv_text2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(),"点击普通文字", Toast.LENGTH_SHORT).show();
}
});
但我们实际上是要点击富文本文字时,不要弹出“点击普通文字”的响应事件的。那么这个问题如何解决呢?马上想到的当然是判断点击了富文本文字时拦截掉点击事件啦。这就需要我们自定义一个MyTextView继承TextView重写performClick()、onTouchEvent(MotionEvent event)还有在LinkMovementMethod的自定义子类中重写onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event)
如下:
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public boolean linkHit;//内部富文本是否被点击
@Override
public boolean performClick() { //最后响应3
if (linkHit) {
return true; //这样textView的OnClick事件不会响应
}
return super.performClick(); //调用监听的onClick方法
}
@Override
public boolean onTouchEvent(MotionEvent event) { //textView的OnClick事件响应,首先响应1
linkHit = false;
return super.onTouchEvent(event);
}
public static class CustomLinkMovementMethod extends LinkMovementMethod { //次之2
static CustomLinkMovementMethod sInstance;
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
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); //水平偏移,第几个字
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget); //ClickableSpan的onClick
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
if (widget instanceof MyTextView) {
((MyTextView) widget).linkHit = true; //点了标记的文字
}
return true;
} else {
Selection.removeSelection(buffer);
super.onTouchEvent(widget, buffer, event);
return false;
}
}
return Touch.onTouchEvent(widget, buffer, event);
}
public static CustomLinkMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new CustomLinkMovementMethod();
}
return sInstance;
}
}
}
参考链接:https://www.jianshu.com/p/84067ad289d2