EspressoでRecyclerViewの内部のViewに対してperformやcheckをしたかったので、そのときのworkaroundです。
次のようなMatcher用のメソッドを作ります。
public static Matcher<View> withIdInRecyclerView(int id, int recyclerViewId, int position) {
return allOf(ViewMatchers.withId(id), isDescendantOfRecyclerView(recyclerViewId, position));
}
public static Matcher<View> isDescendantOfRecyclerView(final int recyclerViewId, final int position) {
return new BaseMatcher<View>() {
@Override
public boolean matches(Object arg) {
if (arg instanceof View) {
View v = (View) arg;
View parent = v;
while (parent.getParent() != null && parent.getParent() instanceof View) {
if (parent.getId() == recyclerViewId && parent instanceof RecyclerView) {
RecyclerView.ViewHolder holder = findContainingViewHolder((RecyclerView) parent, v);
if (holder != null && holder.getAdapterPosition() == position) {
return true;
}
}
parent = (View) parent.getParent();
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("isDescendantOfRecyclerView(")
.appendValue(InstrumentationRegistry.getTargetContext().getResources().getResourceEntryName(recyclerViewId))
.appendText(",")
.appendValue(position)
.appendText(")");
}
};
}
/* このメソッドは最近のRecyclerViewには標準で実装されています。 */
@Nullable
public static RecyclerView.ViewHolder findContainingViewHolder(RecyclerView recyclerView, View view) {
View v = view;
while (v != null && v.getParent() instanceof View) {
if (v.getParent() == recyclerView) {
return recyclerView.getChildViewHolder(v);
}
v = (View) v.getParent();
}
return null;
}
こうしておくと次のようにシンプルに書けます。
int position = 123;
onView(withId(R.id.recyclerview)).perform(scrollToPosition(position));
onView(withIdInRecyclerView(R.id.button_hoge, R.id.recyclerview, position))
.perform(click());