天天看點

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上觸摸點的字元或者附近的字元?