用 Espresso 点击 EditText 的 drawable 右边
Click on EditText's drawable right with Espresso
如何才能点击 EditText 的右侧可绘制对象(查看屏幕截图)?我尝试了几种方法,但总是卡住。
public static Matcher<View> withEditTextDrawable(final int resourceId) {
return new BoundedMatcher<View, EditText>(EditText.class) {
@Override
protected boolean matchesSafely(final EditText editText) {
// ????
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(resourceId);
}
};
}
必须开发这个自定义操作和匹配器来测试我们在某些文本字段右侧丢弃的可绘制对象,
这将在 Espresso 测试中单击 TextView 的任何可绘制对象(左、上、右、下)。
用法:
onView(withId(id)).perform(clickDrawables());
方法:
public static ViewAction clickDrawables()
{
return new ViewAction()
{
@Override
public Matcher<View> getConstraints()//must be a textview with drawables to do perform
{
return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
{
@Override
protected boolean matchesSafely(final TextView tv)
{
if(tv.requestFocusFromTouch())//get fpocus so drawables become visible
for(Drawable d : tv.getCompoundDrawables())//if the textview has drawables then return a match
if(d != null)
return true;
return false;
}
@Override
public void describeTo(Description description)
{
description.appendText("has drawable");
}
});
}
@Override
public String getDescription()
{
return "click drawables";
}
@Override
public void perform(final UiController uiController, final View view)
{
TextView tv = (TextView)view;
if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
{
Drawable[] drawables = tv.getCompoundDrawables();
Rect tvLocation = new Rect();
tv.getHitRect(tvLocation);
Point[] tvBounds = new Point[4];//find textview bound locations
tvBounds[0] = new Point(tvLocation.left, tvLocation.centerY());
tvBounds[1] = new Point(tvLocation.centerX(), tvLocation.top);
tvBounds[2] = new Point(tvLocation.right, tvLocation.centerY());
tvBounds[3] = new Point(tvLocation.centerX(), tvLocation.bottom);
for(int location = 0; location < 4; location++)
if(drawables[location] != null)
{
Rect bounds = drawables[location].getBounds();
tvBounds[location].offset(bounds.width() / 2, bounds.height() / 2);//get drawable click location for left, top, right, bottom
if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, tvBounds[location].x, tvBounds[location].y, 0)))
tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, tvBounds[location].x, tvBounds[location].y, 0));
}
}
}
};
}
这是我对 select 要单击的特定绘图所做的改进:
用法:
onView(withId(id)).perform(new ClickDrawableAction(ClickDrawableAction.Right));
方法:
public static class ClickDrawableAction implements ViewAction
{
public static final int Left = 0;
public static final int Top = 1;
public static final int Right = 2;
public static final int Bottom = 3;
@Location
private final int drawableLocation;
public ClickDrawableAction(@Location int drawableLocation)
{
this.drawableLocation = drawableLocation;
}
@Override
public Matcher<View> getConstraints()
{
return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
{
@Override
protected boolean matchesSafely(final TextView tv)
{
//get focus so drawables are visible and if the textview has a drawable in the position then return a match
return tv.requestFocusFromTouch() && tv.getCompoundDrawables()[drawableLocation] != null;
}
@Override
public void describeTo(Description description)
{
description.appendText("has drawable");
}
});
}
@Override
public String getDescription()
{
return "click drawable ";
}
@Override
public void perform(final UiController uiController, final View view)
{
TextView tv = (TextView)view;//we matched
if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
{
//get the bounds of the drawable image
Rect drawableBounds = tv.getCompoundDrawables()[drawableLocation].getBounds();
//calculate the drawable click location for left, top, right, bottom
final Point[] clickPoint = new Point[4];
clickPoint[Left] = new Point(tv.getLeft() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
clickPoint[Top] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getTop() + (drawableBounds.height() / 2));
clickPoint[Right] = new Point(tv.getRight() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
clickPoint[Bottom] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getBottom() + (drawableBounds.height() / 2));
if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0)))
tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0));
}
}
@IntDef({ Left, Top, Right, Bottom })
@Retention(RetentionPolicy.SOURCE)
public @interface Location{}
}
来自@dareniott 的完美答案。
我刚用 kotlin 实现:
import android.graphics.Point
import android.support.annotation.IntDef
import android.support.test.espresso.UiController
import android.support.test.espresso.ViewAction
import android.support.test.espresso.matcher.BoundedMatcher
import android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
class ClickDrawableAction(@param:Location @field:Location private val drawableLocation: Int) : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(isAssignableFrom(TextView::class.java), object : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(tv: TextView): Boolean {
return tv.requestFocusFromTouch() && tv.compoundDrawables[drawableLocation] != null
}
override fun describeTo(description: Description) {
description.appendText(DESCRIPTION_HAS_DRAWABLE)
}
})
}
override fun getDescription(): String {
return DESCRIPTION_CLICK_DRAWABLE
}
override fun perform(uiController: UiController, view: View) {
val tv = view as TextView
if (tv.requestFocusFromTouch())
{
val drawableBounds = tv.compoundDrawables[drawableLocation].bounds
val clickPoint = arrayOfNulls<Point>(SIZE_CLICK_POINT)
clickPoint[LEFT] = Point(tv.left + drawableBounds.width() / HALF_DIVISOR, (tv.pivotY + drawableBounds.height() / HALF_DIVISOR).toInt())
clickPoint[TOP] = Point((tv.pivotX + drawableBounds.width() / HALF_DIVISOR).toInt(), tv.top + drawableBounds.height() / HALF_DIVISOR)
clickPoint[RIGHT] = Point(tv.right + drawableBounds.width() / HALF_DIVISOR, (tv.pivotY + drawableBounds.height() / HALF_DIVISOR).toInt())
clickPoint[BOTTOM] = Point((tv.pivotX + drawableBounds.width() / HALF_DIVISOR).toInt(), tv.bottom + drawableBounds.height() / HALF_DIVISOR)
clickPoint[drawableLocation]?.let { point ->
if (tv.dispatchTouchEvent(
MotionEvent.obtain(
android.os.SystemClock.uptimeMillis(),
android.os.SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
point.x.toFloat(),
point.y.toFloat(),
0)
)) {
tv.dispatchTouchEvent(
MotionEvent.obtain(
android.os.SystemClock.uptimeMillis(),
android.os.SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
point.x.toFloat(),
point.y.toFloat(),
0))
}
}
}
}
@IntDef(LEFT, TOP, RIGHT, BOTTOM)
@Retention(AnnotationRetention.SOURCE)
annotation class Location
companion object {
const val LEFT = 0
const val TOP = 1
const val RIGHT = 2
const val BOTTOM = 3
const val SIZE_CLICK_POINT = 4
const val HALF_DIVISOR = 2
const val DESCRIPTION_HAS_DRAWABLE = "has drawable"
const val DESCRIPTION_CLICK_DRAWABLE = "click drawable "
}
}
我使用 Material 组件解决了它 TextInputLayout and TextInputEditText
在你的xml中:
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:id="@+id/til_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endIconMode="custom"
app:endIconDrawable="@drawable/ic_edit_location"
app:endIconTint="@color/colorAccent"
app:hintEnabled="false"
app:endIconContentDescription="open map icon">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_address"
android:textColor="@color/colorAccent"
android:layout_height="wrap_content"
android:layout_width="match_parent"
tools:text="3 streenasdasdasd"/>
</com.google.android.material.textfield.TextInputLayout>
这里的技巧是设置 app:endIconMode="custom"
然后根据需要添加可绘制对象。
在java class:
您可以使用 setEndIconOnClickListener
并执行以下操作:
tilLocation.setEndIconOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//do click
}
});
对于截至 2021 年 1 月 7 日(欧洲格式)现在遇到此问题的人。
我遇到了同样的问题并像这样解决了它:
在你的 xml 中,也许你有这样的东西:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/edittext_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edittext_hint"
app:endIconMode="clear_text"
app:endIconCheckable="true"
app:endIconTint="@color/colorSemiTranspBlack"
app:endIconContentDescription="clear text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/eddittext"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
正如你看到上面的 xml 代码,我添加了 app:endIconContentDescription="clear text"
。
从 xml、app:endIconContentDescription="clear text"
抓取并在您的 Instrumented 测试方法中添加 onView(withContentDescription("clear text")).perform(click());
,在您的测试 class 中,例如:
@Test
public void testEdittext(){
onView(withId(R.id.eddittext)).perform(typeText("Type something."));
onView(withContentDescription("clear text")).perform(click());
}
忘了说这onView(withId(R.id.note_title)).perform(typeText("Type something."));
是你必须先在edittext中写点东西的那一行,这样结束图标(eddittext的右边drawable)才会出现,然后点击它。
如何才能点击 EditText 的右侧可绘制对象(查看屏幕截图)?我尝试了几种方法,但总是卡住。
public static Matcher<View> withEditTextDrawable(final int resourceId) {
return new BoundedMatcher<View, EditText>(EditText.class) {
@Override
protected boolean matchesSafely(final EditText editText) {
// ????
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(resourceId);
}
};
}
必须开发这个自定义操作和匹配器来测试我们在某些文本字段右侧丢弃的可绘制对象,
用法:
onView(withId(id)).perform(clickDrawables());
方法:
public static ViewAction clickDrawables()
{
return new ViewAction()
{
@Override
public Matcher<View> getConstraints()//must be a textview with drawables to do perform
{
return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
{
@Override
protected boolean matchesSafely(final TextView tv)
{
if(tv.requestFocusFromTouch())//get fpocus so drawables become visible
for(Drawable d : tv.getCompoundDrawables())//if the textview has drawables then return a match
if(d != null)
return true;
return false;
}
@Override
public void describeTo(Description description)
{
description.appendText("has drawable");
}
});
}
@Override
public String getDescription()
{
return "click drawables";
}
@Override
public void perform(final UiController uiController, final View view)
{
TextView tv = (TextView)view;
if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
{
Drawable[] drawables = tv.getCompoundDrawables();
Rect tvLocation = new Rect();
tv.getHitRect(tvLocation);
Point[] tvBounds = new Point[4];//find textview bound locations
tvBounds[0] = new Point(tvLocation.left, tvLocation.centerY());
tvBounds[1] = new Point(tvLocation.centerX(), tvLocation.top);
tvBounds[2] = new Point(tvLocation.right, tvLocation.centerY());
tvBounds[3] = new Point(tvLocation.centerX(), tvLocation.bottom);
for(int location = 0; location < 4; location++)
if(drawables[location] != null)
{
Rect bounds = drawables[location].getBounds();
tvBounds[location].offset(bounds.width() / 2, bounds.height() / 2);//get drawable click location for left, top, right, bottom
if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, tvBounds[location].x, tvBounds[location].y, 0)))
tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, tvBounds[location].x, tvBounds[location].y, 0));
}
}
}
};
}
这是我对 select 要单击的特定绘图所做的改进:
用法:
onView(withId(id)).perform(new ClickDrawableAction(ClickDrawableAction.Right));
方法:
public static class ClickDrawableAction implements ViewAction
{
public static final int Left = 0;
public static final int Top = 1;
public static final int Right = 2;
public static final int Bottom = 3;
@Location
private final int drawableLocation;
public ClickDrawableAction(@Location int drawableLocation)
{
this.drawableLocation = drawableLocation;
}
@Override
public Matcher<View> getConstraints()
{
return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
{
@Override
protected boolean matchesSafely(final TextView tv)
{
//get focus so drawables are visible and if the textview has a drawable in the position then return a match
return tv.requestFocusFromTouch() && tv.getCompoundDrawables()[drawableLocation] != null;
}
@Override
public void describeTo(Description description)
{
description.appendText("has drawable");
}
});
}
@Override
public String getDescription()
{
return "click drawable ";
}
@Override
public void perform(final UiController uiController, final View view)
{
TextView tv = (TextView)view;//we matched
if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
{
//get the bounds of the drawable image
Rect drawableBounds = tv.getCompoundDrawables()[drawableLocation].getBounds();
//calculate the drawable click location for left, top, right, bottom
final Point[] clickPoint = new Point[4];
clickPoint[Left] = new Point(tv.getLeft() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
clickPoint[Top] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getTop() + (drawableBounds.height() / 2));
clickPoint[Right] = new Point(tv.getRight() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
clickPoint[Bottom] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getBottom() + (drawableBounds.height() / 2));
if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0)))
tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0));
}
}
@IntDef({ Left, Top, Right, Bottom })
@Retention(RetentionPolicy.SOURCE)
public @interface Location{}
}
来自@dareniott 的完美答案。
我刚用 kotlin 实现:
import android.graphics.Point
import android.support.annotation.IntDef
import android.support.test.espresso.UiController
import android.support.test.espresso.ViewAction
import android.support.test.espresso.matcher.BoundedMatcher
import android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
class ClickDrawableAction(@param:Location @field:Location private val drawableLocation: Int) : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(isAssignableFrom(TextView::class.java), object : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(tv: TextView): Boolean {
return tv.requestFocusFromTouch() && tv.compoundDrawables[drawableLocation] != null
}
override fun describeTo(description: Description) {
description.appendText(DESCRIPTION_HAS_DRAWABLE)
}
})
}
override fun getDescription(): String {
return DESCRIPTION_CLICK_DRAWABLE
}
override fun perform(uiController: UiController, view: View) {
val tv = view as TextView
if (tv.requestFocusFromTouch())
{
val drawableBounds = tv.compoundDrawables[drawableLocation].bounds
val clickPoint = arrayOfNulls<Point>(SIZE_CLICK_POINT)
clickPoint[LEFT] = Point(tv.left + drawableBounds.width() / HALF_DIVISOR, (tv.pivotY + drawableBounds.height() / HALF_DIVISOR).toInt())
clickPoint[TOP] = Point((tv.pivotX + drawableBounds.width() / HALF_DIVISOR).toInt(), tv.top + drawableBounds.height() / HALF_DIVISOR)
clickPoint[RIGHT] = Point(tv.right + drawableBounds.width() / HALF_DIVISOR, (tv.pivotY + drawableBounds.height() / HALF_DIVISOR).toInt())
clickPoint[BOTTOM] = Point((tv.pivotX + drawableBounds.width() / HALF_DIVISOR).toInt(), tv.bottom + drawableBounds.height() / HALF_DIVISOR)
clickPoint[drawableLocation]?.let { point ->
if (tv.dispatchTouchEvent(
MotionEvent.obtain(
android.os.SystemClock.uptimeMillis(),
android.os.SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
point.x.toFloat(),
point.y.toFloat(),
0)
)) {
tv.dispatchTouchEvent(
MotionEvent.obtain(
android.os.SystemClock.uptimeMillis(),
android.os.SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
point.x.toFloat(),
point.y.toFloat(),
0))
}
}
}
}
@IntDef(LEFT, TOP, RIGHT, BOTTOM)
@Retention(AnnotationRetention.SOURCE)
annotation class Location
companion object {
const val LEFT = 0
const val TOP = 1
const val RIGHT = 2
const val BOTTOM = 3
const val SIZE_CLICK_POINT = 4
const val HALF_DIVISOR = 2
const val DESCRIPTION_HAS_DRAWABLE = "has drawable"
const val DESCRIPTION_CLICK_DRAWABLE = "click drawable "
}
}
我使用 Material 组件解决了它 TextInputLayout and TextInputEditText
在你的xml中:
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:id="@+id/til_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endIconMode="custom"
app:endIconDrawable="@drawable/ic_edit_location"
app:endIconTint="@color/colorAccent"
app:hintEnabled="false"
app:endIconContentDescription="open map icon">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_address"
android:textColor="@color/colorAccent"
android:layout_height="wrap_content"
android:layout_width="match_parent"
tools:text="3 streenasdasdasd"/>
</com.google.android.material.textfield.TextInputLayout>
这里的技巧是设置 app:endIconMode="custom"
然后根据需要添加可绘制对象。
在java class:
您可以使用 setEndIconOnClickListener
并执行以下操作:
tilLocation.setEndIconOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//do click
}
});
对于截至 2021 年 1 月 7 日(欧洲格式)现在遇到此问题的人。 我遇到了同样的问题并像这样解决了它:
在你的 xml 中,也许你有这样的东西:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/edittext_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edittext_hint"
app:endIconMode="clear_text"
app:endIconCheckable="true"
app:endIconTint="@color/colorSemiTranspBlack"
app:endIconContentDescription="clear text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/eddittext"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
正如你看到上面的 xml 代码,我添加了 app:endIconContentDescription="clear text"
。
从 xml、app:endIconContentDescription="clear text"
抓取并在您的 Instrumented 测试方法中添加 onView(withContentDescription("clear text")).perform(click());
,在您的测试 class 中,例如:
@Test
public void testEdittext(){
onView(withId(R.id.eddittext)).perform(typeText("Type something."));
onView(withContentDescription("clear text")).perform(click());
}
忘了说这onView(withId(R.id.note_title)).perform(typeText("Type something."));
是你必须先在edittext中写点东西的那一行,这样结束图标(eddittext的右边drawable)才会出现,然后点击它。