用过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 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;
}
}
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上触摸点的字符或者附近的字符?