用過Textview設定過字型顔色、大小和emoji之類的同學都知道,要給文字實作豐富多彩的效果就得用到SpannableString或SpannableStringBuilder,這裡要實作的效果還是要用到SpannableStringBuilder。
先看效果圖
下面我來說說如何使用SpannableStringBuilder來實作這個效果先看源碼(比對使用者和話題使用的是正規表達式)
static class Section {
int start;
int end;
Section(int start, int end) {
this.start = start;
this.end = end;
}
}
定義一個結構儲存要改變文字的起始位置。
使用list來儲存所有比對成功的字段。
final ArrayList<Section> sections = new ArrayList<>();
int start = matcher.start(1);
int end = start + at.length();
Section section = new Section(start, end);
sections.add(section);
在MotionEvent.ACTION_DOWN這裡擷取點選點在字元串中的位置index,這裡使用textview的layout來擷取。
int action = event.getAction();
Layout layout = textView.getLayout();
int line = 0;
int index = 0;
switch(action) {
case MotionEvent.ACTION_DOWN:
line = layout.getLineForVertical(textView.getScrollY()+ (int)event.getY());
index = layout.getOffsetForHorizontal(line, (int)event.getX());
然後判斷是否在比對的字段裡面,在的話設定它背景色
final BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);
for (Section section : sections) {
if ( index>=section.start && index <= section.end) {
spannableStringBuilder.setSpan(span,section.start,section.end,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
downSection = section;
textView.setText(spannableStringBuilder);
downX = (int) event.getX();
downY = (int) event.getY();
break;
}
}
然後再MotionEvent.ACTION_UP這裡取消設定的背景色
case MotionEvent.ACTION_UP:
spannableStringBuilder.removeSpan(span);
textView.setText(spannableStringBuilder);
這樣差不多就已經可以了,但是還有一些細節要處理,如果點選以後滑動了怎麼辦?當然也要取消背景了。
那麼我們又得在滑動這裡
final int slop = ViewConfiguration.get(context).getScaledTouchSlop();
case MotionEvent.ACTION_MOVE:
int currentX = (int) event.getX();
int currentY = (int) event.getY();
if (Math.abs(currentX-downX) < slop && Math.abs(currentY-downY) < slop) {
break;
}
case MotionEvent.ACTION_UP:
這裡slop為最小滑動距離判斷,從系統獲得,downX和downY在上面按下時擷取。
在這裡很容易看到,如果小于slop距離就什麼也不做,如果大于的話就跳到MotionEvent.ACTION_UP下面的代碼,然後就在那裡取消背景。
最後,在這裡實作你要的操作,startActivity或者其它。
int upX = (int) event.getX();
int upY = (int) event.getY();
if (Math.abs(upX-downX) < slop && Math.abs(upY-downY) < slop) {
//TODO startActivity or whatever
if (downSection != null) {
String name = source.substring(downSection.start,downSection.end);
Toast.makeText(context,name,Toast.LENGTH_SHORT).show();
}
}
update:
2016.08.10 :修複了嵌入listview,RecyclerView點選滑動後背景不消失bug,這是因為listview處理攔截了move事件,是以裡面的textview接收不到move事件,是以背景一直沒有取消,在這兩場添加代碼
case MotionEvent.ACTION_DOWN:
line = layout.getLineForVertical(textView.getScrollY()+ (int)event.getY());
index = layout.getOffsetForHorizontal(line, (int)event.getX());
Log.d(TAG," index:"+ index+",sections:"+sections.size());
for (Section section : sections) {
if ( index>=section.start && index <= section.end) {
spannableStringBuilder.setSpan(span,section.start,section.end,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
downSection = section;
textView.setText(spannableStringBuilder);
textView.getParent().requestDisallowInterceptTouchEvent(true);//不允許父view攔截
downX = (int) event.getX();
downY = (int) event.getY();
break;
}
}
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int) event.getX();
int currentY = (int) event.getY();
Log.d(TAG, "ACTION_MOVE,x:"+currentX+",y:"+currentY);
if (Math.abs(currentX-downX) < slop && Math.abs(currentY-downY) < slop) {
break;
}
textView.getParent().requestDisallowInterceptTouchEvent(false);//允許父view攔截
這個getParent().requestDisallowInterceptTouchEvent()是viewgroup裡的方法,會在裡面調用mParent.requestDisallowInterceptTouchEvent(),是以控制上層的父view是否能攔截事件。
最後,謝謝閱讀,有不對的地方歡迎指正!
github:demo
參考文章:
SpannableString與SpannableStringBuilder
wenmingvs的weibo demo
怎麼擷取textview上觸摸點的字元或者附近的字元?