2016-07-11 33 views
10

Mam widok tekstowy z wieloma klikalnymi rozpiętościami. Chcę móc przetestować kliknięcie tych przęseł.Jak kliknąć clickablespan za pomocą espresso?

Próbowałem skonfigurować niestandardową funkcję ViewAction, która znajdowałaby clickablespans w TextView, a następnie dopasowywała ich tekst do żądanego tekstu, a następnie kliknęła współrzędne xy tego tekstu. Wydaje się jednak, że zakresy dodane do TextView nie są typu ClickableSpan i zamiast tego są fragmentem, który dodał zakres.

Dlatego nie jestem w stanie rozróżnić rozpiętości linków. Czy jest lepszy sposób to zrobić?

dodanie przęsła: metoda

Util.addClickableSpan(spannableString, string, linkedString, new  ClickableSpan() { 
@Override 
public void onClick(View textView) {} 
}); 

tvAcceptTc.setText(spannableString); 
tvAcceptTc.setMovementMethod(LinkMovementMethod.getInstance()); 

użytkowy:

public static void addClickableSpan(SpannableString spannableString, 
           String text, 
           String subText, 
           ClickableSpan clickableSpan) { 
     int start = text.indexOf(subText); 
     int end = text.indexOf(subText) + subText.length(); 
     int flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; 

     spannableString.setSpan(clickableSpan, start, end, flags); 
} 

Definiowanie ViewAction:

