Android:可点击的TextView中的ClickableSpan

Luk*_*kas 40 java android textview

我有一个可以包含可点击链接的textview.单击其中一个链接时,我想开始一个活动.这样可以正常工作,但也可以单击整个文本视图并启动另一个活动.

这是我目前的解决方案:

    TextView tv = (TextView)findViewById(R.id.textview01);      
    Spannable span = Spannable.Factory.getInstance().newSpannable("test link span");   
    span.setSpan(new ClickableSpan() {  
        @Override
        public void onClick(View v) {  
            Log.d("main", "link clicked");
            Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); 
        } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    tv.setText(span); 

    tv.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d("main", "textview clicked");
            Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show();               
        }
    });

    tv.setMovementMethod(LinkMovementMethod.getInstance());
Run Code Online (Sandbox Code Playgroud)

问题是,当我设置一个OnClickListener,每次我点击一个链接第一监听整个的TextView ,然后将一个用于ClickableSpan被调用.

当点击一个链接时,有没有办法防止android为整个textview调用监听器?或者在监听器中决定整个视图,是否单击了链接?

Yoe*_*der 39

找到了一个非常直接的解决方法.在不属于链接的所有文本区域上定义ClickableSpan并处理单击它们,就像单击文本视图一样:

TextView tv = (TextView)findViewById(R.id.textview01);      
Spannable span = Spannable.Factory.getInstance().newSpannable("test link span");   
span.setSpan(new ClickableSpan() {  
    @Override
    public void onClick(View v) {  
        Log.d("main", "link clicked");
        Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); 
    } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

// All the rest will have the same spannable.
ClickableSpan cs = new ClickableSpan() {  
    @Override
    public void onClick(View v) {  
        Log.d("main", "textview clicked");
        Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show(); 
    } };

// set the "test " spannable.
span.setSpan(cs, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

// set the " span" spannable
span.setSpan(cs, 6, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

tv.setText(span);

tv.setMovementMethod(LinkMovementMethod.getInstance());
Run Code Online (Sandbox Code Playgroud)

希望这有所帮助(我知道这个线程已经过时了,但万一现在有人看到它......).

  • setMovementMethod为我做了诀窍,谢谢! (2认同)

Lah*_*nto 24

这是一个非常简单的解决方案..这对我有用

textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ClassroomLog.log(TAG, "Textview Click listener ");
        if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) {
            // do your code here this will only call if its not a hyperlink
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 奇迹般有效.这应该是公认的答案. (2认同)
  • 以防万一,如果您想知道它为什么会起作用,请在“LinkMovementMethod”类的“onTouchEvent”中搜索“Selection.setSelection”。 (2认同)

Luk*_*kas 10

Matthew建议继承TextView,并用这个提示得出一个相当丑陋的解决方法.但它有效:

我创建了一个"ClickPreventableTextView",当我在TextView中有clickablespans时,我会使用它,它应该是整体可点击的.

在其onTouchEvent方法中,此类在其基础TextView类上调用onTouchEvent之前调用MovementMethod的onTouchEvent方法.因此可以保证,clickablespan的Listener将首先被调用.我可以阻止为整个TextView调用OnClickListener

/**
 * TextView that allows to insert clickablespans while whole textview is still clickable<br>
 * If a click an a clickablespan occurs, click handler of whole textview will <b>not</b> be invoked
 * In your span onclick handler you first have to check whether {@link ignoreSpannableClick} returns true, if so just return from click handler
 * otherwise call {@link preventNextClick} and handle the click event
 * @author Lukas
 *
 */
public class ClickPreventableTextView extends TextView implements OnClickListener {
private boolean preventClick;
private OnClickListener clickListener;
private boolean ignoreSpannableClick;

public ClickPreventableTextView(Context context) {
    super(context);
}

public ClickPreventableTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ClickPreventableTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public boolean onTouchEvent(MotionEvent event) {
    if (getMovementMethod() != null)
        getMovementMethod().onTouchEvent(this, (Spannable)getText(), event);
    this.ignoreSpannableClick = true;
    boolean ret = super.onTouchEvent(event);
    this.ignoreSpannableClick = false;
    return ret;
}

/**
 * Returns true if click event for a clickable span should be ignored
 * @return true if click event should be ignored
 */
public boolean ignoreSpannableClick() {
    return ignoreSpannableClick;
}

/**
 * Call after handling click event for clickable span
 */
public void preventNextClick() {
    preventClick = true;
}

@Override
public void setOnClickListener(OnClickListener listener) {
    this.clickListener = listener;
    super.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    if (preventClick) {
        preventClick = false;
    } else if (clickListener != null)
        clickListener.onClick(v);
}
}
Run Code Online (Sandbox Code Playgroud)

可点击范围的监听器现在看起来像那样

    span.setSpan(new ClickableSpan() {  
        @Override
        public void onClick(View v) {  
            Log.d("main", "link clicked");
            if (widget instanceof ClickPreventableTextView) {
                if (((ClickPreventableTextView)widget).ignoreSpannableClick())
                    return;
                ((ClickPreventableTextView)widget).preventNextClick();
            }

            Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); 
        } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Run Code Online (Sandbox Code Playgroud)

对我来说主要的缺点是,现在getMovementMethod().onTouchEvent将被调用两次(TextView在其onTouchEvent方法中调用该方法).我不知道这是否有任何副作用,因为它按预期工作.


小智 6

代码对我有用,来自LinkMovementMethod的源代码

tv.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                TextView tv = (TextView) v;
                if (action == MotionEvent.ACTION_UP) {
                    int x = (int) event.getX();
                    int y = (int) event.getY();

                    Layout layout = tv.getLayout();
                    int line = layout.getLineForVertical(y);
                    int off = layout.getOffsetForHorizontal(line, x);

                    ClickableSpan[] link = h.diary.contentSpan.getSpans(off, off, ClickableSpan.class);

                    if (link.length != 0) {
                        link[0].onClick(tv);
                    } else {
                       //do other click
                    }
                }
                return true;
            }
        });
Run Code Online (Sandbox Code Playgroud)