TextView添加ClickableSpan和LinkMovementMethod之间的关系

Android中实现部分文字可点击及变色一般都会想到使用ClickableSpan,于是就有了下面的代码

  1. TextView tv = (TextView) findViewById(R.id.textview);  
  2. SpannableString ss = new SpannableString("Java一次编译到处运行并不适用Android");  
  3. ss.setSpan(new MyClickSpan("java"),0,4,0);<pre name="code" class="java">tv.setText(ss);  

运行发现并不生效,搜索发现需要添加下面的方法

  1. tv.setMovementMethod(LinkMovementMethod.getInstance());  
再次运行,发现可以点击了,到这里基本满足的大部分的开发工作,但是作为苦逼的开发者又要满足产品经理(不光点击还要图文混排,oh my god,请给我时间),还要过QA那关。

先说点击的问题,后面再说图文混排。上面的代码目前来看貌似没有问题,但是笔者目前已经发现2个问题:

1.如果textview除了响应部分文件的点击事件外,其余部分还要响应其他操作就会有问题(会同事回调span的onclick和textview的click)

2.如果textview设置的maxLine,并且文字高度超过了maxline的值,那么就会出现文字可以滚动,即使设置了ellipsize的值,原因就是设置了LinkMovementMethod


这2个BUG的存在必然不能过测试,于是各种度娘和google,没有找到满意的答案,于是只能自己动手啃TextView源码。

由于点击必然涉及到onClick或者onTouch事件,先找onClick,但是在TextView没有找到onClick的相关代码。然后就看onTouchEvent方法,

于是找到下面的代码

  1. if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()  
  2.                 && mText instanceof Spannable && mLayout != null) {  
  3.             boolean handled = false;  
  4.   
  5.             if (mMovement != null) {  
  6.                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);  
  7.             }  
  8.             final boolean textIsSelectable = isTextSelectable();  
  9.             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {  
  10.                 // The LinkMovementMethod which should handle taps on links has not been installed  
  11.                 // on non editable text that support text selection.  
  12.                 // We reproduce its behavior here to open links for these.  
  13.                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),  
  14.                         getSelectionEnd(), ClickableSpan.class);  
  15.   
  16.                 if (links.length > 0) {  
  17.                     links[0].onClick(this);  
  18.                     handled = true;  
  19.                 }  
  20.             }  

代码中

  1. if (mMovement != null) {  
  2.         handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);  
  3. }  

部分就是通过setMovementMethod方法设置的对象。由于我们设置的是LinkMovementMethod的一个实例,继续打开LinkMovementMethod源码查看其中onTouchEvent的代码。

  1. @Override  
  2. public boolean onTouchEvent(TextView widget, Spannable buffer,  
  3.                             MotionEvent event) {  
  4.     int action = event.getAction();  
  5.   
  6.     if (action == MotionEvent.ACTION_UP ||  
  7.         action == MotionEvent.ACTION_DOWN) {  
  8.         int x = (int) event.getX();  
  9.         int y = (int) event.getY();  
  10.   
  11.         x -= widget.getTotalPaddingLeft();  
  12.         y -= widget.getTotalPaddingTop();  
  13.   
  14.         x += widget.getScrollX();  
  15.         y += widget.getScrollY();  
  16.   
  17.         Layout layout = widget.getLayout();  
  18.         int line = layout.getLineForVertical(y);  
  19.         int off = layout.getOffsetForHorizontal(line, x);  
  20.   
  21.         ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);  
  22.   
  23.         if (link.length != 0) {  
  24.             if (action == MotionEvent.ACTION_UP) {  
  25.                 link[0].onClick(widget);  
  26.             } else if (action == MotionEvent.ACTION_DOWN) {  
  27.                 Selection.setSelection(buffer,  
  28.                                        buffer.getSpanStart(link[0]),  
  29.                                        buffer.getSpanEnd(link[0]));  
  30.             }  
  31.   
  32.             return true;  
  33.         } else {  
  34.             Selection.removeSelection(buffer);  
  35.         }  
  36.     }  
  37.   
  38.     return super.onTouchEvent(widget, buffer, event);  
  39. }  
一目了然的发现ClickableSpan的实现原理。


由于tv.setMovementMethod(LinkMovementMethod.getInstance());代码上述红色2个问题,再看源码,我们的解决方法如果就是直接给TextView设置onTouchEvent。

下面就是自己实现ClickableSpan点击的代码

  1. tv.setOnTouchListener(new View.OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         int action = event.getAction();  
  5.   
  6.         TextView tv = (TextView) v;  
  7.         CharSequence text = tv.getText();  
  8.         if (text instanceof SpannableString) {  
  9.             if (action == MotionEvent.ACTION_UP) {  
  10.                 int x = (int) event.getX();  
  11.                 int y = (int) event.getY();  
  12.   
  13.                 x -= tv.getTotalPaddingLeft();  
  14.                 y -= tv.getTotalPaddingTop();  
  15.   
  16.                 x += tv.getScrollX();  
  17.                 y += tv.getScrollY();  
  18.   
  19.                 Layout layout = tv.getLayout();  
  20.                 int line = layout.getLineForVertical(y);  
  21.                 int off = layout.getOffsetForHorizontal(line, x);  
  22.   
  23.                 ClickableSpan[] link = ((SpannableString)text).getSpans(off, off, ClickableSpan.class);  
  24.                 if (link.length != 0) {  
  25.                     link[0].onClick(tv);  
  26.                 } else {  
  27.                     //do textview click event  
  28.                 }  
  29.             }  
  30.         }  
  31.   
  32.         return true;  
  33.     }  
  34.   
  35. });  

运行调试,发现再也没有上述的2个问题,大功告成。


虽然最终代码只有很少的一点,但是刚开始遇到问题不知道怎么解决的时候,阅读源码还是很辛苦的,所以平常解决问题多看看源码是有非常大的帮助的。感谢Google把android开源。


附上MyClickSpan的代码,包含自定义可点击文字颜色:

  1. private class MyClickSpan extends ClickableSpan {  
  2.   
  3.     private String tag;  
  4.     public MyClickSpan(String tag){  
  5.         this.tag = tag;  
  6.     }  
  7.     @Override  
  8.     public void onClick(View widget) {  
  9.         Toast.makeText(ClickSpanActivity.this, tag+" is clicked", Toast.LENGTH_SHORT).show();  
  10.     }  
  11.   
  12.     @Override  
  13.     public void updateDrawState(TextPaint ds) {  
  14.         ds.setColor(Color.RED);  
  15.     }  
  16. }  


在作者解决完问题后,在stackoverflow发现的同样的问题,附上链接,供大家参考:

https://stackoverflow.com/questions/5183645/android-clickablespan-in-clickable-textview/40214599#40214599
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值