此篇文章是ViewMatchers的源码,参考地址:
源码地址:
/ / / / / / / / / / / / / / / / ViewMatchers.java
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package android.support.test.espresso.matcher;import static android.support.test.espresso.util.TreeIterables.breadthFirstViewTraversal;import static com.google.common.base.Preconditions.checkNotNull;import static com.google.common.base.Preconditions.checkState;import static org.hamcrest.Matchers.is;import android.support.test.espresso.util.HumanReadables;import com.google.common.base.Predicate;import com.google.common.collect.Iterables;import android.content.Context;import android.content.res.Resources;import android.graphics.Rect;import android.util.DisplayMetrics;import android.util.TypedValue;import android.view.View;import android.view.ViewGroup;import android.view.ViewParent;import android.view.WindowManager;import android.view.inputmethod.EditorInfo;import android.view.inputmethod.InputConnection;import android.webkit.WebView;import android.widget.Checkable;import android.widget.EditText;import android.widget.Spinner;import android.widget.TextView;import junit.framework.AssertionFailedError;import org.hamcrest.Description;import org.hamcrest.Matcher;import org.hamcrest.Matchers;import org.hamcrest.StringDescription;import org.hamcrest.TypeSafeMatcher;import java.util.Iterator;/** * A collection of hamcrest matchers that match {@link View}s. */public final class ViewMatchers { private ViewMatchers() {} /** * Returns a matcher that matches Views which are an instance of or subclass of the provided * class. Some versions of Hamcrest make the generic typing of this a nightmare, so we have a * special case for our users. */ public static MatcherisAssignableFrom(final Class clazz) { checkNotNull(clazz); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("is assignable from class: " + clazz); } @Override public boolean matchesSafely(View view) { return clazz.isAssignableFrom(view.getClass()); } }; } /** * Returns a matcher that matches Views with class name matching the given matcher. */ public static Matcher withClassName(final Matcher classNameMatcher) { checkNotNull(classNameMatcher); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("with class name: "); classNameMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { return classNameMatcher.matches(view.getClass().getName()); } }; } /** * Returns a matcher that matches {@link View}s that are currently displayed on the screen to the * user. * * Note: isDisplayed will select views that are partially displayed (eg: the full height/width of * the view is greater then the height/width of the visible rectangle). If you wish to ensure the * entire rectangle this view draws is displayed to the user use isCompletelyDisplayed. */ public static Matcher isDisplayed() { return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("is displayed on the screen to the user"); } @Override public boolean matchesSafely(View view) { return view.getGlobalVisibleRect(new Rect()) && withEffectiveVisibility(Visibility.VISIBLE).matches(view); } }; } /** * Returns a matcher which only accepts a view whose height and width fit perfectly within * the currently displayed region of this view. * * There exist views (such as ScrollViews) whose height and width are larger then the physical * device screen by design. Such views will _never_ be completely displayed. */ public static Matcher isCompletelyDisplayed() { return isDisplayingAtLeast(100); } /** * Returns a matcher which accepts a view so long as a given percentage of that view's area is * not obscured by any other view and is thus visible to the user. * * @param areaPercentage an integer ranging from (0, 100] indicating how much percent of the * surface area of the view must be shown to the user to be accepted. */ public static Matcher isDisplayingAtLeast(final int areaPercentage) { checkState(areaPercentage <= 100, "Cannot have over 100 percent: %s", areaPercentage); checkState(areaPercentage > 0, "Must have a positive, non-zero value: %s", areaPercentage); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText(String.format( "at least %s percent of the view's area is displayed to the user.", areaPercentage)); } @Override public boolean matchesSafely(View view) { Rect visibleParts = new Rect(); boolean visibleAtAll = view.getGlobalVisibleRect(visibleParts); if (!visibleAtAll) { return false; } Rect screen = getScreenWithoutStatusBarActionBar(view); int viewHeight = (view.getHeight() > screen.height()) ? screen.height() : view.getHeight(); int viewWidth = (view.getWidth() > screen.width()) ? screen.width() : view.getWidth(); double maxArea = viewHeight * viewWidth; double visibleArea = visibleParts.height() * visibleParts.width(); int displayedPercentage = (int) ((visibleArea / maxArea) * 100); return displayedPercentage >= areaPercentage && withEffectiveVisibility(Visibility.VISIBLE).matches(view); } private Rect getScreenWithoutStatusBarActionBar(View view) { DisplayMetrics m = new DisplayMetrics(); ((WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay().getMetrics(m); // Get status bar height int resourceId = view.getContext().getResources() .getIdentifier("status_bar_height", "dimen", "android"); int statusBarHeight = (resourceId > 0) ? view.getContext().getResources() .getDimensionPixelSize(resourceId) : 0; // Get action bar height TypedValue tv = new TypedValue(); int actionBarHeight = (view.getContext().getTheme().resolveAttribute( android.R.attr.actionBarSize, tv, true)) ? TypedValue.complexToDimensionPixelSize( tv.data, view.getContext().getResources().getDisplayMetrics()) : 0; return new Rect(0, 0, m.widthPixels, m.heightPixels - (statusBarHeight + actionBarHeight)); } }; } /** * Returns a matcher that matches {@link View}s that are enabled. */ public static Matcher isEnabled() { return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("is enabled"); } @Override public boolean matchesSafely(View view) { return view.isEnabled(); } }; } /** * Returns a matcher that matches {@link View}s that are focusable. */ public static Matcher isFocusable() { return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("is focusable"); } @Override public boolean matchesSafely(View view) { return view.isFocusable(); } }; } /** * Returns a matcher that matches {@link View}s currently have focus. */ public static Matcher hasFocus() { return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("has focus on the screen to the user"); } @Override public boolean matchesSafely(View view) { return view.hasFocus(); } }; } /** * Returns a matcher that matches {@link View}s that are selected. */ public static Matcher isSelected() { return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("is selected"); } @Override public boolean matchesSafely(View view) { return view.isSelected(); } }; } /** * Returns an * Matcher
that matches {@link View}s based on their siblings. * * This may be particularly useful when a view cannot be uniquely selected on properties such as * text or R.id. For example: a call button is repeated several times in a contacts layout and the * only way to differentiate the call button view is by what appears next to it (e.g. the unique * name of the contact). * * @param siblingMatcher a * *Matcher
for the sibling of the view. */ public static MatcherhasSibling(final Matcher siblingMatcher) { checkNotNull(siblingMatcher); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("has sibling: "); siblingMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { ViewParent parent = view.getParent(); if (!(parent instanceof ViewGroup)) { return false; } ViewGroup parentGroup = (ViewGroup) parent; for (int i = 0; i < parentGroup.getChildCount(); i++) { if (siblingMatcher.matches(parentGroup.getChildAt(i))) { return true; } } return false; } }; } /** * Returns a * Matcher
that matches {@link View}s based on content description property * value. * * @param resourceId the resource id of the content description to match on. */ public static MatcherwithContentDescription(final int resourceId) { return new TypeSafeMatcher () { private String resourceName = null; private String expectedText = null; @Override public void describeTo(Description description) { description.appendText("with content description from resource id: "); description.appendValue(resourceId); if (null != this.resourceName) { description.appendText("["); description.appendText(resourceName); description.appendText("]"); } if (null != this.expectedText) { description.appendText(" value: "); description.appendText(expectedText); } } @Override public boolean matchesSafely(View view) { if (null == this.expectedText) { try { expectedText = view.getResources().getString(resourceId); resourceName = view.getResources().getResourceEntryName(resourceId); } catch (Resources.NotFoundException ignored) { // view could be from a context unaware of the resource id. } } if (null != expectedText && null != view.getContentDescription()) { return expectedText.equals(view.getContentDescription().toString()); } else { return false; } } }; } /** * Returns an * Matcher
that matches {@link View}s based on content description property * value. Sugar for withContentDescription(is("string")). * * @param text the text to match on. */ public static MatcherwithContentDescription(String text) { return withContentDescription(is(text)); } /** * Returns an * Matcher
that matches {@link View}s based on content description property * value. * * @param charSequenceMatcher a {@link CharSequence} * *Matcher
for the content description */ public static MatcherwithContentDescription( final Matcher charSequenceMatcher) { checkNotNull(charSequenceMatcher); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("with content description: "); charSequenceMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { return charSequenceMatcher.matches(view.getContentDescription()); } }; } /** * Same as withId(is(int)), but attempts to look up resource name of the given id and use an * R.id.myView style description with describeTo. If resource lookup is unavailable, at the time * describeTo is invoked, this will print out a simple "with id: %d". If resource lookup is * available, but looking up the name for the given id, fails, "with id: %d (resource name not * found)" will be returned as the description. * * @param id the resource id. */ public static Matcher withId(final int id) { return new TypeSafeMatcher () { Resources resources = null; @Override public void describeTo(Description description) { String idDescription = Integer.toString(id); if (resources != null) { try { idDescription = resources.getResourceName(id); } catch (Resources.NotFoundException e) { // No big deal, will just use the int value. idDescription = String.format("%s (resource name not found)", id); } } description.appendText("with id: " + idDescription); } @Override public boolean matchesSafely(View view) { resources = view.getResources(); return id == view.getId(); } }; } /** * Returns a matcher that matches {@link View}s based on resource ids. Note: Android resource ids * are not guaranteed to be unique. You may have to pair this matcher with another one to * guarantee a unique view selection. * * @param integerMatcher a Matcher for resource ids */ public static Matcher withId(final Matcher integerMatcher) { checkNotNull(integerMatcher); return new TypeSafeMatcher () { @Override public void describeTo(Description description) { description.appendText("with id: "); integerMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { return integerMatcher.matches(view.getId()); } }; } /** * Returns a matcher that matches {@link View} based on tag keys. * * @param key to match */ public static Matcher withTagKey(final int key) { return withTagKey(key, Matchers.notNullValue()); } /** * Returns a matcher that matches {@link View}s based on tag keys. * * @param key to match * @param objectMatcher Object to match */ public static Matcher withTagKey(final int key, final Matcher