2013-05-28 5 views
18

У меня есть TextView в макете. Это так просто. Я поместил OnClickListener в макет, и некоторая часть TextView настроена на ClickableSpan. Я хочу, чтобы ClickableSpan что-то делал в функции onClick при щелчке и , когда нажата другая часть TextView, она должна что-то сделать в функциях onClick для OnClickListener макета. Вот мой код.android ClickableSpan перехватывает событие click

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

и ваши проблемы есть? – Pragnani

+0

Когда я нажимаю на TextView за пределами ClickableSpan, сообщение «всего макета» не появляется. Я должен это сделать. –

+0

Установите ширину и высоту 'match_parent' вместо' wrap_content' – Pragnani

ответ

0

Объявите глобальную переменную boolean:

boolean wordClicked = false; 

Declare и инициализировать 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); 
    } 
}; 

Edit:

Keeping ответ пользовательского KMDev в ввиду, следующий код будет соответствовать вашим требованиям. Мы создаем два пролета: один для указанной длины: spannableString.setSpan(.., 0, 5, ..);, а другой с остатком: spannableString.setSpan(.., 6, spannableString.legth(), ..);. Второй ClickableSpan (span2) выполняет щелчок по RelativeLayout. Более того, переопределяя 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 за то, что вы заметили проблемы с моим оригинальным ответом. Нет необходимости выполнять (ошибочную) проверку с использованием логической переменной (-ов), а установка OnclickListener для TextView не требуется.

+0

На самом деле я создаю макеты динамически и локально. Таким образом, у меня много текстовых комментариев, которые потребуют этой реализации. Поэтому я не могу сохранить глобальную логическую переменную. –

+0

@DeepakSenapati Вы можете повторно использовать глобальную логическую переменную с помощью динамически созданных макетов. Логическое является блокирующим механизмом. Он довольно независим от макетов, которые его используют. Я буду редактировать/улучшать свой ответ, потому что пользователь KMDev поднимает некоторые отличные моменты. – Vikram

+0

@DeepakSenapati Я обновил свой ответ (см. Править), удалив требование для глобальной логической переменной. Протестировано: хорошо работает на моем конце. – Vikram

16

Первый ответ на ваш вопрос заключается в том, что вы не устанавливаете прослушиватель кликов в своем TextView, который потребляет события click, как указывает пользователь2558882. После того, как вы установите прослушиватель кликов в TextView, вы увидите, что области, находящиеся за пределами сенсорной зоны ClickableSpans, будут работать должным образом. Тем не менее, вы обнаружите, что когда вы нажмете на один из ваших ClickableSpans, обратный вызов onClick TextView также будет запущен. Это приводит нас к сложной проблеме, если для вас есть проблема с огнем. Ответ пользователя2558882 не может гарантировать, что ваш обратный вызов onClick ClickableSpan будет запущен перед вашим TextView. Вот некоторые решения от a similar thread, которые лучше реализованы и объяснение от источника. Принятый ответ, что поток должен работать на большинстве устройств, но комментарии для этого ответа указывают на некоторые устройства, имеющие проблемы. Похоже, что некоторые устройства с пользовательскими интерфейсами пользовательских интерфейсов/изготовителей виноваты, но это спекуляция.

Так почему бы вам не гарантировать заказ обратного вызова onClick? Если вы посмотрите на источник TextView (Android 4.3), вы заметите, что в методе onTouchEvent boolean superResult = super.onTouchEvent(event); (super is View) вызывается до handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);, который является вызовом вашего метода перемещения, который затем вызывает ваш ClickClick onClick. Взгляните на супер (View) 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 
    } 

performClick() вызывает набор нажмите слушателю, который в данном случае является щелчок слушатель нашей TextView в. Это означает, что вы не будете знать, в каком порядке будут срабатывать ваши обратные вызовы onClick. То, что вы знаете, заключается в том, что ваши вызовы ClickableSpan и TextView будут вызваны. Решение по потоку, о котором я упоминал ранее, помогает обеспечить порядок, чтобы вы могли использовать флаги.

