5

How could be possible to click on EditText's right drawable (check the screenshot)? I have tried several ways but I always get stuck.

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

enter image description here

David
  • 901
  • 1
  • 13
  • 37
  • Have you try this? http://stackoverflow.com/questions/13135447/setting-onclicklistner-for-the-drawable-right-of-an-edittext – Mayuri Joshi Oct 27 '15 at 12:49

4 Answers4

16

Had to develop this custom action and matcher to test our discard drawables on the right of some text fields, discard edittext this will click any drawable (left, top, right, bottom) of a TextView in an Espresso test.

Usage:

onView(withId(id)).perform(clickDrawables());

Method:

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



This is a refinement I made to select a specific drawable to click:

Usage:

onView(withId(id)).perform(new ClickDrawableAction(ClickDrawableAction.Right));

Method:

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
  • 355
  • 4
  • 8
  • 1
    Mark this as the answer works like a charm on my custom CallToActionEdit text field! – JPM Sep 27 '16 at 18:37
5

I solved it using the Material Component TextInputLayout and TextInputEditText

In your 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>

The trick here is to set the app:endIconMode="custom" then add your drawable as u want.

In java class:

you can use the setEndIconOnClickListener and do what you want as below:

tilLocation.setEndIconOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //do click
        }
    });
Mahmoud Ayman
  • 1,157
  • 1
  • 17
  • 27
2

For people that have this problem right now as of 1/7/2021 (European format). I had the same problem and solved it like this:

In your xml, maybe you have something like this:

<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>

As you saw the above xml code, i added app:endIconContentDescription="clear text". grab from the xml, app:endIconContentDescription="clear text" and add onView(withContentDescription("clear text")).perform(click()); in your Instrumented test method, in your test class, like:

    @Test
    public void testEdittext(){
    onView(withId(R.id.eddittext)).perform(typeText("Type something."));
    onView(withContentDescription("clear text")).perform(click());
    }

Forgot to say that this onView(withId(R.id.note_title)).perform(typeText("Type something.")); is the line that you must first write something in the edittext, so that the end icon(eddittext's right drawable) will show up and then click it.

John Xenakis
  • 211
  • 2
  • 8
0

Perfect answer from @dareniott .

I just made it in 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 "
    }
}
Rafael Baptista
  • 336
  • 2
  • 7