android ClickableSpan截取单击事件

android ClickableSpan截取单击事件,android,events,textview,intercept,Android,Events,Textview,Intercept,我在布局中有一个文本视图。很简单。 我在布局中放置了一个OnClickListener,TextView的某些部分被设置为ClickableSpan。 我希望ClickableSpan在单击并单击时在onClick函数中执行某些操作 单击TextView的另一部分时,它必须在布局的OnClickListener的onClick函数中执行某些操作。 这是我的密码 RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayo

我在布局中有一个文本视图。很简单。 我在布局中放置了一个OnClickListener,TextView的某些部分被设置为ClickableSpan。 我希望ClickableSpan在单击并单击时在onClick函数中执行某些操作 单击TextView的另一部分时,它必须在布局的OnClickListener的onClick函数中执行某些操作。 这是我的密码

    RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
    l.setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "whole layout", Toast.LENGTH_SHORT).show();
        }
    });

    TextView textView = (TextView)findViewById(R.id.t1);
    textView.setMovementMethod(LinkMovementMethod.getInstance());

    SpannableString spannableString = new SpannableString(textView.getText().toString());
    ClickableSpan span = new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            Toast.makeText(MainActivity.this, "just word", Toast.LENGTH_SHORT).show();
        }
    };
    spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);        
    textView.setText(spannableString);

声明一个全局
布尔
变量:

boolean wordClicked = false;
声明并初始化
l
final

final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
OnClickListener
添加到
textView

textView.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        if (!wordClicked) {
            // Let the click be handled by `l's` OnClickListener
            l.performClick();   
        }
    }
});
span
更改为:

ClickableSpan span = new ClickableSpan() {

    @Override
    public void onClick(View widget) {
        wordClicked = true;
        Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();

        // A 100 millisecond delay to let the click event propagate to `textView's` 
        // OnClickListener and to let the check `if (!wordClicked)` fail
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                wordClicked = false;                        
            }
        }, 100L);
    }
};

编辑:

考虑到用户KMDev的答案,以下代码将符合您的规范。我们创建两个跨度:一个用于指定的长度:
spannableString.setSpan(..,0,5,…)
和另一个以及其余部分:
spannableString.setSpan(..,6,spannableString.legth(),…)。第二个
可点击span
(span2)在
相对值上执行点击操作。此外,通过覆盖
updateDrawState(TextPaint)
,我们能够为第二个跨度提供一个非独特(非样式化)的外观。然而,第一个跨距有一个链接颜色,并带有下划线

final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
    l.setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View v) {
            Toast.makeText(Trial.this, "whole layout", Toast.LENGTH_SHORT).show();
        }
    });

TextView textView = (TextView)findViewById(R.id.t1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);

SpannableString spannableString = new SpannableString(textView.getText().toString());

ClickableSpan span = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();
    }
};

spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);     

ClickableSpan span2 = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        l.performClick();
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.bgColor = getResources().getColor(android.R.color.transparent);
        tp.setUnderlineText(false);
    }
};

spannableString.setSpan(span2, 6, spannableString.length(), 
                                      Spannable.SPAN_INCLUSIVE_INCLUSIVE);  

textView.setText(spannableString);

特别感谢用户KMDev注意到我原始答案中的问题。不需要使用布尔变量执行(错误的)检查,也不需要为TextView设置
OnclickListener

您的问题的第一个答案是,您没有在TextView上设置click listener,正如用户2558882指出的那样,它正在使用click事件。在TextView上设置click listener后,您将看到ClickableSpans触摸区域之外的区域将按预期工作。但是,您会发现,当您单击其中一个ClickableSpans时,TextView的onClick回调也会被触发。这就导致了一个困难的问题,如果两个火都是你的问题。user2558882的回复不能保证ClickableSpan的onClick回调会在TextView之前触发。下面是一些更好地实现的解决方案,以及来自源代码的解释。公认的答案是线程应该在大多数设备上工作,但对该答案的评论提到某些设备存在问题。看起来一些带有定制运营商/制造商用户界面的设备是罪魁祸首,但这只是猜测

那你为什么不能保证一次点击回叫订单?如果您查看TextView(Android 4.3)的源代码,您会注意到在onTouchEvent方法中,
booleanSuperResult=super.onTouchEvent(事件)handled |=mMovement.onTouchEvent(此,(可扩展)多行文字,事件)之前调用code>(super is View)
这是对移动方法的调用,然后调用ClickableSpan的onClick。看看super的(视图)onTouchEvent(..),您会注意到:

    // Use a Runnable and post this rather than 
    // performClick directly. This lets other visual 
    // of the view update before click actions start.
    if (mPerformClick == null) {
        mPerformClick = new PerformClick();
    }
    if (!post(mPerformClick)) { // <---- In the case that this won't post, 
        performClick();         //    it'll fallback to calling it directly
    }
