我在Android開發中遇到的坑之微博正文點擊處理

wangxia 8年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

  • 開發是一個漫長的過程,我們會遇到很多很多的坑,有些卻是系統級的坑,有時候遇到真是抓狂,不過這也是我們不斷進步的過程,今天就給大家講一個我遇到的一個很坑的問題。
  • 還好我遇到了一個萬能的 Android 大神 stainberg ,他幫助我仔細排查并且解決了問題,有他我真的提高了好多。

需求描述

  • 上圖是我們常見的微博界面,其中微博正文中出現了不同標記的字段,有At用戶,有##話題,有Url標簽。
  • 重點就是如何處理類似于微博正文中,不同標記的點擊事件。
  • 很顯然,使用過微博SDK的同學們都知道,其中微博正文這一段字是在一個 Text 中返回的,所以我們也理應在一個 TextView 中對不同的標記做處理。
  • 處理的方式很簡單,就是使用 Android 中的 SpannableString 和 ClickableSpan ,先配合正則表達式匹配出想要的字符,再通過 SpannableString 的 setSpan() 方法來對標記出得字符串做處理,我們可以對該字符串自定義顏色,點擊事件等(后面會有源碼)。
  • 注意所在的 TextView 要實現 textview.setMovementMethod(LinkMovementMethod.getInstance()) 才可以使自定義的點擊事件生效。

一個巨大的坑

  • 當我做完上面這些后,哇...好棒,每一個標記的字段都可以執行自己規定的點擊事件了。
  • 但是!我發現了一個很嚴重的問題,標記的字段是可以點擊,但由于設置了 textview.setMovementMethod(LinkMovementMethod.getInstance()) 導致 TextView 對點擊事件做了攔截,而原本在 RecyclerView 中 item 自己的點擊事件卻失效了。
  • 就是說,textView 攔截了全部的點擊事件,如果我這一段文字沒有任何匹配到的At,##話題標簽和Url這類的字符串,它任會攔截。
  • 我原本想要設計的效果是,當點擊特殊字符串的時候,執行自定義的點擊事件,而沒有特殊字符出現的時候,執行 item 原本的點擊事件,例如點擊正常文字,進入微博詳情頁。

