天天看点

android 实现textview部分文字点击效果,类似于微博的话题丶用户

用过Textview设置过字体颜色、大小和emoji之类的同学都知道,要给文字实现丰富多彩的效果就得用到SpannableString或SpannableStringBuilder,这里要实现的效果还是要用到SpannableStringBuilder。

  先看效果图

android 实现textview部分文字点击效果,类似于微博的话题丶用户

下面我来说说如何使用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上触摸点的字符或者附近的字符?