@Override 
     public void perform(UiController uiController, View view) { 
      uiController.loopMainThreadUntilIdle(); 
      if (view instanceof TextView) { 

       TextView textView = (TextView) view; 
       Layout textViewLayout = textView.getLayout(); 


       SpannableString fullSpannable = new SpannableString(textView.getText()); 

       Object[] spans = fullSpannable.getSpans(0, fullSpannable.length(), Object.class); 

       ClickableSpan span = null; 
       for (Object object : spans) { 
        if (object instanceof BaseFragment) { 
         ClickableSpan foundSpan = (ClickableSpan)object; 
         int spanStart = fullSpannable.getSpanStart(foundSpan); 
         int spanEnd = fullSpannable.getSpanEnd(foundSpan); 
         if (fullSpannable.subSequence(spanStart, spanEnd).equals(aSubstring)) { 
          //Found the correct span! 
          span = foundSpan; 
         } 
        } 
       } ... go on to click the xy-coordinates 
+0

jak dodać swoje przęsła? czy próbowałeś wywołać 'TextUtils # dumpSpans'? – pskink

+0

Dodałem kod do dodawania przęseł. Działa to teraz, jeśli usunę zaznaczenie i wykonanie instancji, ale znajdzie się dowolny zakres z tekstem zamiast tylko ClickableSpan. Spojrzałem na rozpiętości w debuggerze i żaden z nich nie był typu ClickableSpan, ale zamiast tego był fragmentem, który dodał rozpiętości. –

+0

zobacz ostatni parametr 'getSpans' – pskink

Odpowiedz

14

To jest moje rozwiązanie. Jest to prostsze, ponieważ nie musimy znajdować współrzędnych. Raz znaleźliśmy ClickableSpan, po prostu kliknij na nią:

public static ViewAction clickClickableSpan(final CharSequence textToClick) { 
    return new ViewAction() { 
     @Override 
     public Matcher<View> getConstraints() { 
      return Matchers.instanceOf(TextView.class); 
     } 

     @Override 
     public String getDescription() { 
      return "clicking on a ClickableSpan"; 
     } 

     @Override 
     public void perform(UiController uiController, View view) { 
      TextView textView = (TextView) view; 
      SpannableString spannableString = (SpannableString) textView.getText(); 

      if (spannableString.length() == 0) { 
       // TextView is empty, nothing to do 
       throw new NoMatchingViewException.Builder() 
         .includeViewHierarchy(true) 
         .withRootView(textView) 
         .build(); 
      } 

      // Get the links inside the TextView and check if we find textToClick 
      ClickableSpan[] spans = spannableString.getSpans(0, spannableString.length(), ClickableSpan.class); 
      if (spans.length > 0) { 
       ClickableSpan spanCandidate; 
       for (ClickableSpan span : spans) { 
        spanCandidate = span; 
        int start = spannableString.getSpanStart(spanCandidate); 
        int end = spannableString.getSpanEnd(spanCandidate); 
        CharSequence sequence = spannableString.subSequence(start, end); 
        if (textToClick.toString().equals(sequence.toString())) { 
         span.onClick(textView); 
         return; 
        } 
       } 
      } 

      // textToClick not found in TextView 
      throw new NoMatchingViewException.Builder() 
        .includeViewHierarchy(true) 
        .withRootView(textView) 
        .build(); 

     } 
    }; 
} 

Teraz można skorzystać z naszej zwyczaj ViewAction właśnie tak:

onView(withId(R.id.myTextView)).perform(clickClickableSpan("myLink")); 
+2

Powinieneś przesłać to do Android Supporting Library i przekonwertować na Espresso. https://google.github.io/android-testing-support-library/contribute/index.html – tir38

0

Działa to dla mnie:

/** 
* Clicks the first ClickableSpan in the TextView 
*/ 
public static ViewAction clickFirstClickableSpan() { 
    return new GeneralClickAction(
      Tap.SINGLE, 
      new CoordinatesProvider() { 
       @Override 
       public float[] calculateCoordinates(View view) { 
        //https://leons.im/posts/how-to-get-coordinate-of-a-clickablespan-inside-a-textview/ 
        TextView textView = (TextView) view; 
        Rect parentTextViewRect = new Rect(); 

        SpannableString spannableString = (SpannableString) textView.getText(); 
        Layout textViewLayout = textView.getLayout(); 
        ClickableSpan spanToLocate = null; 

        if (spannableString.length() == 0) { 
         return new float[2]; 
        } 

        ClickableSpan[] spans = spannableString.getSpans(0, spannableString.length(), ClickableSpan.class); 
        if (spans.length > 0) { 
         spanToLocate = spans[0]; 
        } 

        if (spanToLocate == null) { 
         // no specific view found 
         throw new NoMatchingViewException.Builder() 
           .includeViewHierarchy(true) 
           .withRootView(textView) 
           .build(); 
        } 

        double startOffsetOfClickedText = spannableString.getSpanStart(spanToLocate); 
        double endOffsetOfClickedText = spannableString.getSpanEnd(spanToLocate); 
        double startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int) startOffsetOfClickedText); 
        double endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int) endOffsetOfClickedText); 

        // Get the rectangle of the clicked text 
        int currentLineStartOffset = textViewLayout.getLineForOffset((int) startOffsetOfClickedText); 
        int currentLineEndOffset = textViewLayout.getLineForOffset((int) endOffsetOfClickedText); 
        boolean keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset; 
        textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect); 

        // Update the rectangle position to his real position on screen 
        int[] parentTextViewLocation = {0, 0}; 
        textView.getLocationOnScreen(parentTextViewLocation); 

        double parentTextViewTopAndBottomOffset = (
          parentTextViewLocation[1] - 
            textView.getScrollY() + 
            textView.getCompoundPaddingTop() 
        ); 
        parentTextViewRect.top += parentTextViewTopAndBottomOffset; 
        parentTextViewRect.bottom += parentTextViewTopAndBottomOffset; 
        parentTextViewRect.left += (
          parentTextViewLocation[0] + 
            startXCoordinatesOfClickedText + 
            textView.getCompoundPaddingLeft() - 
            textView.getScrollX() 
        ); 
        parentTextViewRect.right = (int) (
          parentTextViewRect.left + 
            endXCoordinatesOfClickedText - 
            startXCoordinatesOfClickedText 
        ); 

        int screenX = (parentTextViewRect.left + parentTextViewRect.right)/2; 
        int screenY = (parentTextViewRect.top + parentTextViewRect.bottom)/2; 
        if (keywordIsInMultiLine) { 
         screenX = parentTextViewRect.left; 
         screenY = parentTextViewRect.top; 
        } 
        return new float[]{screenX, screenY}; 
       } 
      }, 
      Press.FINGER); 
} 
0