您会注意到,它采用MotionEvent,获取动作(动作向上:抬起手指,动作向下:按下手指),触摸起始位置的x和y坐标,然后查找触摸击中的行号和偏移量(文本中的位置)。最后,如果存在包含该点的可单击范围,则将检索这些范围并调用其onClick方法。由于我们希望将任何触摸传递给父布局,如果您希望它在触摸时执行所有操作,您可以调用布局onTouchEvent,或者如果它实现了您所需的功能,您可以调用它的click listener。以下是我们应该做的:

         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);

                // Your call to your layout's onTouchEvent or it's 
                //onClick listener depending on your needs

            }
         }
因此,为了查看,您将创建一个扩展LinkMovementMethod的新类,重写它的onTouchEvent方法,将调用复制并粘贴到我注释的正确位置,确保您将TextView的移动方法设置为这个新的子类,并且您应该被设置

再次编辑以避免可能的副作用
查看ScrollingMovementMethod的源代码(LinkMovementMethod的父级),您将看到它是一个委托方法,调用静态方法
return Touch.onTouchEvent(小部件、缓冲区、事件)这意味着您可以将其添加为方法中的最后一行,并避免调用super(LinkMovementMethod)onTouchEvent实现,这将复制您粘贴的内容,其他事件可能会如预期的那样失败。

我也遇到了这个问题,多亏了前面提到的源代码@KMDev,我想出了一个更干净的方法

首先,由于我只有一个部分可点击的
TextView
,事实上我不需要大多数功能
LinkMovementMethod
(及其超级类
ScrollingMovementMethod
),它增加了处理按键、滚动等操作的能力

相反,创建一个自定义的
MovementMethod
,该方法使用
OnTouch()
code from
LinkMovementMethod

ClickableMovementMethod.java

package com.example.yourapplication;

import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.BaseMovementMethod;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * A movement method that traverses links in the text buffer and fires clicks. Unlike
 * {@link LinkMovementMethod}, this will not consume touch events outside {@link ClickableSpan}s.
 */
public class ClickableMovementMethod extends BaseMovementMethod {

    private static ClickableMovementMethod sInstance;

    public static ClickableMovementMethod getInstance() {
        if (sInstance == null) {
            sInstance = new ClickableMovementMethod();
        }
        return sInstance;
    }

    @Override
    public boolean canSelectArbitrarily() {
        return false;
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {

        int action = event.getActionMasked();
        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 {
                    Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
                            buffer.getSpanEnd(link[0]));
                }
                return true;
            } else {
                Selection.removeSelection(buffer);
            }
        }

        return false;
    }

    @Override
    public void initialize(TextView widget, Spannable text) {
        Selection.removeSelection(text);
    }
}
然后使用此
ClickableMovementMethod
,移动方法将不再使用触摸事件。但是,
TextView.setMovementMethod()
调用
TextView.fixFocusableAndClickableSettings()
将可点击、长点击和可聚焦设置设置为true,这将使
View.onTouchEvent()
使用触摸事件。要解决此问题,只需重置三个属性

因此,
ClickableMovementMethod
附带的最后一个实用方法是:

public static void setTextViewLinkClickable(TextView textView) {
    textView.setMovementMethod(ClickableMovementMethod.getInstance());
    // Reset for TextView.fixFocusableAndClickableSettings(). We don't want View.onTouchEvent()
    // to consume touch events.
    textView.setClickable(false);
    textView.setLongClickable(false);
}
这对我来说很有魅力

单击
ClickableSpan
s上的事件将被激发,单击它们之外的事件将传递给父布局侦听器


请注意,如果您的
文本视图是可选择的,我还没有测试过这种情况,也许您需要自己深入了解源代码:这里有一个简单的解决方案,它对我很有用

你可以做到这一点
public static void setTextViewLinkClickable(TextView textView) {
    textView.setMovementMethod(ClickableMovementMethod.getInstance());
    // Reset for TextView.fixFocusableAndClickableSettings(). We don't want View.onTouchEvent()
    // to consume touch events.
    textView.setClickable(false);
    textView.setLongClickable(false);
}
tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ClassroomLog.log(TAG, "Textview Click listener ");
        if (tv.getSelectionStart() == -1 && tv.getSelectionEnd() == -1) {
            //This condition will satisfy only when it is not an autolinked text
            //Fired only when you touch the part of the text that is not hyperlinked 
        }
    }
});
new SmartClickableSpan
   .Builder(this)
   .regularText("I agree to all ")
   .clickableText(new ClickableOptions().setText("Terms of Use").setOnClick(new 
       ClickableSpan() {
           @Override
           public void onClick(@NonNull View view) {
               // Your Code..
           }
   }))
   .into(myTextView);