TextView中ClickableSpan与文本自由复制(TextIsSelectable)冲突问题

本文介绍了解决Android TextView中超链接点击与文本复制功能冲突的方法。通过自定义TextView并重写onTouchEvent方法,实现了既能点击超链接又能自由复制文本的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇文章解决了UrlSpan与文本中url链接识别和设置的问题,超链接可以正常显示了。但由于用户期望TextView中的文章内容可以自由复制,这个需求嘛实现起来就是设置下textView.setTextIsSelectable(true)就可以了。

天真的以为没啥问题,版本转测。立马出现一个严重问题,第一次点击超链接打开的网址与实际不符,第二次点击超链接才能打开正确的网址,或者说每次点击超链接文本打开的网址都是上一次点击区域内的超链接url。

贴TextView::onTouchEvent的源码

01     @Override
02     public boolean onTouchEvent(MotionEvent event) {
03         。。。
04         final boolean superResult = super.onTouchEvent(event);
05          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
06                 && mText instanceof Spannable && mLayout != null) {
07             boolean handled = false;
08  
09             if (mMovement != null) {
10                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
11             }
12  
13             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
14                 // The LinkMovementMethod which should handle taps on links has not been installed
15                 // on non editable text that support text selection.
16                 // We reproduce its behavior here to open links for these.
17                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
18                         getSelectionEnd(), ClickableSpan.class);
19  
20                 if (links.length != 0) {
21                     links[0].onClick(this);
22                     handled = true;
23                 }
24             }
25             。。。
26             if (handled) {
27                 return true;
28             }
29         }
30         return superResult;
31     }
    可以看到onTouchEvent()方法中涉及ClickableSpan处理的有两个关键点

关键点1. 执行MovementMothed::onTouchEvent()方法(上面源码中蓝色部分)------mMovement.onTouchEvent(this, (Spannable) mText, event);

关键点2.上面源码中蓝色部分,在TextView的LinksClickable、AutoLink、TextIsSelectable属性都已设置情况下,获取当前选择区域内的ClickableSpan并执行其onClick()方法;

   对于1,支持ClickableSpan使用的MovementMothed为LinkMovementMethod。而支持文本自由复制的MovementMothed为ArrowKeyMovementMethod。

       设置了TextView的textIsSelectable属性后,TextView会自动设置器mMovement成员为ArrowKeyMovementMethod实例。此时文本才可以自由复制。也即上面关键点1中执行的是ArrowKeyMovementMethod::onTouchEvent()方法:长按时弹出复制、粘贴、选择菜单

    而我也正好设置了TextView的autoLink属性,故关键点2的if判断通过,但此时TextView中当前选择文本为记录的用户上次点击位置,故打开的链接也就是用户上次点击位置对应的链接url了,若用户上次点击位置没有超链接,就不会弹出浏览器打开链接的页面。

    当然若去掉TextView的autoLink属性,用户点击超链接将不会产生任何操作。


    总结:TextView的超链接点击打开和文本自由复制两个功能是互斥的,没办法同时支持。终于只要为啥百度贴吧 帖子中不能自由复制文本了。。。

     两个功能都很重要,不可能删除哪一个。相比较之下文本自由复功能是帖子内容TextView中所有文本都需要支持的功能,而超链接点击打开功能仅仅是超链接部分文本需要的。故我选择设置TextView的textIsSelectable属性,即默认支持文本自由复制(此时不能修改mMovement为ArrowKeyMovementMethod外的其他MovementMothed,不然自由复制功能将会失效)。然后重写TextView::onTouchEvent()方法来支持超链接点击打开功能。具体如下:

01 public class CustomTextView extends TextView {
02      
03     private long mLastActionDownTime = -1;
04     public CustomTextView(Context context) {
05         super(context);
06     }
07      
08     public CustomTextView(Context context, AttributeSet attrs) {
09         super(context, attrs);
10     }
11  
12     public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
13         super(context, attrs, defStyle);
14     }
15  
16     @Override
17     public boolean onTouchEvent(MotionEvent event) {
18         CharSequence text = getText();
19         if (text != null && text instanceof Spannable) {
20             handleLinkMovementMethod(this, (Spannable)text, event);
21         }
22  
23         return super.onTouchEvent(event);
24     }
25      
26     private boolean handleLinkMovementMethod(TextView widget, Spannable buffer, MotionEvent event) {
27         int action = event.getAction();
28  
29         if (action == MotionEvent.ACTION_UP ||
30             action == MotionEvent.ACTION_DOWN) {
31             int x = (int) event.getX();
32             int y = (int) event.getY();
33  
34             x -= widget.getTotalPaddingLeft();
35             y -= widget.getTotalPaddingTop();
36  
37             x += widget.getScrollX();
38             y += widget.getScrollY();
39  
40             Layout layout = widget.getLayout();
41             int line = layout.getLineForVertical(y);
42             int off = layout.getOffsetForHorizontal(line, x);
43  
44             ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
45  
46             if (link.length != 0) {
47                 if (action == MotionEvent.ACTION_UP) {
48                     long actionUpTime = System.currentTimeMillis();
49                     if (actionUpTime - mLastActionDownTime > ViewConfiguration.getLongPressTimeout()) { //长按事件,取消LinkMovementMethod处理,即不处理ClickableSpan点击事件
50                         return false;
51                     }
52                     link[0].onClick(widget);
53                     Selection.removeSelection(buffer);
54                 else if (action == MotionEvent.ACTION_DOWN) {
55                     Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]));
56                     mLastActionDownTime = System.currentTimeMillis();
57                 }
58             }
59         }
60          
61         return false;
62     }
63 }

    上面的TextView::handleLinkMovementMethod()方法,其实就是从LinkMovementMethod::onTouchEvent()方法,稍微修改了下。主要就是两个逻辑:

1.ACTION_DOWN时,将点击位置的超链接选中;

2.ACTION_UP时,在非长按情况下,执行ClickableSpan的点击处理。


    然后就完美解决了TextView的超链接点击打开和文本自由复制两个功能是互斥问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值