można użyć zamiast SpannableStringSpannable kompatybilny z SpannableStringBuilder.

przepraszam, jestem nowy człowiek, ma tylko 1 reputacji, nie można dodać comment.Even mój angielski jest bardzo słaba .....

proponuję używać:

Spannable spannableString = (Spannable) textView.getText(); 

zamiast:

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

postu cały kod poniżej:

public class CustomViewActions { 

    /** 
    * click specific spannableString 
    */ 
    public static ViewAction clickClickableSpan(final CharSequence textToClick) { 
     return clickClickableSpan(-1, textToClick); 
    } 

    /** 
    * click the first spannableString 
    */ 
    public static ViewAction clickClickableSpan() { 
     return clickClickableSpan(0, null); 
    } 

    /** 
    * click the nth spannableString 
    */ 
    public static ViewAction clickClickableSpan(final int index) { 
     return clickClickableSpan(index, null); 
    } 

    public static ViewAction clickClickableSpan(final int index,final CharSequence textToClick) { 
     return new ViewAction() { 
      @Override 
      public Matcher<View> getConstraints() { 
       return instanceOf(TextView.class); 
      } 

      @Override 
      public String getDescription() { 
       return "clicking on a ClickableSpan"; 
      } 

      @Override 
      public void perform(UiController uiController, View view) { 
       TextView textView = (TextView) view; 
       Spannable spannableString = (Spannable) textView.getText(); 
       ClickableSpan spanToLocate = null; 
       if (spannableString.length() == 0) { 
        // TextView is empty, nothing to do 
        throw new NoMatchingViewException.Builder() 
          .includeViewHierarchy(true) 
          .withRootView(textView) 
          .build(); 
       } 

       // Get the links inside the TextView and check if we find textToClick 
       ClickableSpan[] spans = spannableString.getSpans(0, spannableString.length(), ClickableSpan.class); 

       if (spans.length > 0) { 
        if(index >=spans.length){ 
         throw new NoMatchingViewException.Builder() 
          .includeViewHierarchy(true) 
          .withRootView(textView) 
          .build(); 
        }else if (index >= 0) { 
         spanToLocate = spans[index]; 
         spanToLocate.onClick(textView); 
         return; 
        } 
        for (int i = 0; i < spans.length; i++) { 
         int start = spannableString.getSpanStart(spans[i]); 
         int end = spannableString.getSpanEnd(spans[i]); 
         CharSequence sequence = spannableString.subSequence(start, end); 
         if (textToClick.toString().equals(sequence.toString())) { 
          spanToLocate = spans[i]; 
          spanToLocate.onClick(textView); 
          return; 
         } 
        } 
       } 

       // textToClick not found in TextView 
       throw new NoMatchingViewException.Builder() 
         .includeViewHierarchy(true) 
         .withRootView(textView) 
         .build(); 

      } 
     }; 
    } 

} 
+0

Chociaż może to być cenna wskazówka, aby rozwiązać problem, dobra odpowiedź pokazuje również rozwiązanie. Proszę [EDYTUJ] (http: // stackoverflow.com/posts/5419867/edit), aby podać przykładowy kod pokazujący, co masz na myśli. Alternatywnie, rozważ napisanie tego jako komentarza zamiast: –

+0

btw, bezpośredni 'onClick' może ominąć jakiś błąd. Na przykład tekstView jest maskowany przez inne klikalne obszary. to jest moje przypuszczenie bez testu. –