Если обеспечение совместимости с большим количеством устройств является приоритетом, вам лучше всего воспользоваться вторым взглядом на ваш макет, чтобы увидеть, можете ли вы избежать застревания в этой ситуации. Как правило, есть много вариантов компоновки, чтобы обойти такие случаи.

Редактировать для комментариев ответа:

Когда TextView выполняет onTouchEvent, он вызывает onTouchEvent вашего LinkMovementMethod, так что он может обрабатывать вызовы методов OnClick различного ClickableSpan в. Ваш LinkMovementMethod выполняет следующие действия в onTouchEvent:

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

Вы заметите, что он принимает MotionEvent, получает действие (ACTION_UP: поднимая палец, ACTION_DOWN: нажимая на палец), х и у координаты, где touch, а затем находит, какой номер строки и смещение (позиция в тексте) касание. Наконец, если есть ClickableSpans, которые охватывают эту точку, они извлекаются и вызывается их методы onClick. Поскольку мы хотим передать любые прикосновения к вашему макету родителя, вы можете либо вызвать свои макеты onTouchEvent, если хотите, чтобы он делал все, что он делает, когда прикасался, или вы могли бы назвать его клише-слушателем, если это реализует ваши необходимые функции. Вот где сделать, что:

  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 на этот новый подкласс, и вы должны быть установлены.

Edited снова для возможного избежания побочных эффектов Посмотрите на источник ScrollingMovementMethod (в родительском LinkMovementMethod), и вы увидите, что это метод делегата, который вызывает статический метод return Touch.onTouchEvent(widget, buffer, event); Это означает, что вы можете просто добавить, что, как ваш последней строки в методе и избежать вызова функции super (LinkMovementMethod's) onTouchEvent, которая будет дублировать то, что вы вставляете, а другие события могут провалиться, как ожидалось.

+0

Спасибо за отличный ответ. Однако я хочу, чтобы родительский макет текста View обрабатывал сенсорные области вне зоны касания Clickable Spans, но я все еще не могу ее получить. Если я перехвачу прикосновение родителя, я не смогу достичь кликабельных промежутков. Можете ли вы предложить решение для преодоления этого? –

+0

Могу ли я добавить super.onTouchEvent (виджет, буфер, событие); в другой части ниже Selection.removeSelection (buffer); так что прикосновение будет обработано некоторыми родителями textview, которые реализуют метод ontouch? –

+0

Если вы вызываете super.onTouchEvent (виджет, буфер, событие); который будет специально называть [ScrollingMovementMethod] (http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r2.1/android/text/method/ScrollingMovementMethod.java # 89) для реализации onTouchEvent. Вы можете вызвать любой другой класс, который реализует этот метод или что-то еще с подобной подписью. Поэтому, если вы хотите сказать RelativeLayout, чтобы справиться с этим, вы бы сделали что-то вроде RelativeLayout mLayout = new RelativeLayout (контекст); mLayout.onTouchEvent(); – KMDev

14

Я также столкнулся с этой проблемой, и, благодаря исходному коду @KMDev, я придумал гораздо более чистый подход.

Во-первых, так как я только имея TextView, который должен быть частично интерактивными, на самом деле мне не нужно большинство функциональных LinkMovementMethod (и его суперкласс ScrollingMovementMethod), который добавляет возможность обрабатывать нажатия клавиш, скроллинга и т.д.

Вместо создания пользовательского MovementMethod, который использует OnTouch() код из 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 увольняются, а выходы за их пределами передаются в родительский приемник макета.

Обратите внимание, что если ваш сделать свой TextView Выбирается, я не проверял, для этого случая, и, возможно, вам нужно копаться в источнике сами: P

+0

Большое спасибо, хорошо работает. – natario

+0

Perfect Dreaming! – anthony

+0

Это сработало очень хорошо, спасибо! – jssingh

2

Вот простое решение, и она работала для меня

Вы можете достичь этого, используя работу вокруг в getSelectionStart() и функции класса TextView getSelectionEnd(),

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 
     } 
    } 
}); 
Смежные вопросы