排查問題

  • 我想問題的原因,應該就是出在了 textview.setMovementMethod(LinkMovementMethod.getInstance()) 上面,所以我查看了 LinkMovementMethod 的源碼。
  • 通過打 debug 發現執行攔截操作的核心代碼是下面這一段。

    @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);
              } else if (action == MotionEvent.ACTION_DOWN) {
                  Selection.setSelection(buffer,
                                         buffer.getSpanStart(link[0]),
                                         buffer.getSpanEnd(link[0]));
              }
    
              return true;
          } else {
              Selection.removeSelection(buffer);
          }
      }
    
      return super.onTouchEvent(widget, buffer, event);
    

    }</code></pre> </li>

  • 其中有特殊字符串時,走 if (link.length != 0) {} 這里面,執行你的自定義點擊事件,沒有特使字符串的時候走 return super.onTouchEvent(widget, buffer, event);
  • 然后我繼續對沒有特使字符串的地方打斷點排查,這時候我發現了一個很坑的問題,無論什么樣, return super.onTouchEvent(widget, buffer, event); 都返回 true ,這就意味著 TextView 會一直攔截事件,而外層的 item 永遠不會執行點擊事件,這里我終于找到了問題的所在。
  • 我靠,這是一個系統級的 bug 啊,很早之前我就發現了這個問題,但我一直不知道問什么,今天終于明白了,這么久 Google 竟然還不修復。
  • </ul>

    解決方案

    • 既然我們知道了問題出現的原因,那么就很好解決了,在沒有匹配到特殊字符串的時候,返回 False 就好啦。
    • 一開始我想著重寫 LinkMovementMethod ,然后在最后返回 False ,然而并沒有什么卵用,依舊被攔截。
    • 最后在萬能的 StackOverFlow 上發現了解決的方法,就是重寫一個 TextView 的 setontouchlistener 方法,把上面的代碼寫到里面就好了,沒錯就是這么簡單,膜拜一下 StackOverFlow 上的大神(代碼如下)。

      public class MyLinkMovementMethod implements View.OnTouchListener {

      public static MyLinkMovementMethod getInstance() { if (sInstance == null) sInstance = new MyLinkMovementMethod();

        return sInstance;
      

      }

      private static MyLinkMovementMethod sInstance;

      @Override public boolean onTouch(View v, MotionEvent event) { boolean ret = false; CharSequence text = ((TextView) v).getText(); Spannable stext = Spannable.Factory.getInstance().newSpannable(text); TextView widget = (TextView) v; 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 = stext.getSpans(off, off, ClickableSpan.class);
      
            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    link[0].onClick(widget);
                }
                ret = true;
            }
        }
        return ret;
      

      } }</code></pre> </li>

    • 然后在 textView 上調用 textView.setOnTouchListener(MyLinkMovementMethod.getInstance());
    • 就這樣!有特殊字符串的地方,會執行自定義點擊事件,沒有特殊字符串的地方執行 item 原有的點擊事件。
    • </ul>

      一些代碼

      • 其中正則表達式親測有效,可放心使用。

        /**

        • 將微博正文中的 @ 和 # ,url標識出 *
        • @param text
        • @return */ public static SpannableString getWeiBoText(Context context, String text) { Resources res = context.getResources(); //四種正則表達式 Pattern AT_PATTERN = Pattern.compile("@[\u4e00-\u9fa5\w\-]+"); Pattern TAG_PATTERN = Pattern.compile("#([^\#|.]+)#"); Pattern UrlPATTERN = Pattern.compile("((http|https|ftp|ftps):\/\/)?([a-zA-Z0-9-]+\.){1,5}(com|cn|net|org|hk|tw)((\/(\w|-)+(\.([a-zA-Z]+))?)+)?(\/)?(\??([\.%:a-zA-Z0-9-]+=[#\.%:a-zA-Z0-9_-]+(&)?)+)?"); Pattern EMOJI_PATTER = Pattern.compile("\[([\u4e00-\u9fa5\w])+\]");

          SpannableString spannable = new SpannableString(text);

          Matcher tag = TAG_PATTERN.matcher(spannable); while (tag.find()) {

           String tagNameMatch = tag.group();
           int start = tag.start();
           spannable.setSpan(new MyTagSpan(context, tagNameMatch), start, start + tagNameMatch.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
          

          }

          Matcher at = AT_PATTERN.matcher(spannable); while (at.find()) {

           String atUserName = at.group();
           int start = at.start();
           spannable.setSpan(new MyAtSpan(context, atUserName), start, start + atUserName.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
          

          }

          Matcher url = Url_PATTERN.matcher(spannable); while (url.find()) {

           String urlString = url.group();
           int start = url.start();
           spannable.setSpan(new MyURLSpan(context, urlString), start, start + urlString.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
          

          }

          Matcher emoji = EMOJI_PATTER.matcher(spannable); while (emoji.find()) {

           String key = emoji.group(); // 獲取匹配到的具體字符
           int start = emoji.start(); // 匹配字符串的開始位置
           Integer imgRes = Emotion.getImgByName(key);
           System.out.println("@@@"+imgRes);
           if (imgRes != null) {
               BitmapFactory.Options options = new BitmapFactory.Options();
               options.inJustDecodeBounds = true;
               BitmapFactory.decodeResource(res, imgRes, options);
          
               int scale = (int) (options.outWidth / 32);
               options.inJustDecodeBounds = false;
               options.inSampleSize = scale;
               Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes, options);
          
               ImageSpan span = new ImageSpan(context, bitmap);
               spannable.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
           }
          

          }

          return spannable; }

          /**

        • 用于weibo text中的連接跳轉 */ private static class MyURLSpan extends ClickableSpan { private String mUrl; private Context context;

          MyURLSpan(Context ctx, String url) {

           context = ctx;
           mUrl = url;
          

          }

          @Override public void updateDrawState(TextPaint ds) {

           ds.setColor(Color.parseColor("#f44336"));
          

          }

          @Override public void onClick(View widget) {

           Intent intent = UrlActivity.newIntent(context, mUrl);
           context.startActivity(intent);
          
          

          } }</code></pre> </li> </ul>

           

           

          來自:http://www.jianshu.com/p/68b12336d6e0

           

 本文由用戶 wangxia 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!