Как щелкнуть clickablespan с помощью эспрессо?
У меня есть текстовое окно с несколькими кликабельными промежутками. Я хочу, чтобы у меня была возможность проверить нажатие этих интервалов.
Я попробовал настроить пользовательский ViewAction, который найдет области clickables в TextView, а затем сопоставляет их текст с нужным текстом, а затем нажимает на координаты xy этого текста. Тем не менее, кажется, что промежутки, добавленные в TextView, не относятся к типу ClickableSpan и вместо этого являются фрагментом, который добавил диапазон.
Поэтому я не могу отличить промежутки ссылок. Есть ли лучший способ сделать это?
Добавление интервалов:
Util.addClickableSpan(spannableString, string, linkedString, new ClickableSpan() {
@Override
public void onClick(View textView) {}
});
tvAcceptTc.setText(spannableString);
tvAcceptTc.setMovementMethod(LinkMovementMethod.getInstance());
Полезный метод:
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);
}
Определение 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
Ответы
Ответ 1
Это мое решение. Это проще, потому что нам не нужно находить координаты. Как только мы найдем ClickableSpan, мы просто нажимаем на него:
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();
}
};
}
Теперь вы можете использовать наш пользовательский ViewAction так:
onView(withId(R.id.myTextView)).perform(clickClickableSpan("myLink"));
Ответ 2
Это работает для меня:
/**
* 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);
}
Ответ 3
вы можете использовать Spannable
вместо SpannableString
, совместимого с SpannableStringBuilder
.
Извините, я новый человек, имею только 1 репутацию, не могу добавить комментарий. Даже мой английский очень плохой.....
Я предлагаю использовать:
Spannable spannableString = (Spannable) textView.getText();
вместо:
SpannableString spannableString = (SpannableString) textView.getText();
разместите весь код ниже:
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();
}
};
}
}
Ответ 4
Лучшим вариантом будет подкласс ViewAction. Вот как это сделать в Котлине:
class SpannableTextClickAction(val text: String) : ViewAction {
override fun getDescription(): String = "SpannableText click action"
override fun getConstraints(): Matcher<View> =
isAssignableFrom(TextView::class.java)
override fun perform(uiController: UiController?, view: View?) {
val textView = view as TextView
val spannableString = textView.text as SpannableString
val spans = spannableString.getSpans(0, spannableString.count(), ClickableSpan::class.java)
val spanToLocate = spans.firstOrNull { span: ClickableSpan ->
val start = spannableString.getSpanStart(span)
val end = spannableString.getSpanEnd(span)
val spanText = spannableString.subSequence(start, end).toString()
spanText == text
}
if (spanToLocate != null) {
spanToLocate.onClick(textView)
return
}
// textToClick not found in TextView
throw NoMatchingViewException.Builder()
.includeViewHierarchy(true)
.withRootView(textView)
.build()
}
}
и использовать его как:
onView(withId(<view_id>)).perform(scrollTo(), SpannableTextClickAction(text))