TextView+SpannableString實現Android中富文字的顯示及點選衝突解決
前言
最近專案中需要實現一個文章跟讀效果的顯示,還要能夠點選文章中的單詞能夠彈出對話方塊顯示單詞的英美髮音,那麼如何實現這樣的需求呢?當然是利用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);
clickableSpan和TextView的點選事件衝突
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;
}
}
}