I have a textview with multiple clickable spans in it. I want to be able to test clicking these spans.
I tried setting up a custom ViewAction that would find the clickablespans in the TextView and then match their text with the desired text and then click on the xy coordinates of that text. However, it seems the spans added to the TextView aren't of type ClickableSpan and are instead the the fragment that added the span.
Therefore, I am not able to distinguish the link spans. Is there a better way to do this?
Adding the spans:
Util.addClickableSpan(spannableString, string, linkedString, new ClickableSpan() {
@Override
public void onClick(View textView) {}
});
tvAcceptTc.setText(spannableString);
tvAcceptTc.setMovementMethod(LinkMovementMethod.getInstance());
Utility method:
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);
}
Defining the 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
This is my solution. It's simpler because we don't need to find the coordinates. Once we have found the ClickableSpan, we just click on it:
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();
}
};
}
Now you can use our custom ViewAction just like that:
onView(withId(R.id.myTextView)).perform(clickClickableSpan("myLink"));
Here is the Kotlin version of accepted answer
fun clickClickableSpan(textToClick: CharSequence): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return Matchers.instanceOf(TextView::class.java)
}
override fun getDescription(): String {
return "clicking on a ClickableSpan";
}
override fun perform(uiController: UiController, view: View) {
val textView = view as TextView
val spannableString = textView.text as SpannableString
if (spannableString.isEmpty()) {
// TextView is empty, nothing to do
throw NoMatchingViewException.Builder()
.includeViewHierarchy(true)
.withRootView(textView)
.build();
}
// Get the links inside the TextView and check if we find textToClick
val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
if (spans.isNotEmpty()) {
var spanCandidate: ClickableSpan
for (span: ClickableSpan in spans) {
spanCandidate = span
val start = spannableString.getSpanStart(spanCandidate)
val end = spannableString.getSpanEnd(spanCandidate)
val sequence = spannableString.subSequence(start, end)
if (textToClick.toString().equals(sequence.toString())) {
span.onClick(textView)
return;
}
}
}
// textToClick not found in TextView
throw NoMatchingViewException.Builder()
.includeViewHierarchy(true)
.withRootView(textView)
.build()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With