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

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

  • 開發(fā)是一個漫長的過程,我們會遇到很多很多的坑,有些卻是系統(tǒng)級的坑,有時候遇到真是抓狂俯在,不過這也是我們不斷進步的過程,今天就給大家講一個我遇到的一個很坑的問題娃惯。
  • 還好我遇到了一個萬能的 Android 大神 stainberg 跷乐,他幫助我仔細排查并且解決了問題,有他我真的提高了好多趾浅。
QQ20161129-0@2x.png

需求描述

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

一個巨大的坑

  • 當我做完上面這些后酪惭,哇...好棒希痴,每一個標記的字段都可以執(zhí)行自己規(guī)定的點擊事件了。
  • 但是撞蚕!我發(fā)現(xiàn)了一個很嚴重的問題润梯,標記的字段是可以點擊过牙,但由于設(shè)置了 textview.setMovementMethod(LinkMovementMethod.getInstance()) 導致 TextView 對點擊事件做了攔截甥厦,而原本在 RecyclerView 中 item 自己的點擊事件卻失效了。
  • 就是說寇钉,textView 攔截了全部的點擊事件刀疙,如果我這一段文字沒有任何匹配到的At,##話題標簽和Url這類的字符串扫倡,它任會攔截谦秧。
  • 我原本想要設(shè)計的效果是,當點擊特殊字符串的時候撵溃,執(zhí)行自定義的點擊事件疚鲤,而沒有特殊字符出現(xiàn)的時候,執(zhí)行 item 原本的點擊事件缘挑,例如點擊正常文字集歇,進入微博詳情頁。

排查問題

  • 我想問題的原因语淘,應(yīng)該就是出在了 textview.setMovementMethod(LinkMovementMethod.getInstance()) 上面诲宇,所以我查看了 LinkMovementMethod 的源碼。
  • 通過打 debug 發(fā)現(xiàn)執(zhí)行攔截操作的核心代碼是下面這一段惶翻。
  @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);
    }
  • 其中有特殊字符串時姑蓝,走 if (link.length != 0) {}這里面,執(zhí)行你的自定義點擊事件吕粗,沒有特使字符串的時候走 return super.onTouchEvent(widget, buffer, event);
  • 然后我繼續(xù)對沒有特使字符串的地方打斷點排查纺荧,這時候我發(fā)現(xiàn)了一個很坑的問題,無論什么樣颅筋,return super.onTouchEvent(widget, buffer, event);都返回 true 宙暇,這就意味著 TextView 會一直攔截事件,而外層的 item 永遠不會執(zhí)行點擊事件垃沦,這里我終于找到了問題的所在客给。
  • 我靠,這是一個系統(tǒng)級的 bug 啊肢簿,很早之前我就發(fā)現(xiàn)了這個問題靶剑,但我一直不知道問什么蜻拨,今天終于明白了,這么久 Google 竟然還不修復桩引。

解決方案

  • 既然我們知道了問題出現(xiàn)的原因缎讼,那么就很好解決了,在沒有匹配到特殊字符串的時候坑匠,返回 False 就好啦血崭。
  • 一開始我想著重寫 LinkMovementMethod ,然后在最后返回 False 厘灼,然而并沒有什么卵用夹纫,依舊被攔截。
  • 最后在萬能的 StackOverFlow 上發(fā)現(xiàn)了解決的方法设凹,就是重寫一個 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;
    }
  }
  • 然后在 textView 上調(diào)用 textView.setOnTouchListener(MyLinkMovementMethod.getInstance());
  • 就這樣!有特殊字符串的地方奋姿,會執(zhí)行自定義點擊事件锄开,沒有特殊字符串的地方執(zhí)行 item 原有的點擊事件。

一些代碼

  • 其中正則表達式親測有效称诗,可放心使用萍悴。
  /**
    * 將微博正文中的 @ 和 # ,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 Url_PATTERN = 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中的連接跳轉(zhuǎn)
    */
   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);

       }
   }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粪狼,一起剝皮案震驚了整個濱河市退腥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌再榄,老刑警劉巖狡刘,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異困鸥,居然都是意外死亡嗅蔬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門疾就,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澜术,“玉大人,你說我怎么就攤上這事猬腰∧穹希” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵姑荷,是天一觀的道長盒延。 經(jīng)常有香客問我缩擂,道長,這世上最難降的妖魔是什么添寺? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任胯盯,我火速辦了婚禮,結(jié)果婚禮上计露,老公的妹妹穿的比我還像新娘博脑。我一直安慰自己,他們只是感情好票罐,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布叉趣。 她就那樣靜靜地躺著,像睡著了一般胶坠。 火紅的嫁衣襯著肌膚如雪君账。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天沈善,我揣著相機與錄音,去河邊找鬼椭蹄。 笑死闻牡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绳矩。 我是一名探鬼主播罩润,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼翼馆!你這毒婦竟也來了割以?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤应媚,失蹤者是張志新(化名)和其女友劉穎严沥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體中姜,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡消玄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丢胚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翩瓜。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖携龟,靈堂內(nèi)的尸體忽然破棺而出兔跌,到底是詐尸還是另有隱情,我是刑警寧澤峡蟋,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布坟桅,位于F島的核電站相满,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏桦卒。R本人自食惡果不足惜立美,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望方灾。 院中可真熱鬧建蹄,春花似錦、人聲如沸裕偿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘿棘。三九已至劲腿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸟妙,已是汗流浹背焦人。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留重父,地道東北人花椭。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像房午,于是被迫代替她去往敵國和親矿辽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容