TextView添加ClickableSpan和LinkMovementMethod之间的关系

xiaoxiao2021-02-27  185

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

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

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

[java] view plain copy 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方法,

于是找到下面的代码

[java] view plain copy if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()                  && mText instanceof Spannable && mLayout != null) {              boolean handled = false;                if (mMovement != null) {                  handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);              }              final boolean textIsSelectable = isTextSelectable();              if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {                  // The LinkMovementMethod which should handle taps on links has not been installed                  // on non editable text that support text selection.                  // We reproduce its behavior here to open links for these.                  ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),                          getSelectionEnd(), ClickableSpan.class);                    if (links.length > 0) {                      links[0].onClick(this);                      handled = true;                  }              }  

代码中

[java] view plain copy if (mMovement != null) {          handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);  }  

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

[java] view plain copy @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);  }   一目了然的发现ClickableSpan的实现原理。

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

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

[java] view plain copy tv.setOnTouchListener(new View.OnTouchListener() {      @Override      public boolean onTouch(View v, MotionEvent event) {          int action = event.getAction();            TextView tv = (TextView) v;          CharSequence text = tv.getText();          if (text instanceof SpannableString) {              if (action == MotionEvent.ACTION_UP) {                  int x = (int) event.getX();                  int y = (int) event.getY();                    x -= tv.getTotalPaddingLeft();                  y -= tv.getTotalPaddingTop();                    x += tv.getScrollX();                  y += tv.getScrollY();                    Layout layout = tv.getLayout();                  int line = layout.getLineForVertical(y);                  int off = layout.getOffsetForHorizontal(line, x);                    ClickableSpan[] link = ((SpannableString)text).getSpans(off, off, ClickableSpan.class);                  if (link.length != 0) {                      link[0].onClick(tv);                  } else {                      //do textview click event                  }              }          }            return true;      }    });  

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

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

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

[java] view plain copy private class MyClickSpan extends ClickableSpan {        private String tag;      public MyClickSpan(String tag){          this.tag = tag;      }      @Override      public void onClick(View widget) {          Toast.makeText(ClickSpanActivity.this, tag+" is clicked", Toast.LENGTH_SHORT).show();      }        @Override      public void updateDrawState(TextPaint ds) {          ds.setColor(Color.RED);      }  }  

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

https://stackoverflow.com/questions/5183645/android-clickablespan-in-clickable-textview/40214599#40214599
转载请注明原文地址: https://www.6miu.com/read-13121.html

最新回复(0)