Espresso,当NestedScrollView或RecyclerView在CoordinatorLayout中时滚动不起作用

看起来CoordinatorLayout会破坏Espresso操作的行为,例如scrollTo()RecyclerViewActions.scrollToPosition()

NestedScrollView问题

对于像这样的布局:

   ...   ...   

如果我尝试使用ViewActions.scrollTo()滚动到NestedScrollView内的任何视图,我发现的第一个问题是我得到一个PerformException 。 这是因为此操作仅支持ScrollViewNestedScrollView不会扩展它。 这里解释了这个问题的解决方法,基本上我们可以在scrollTo()复制代码并更改约束以支持NestedScrollView 。 如果NestedScrollView不在CoordinatorLayout但是只要将它放在CoordinatorLayout ,滚动操作就会失败,这似乎有效。

RecyclerView问题

对于相同的布局,如果我用RecyclerView替换NestedScrollView ,则滚动也会出现问题。

在这种情况下,我使用RecyclerViewAction.scrollToPosition(position) 。 与NestedScrollView不同,在这里我可以看到一些滚动发生。 但是,它看起来像滚动到错误的位置。 例如,如果我滚动到最后一个位置,它会显示倒数第二个但不是最后一个。 当我将RecyclerView移出CoordinatorLayout ,滚动工作正常。

目前,由于此问题,我们无法为使用CoordinatorLayout的屏幕编写任何Espresso测试。 任何遇到相同问题或知道解决方法的人?

发生这种情况是因为Espresso scrollTo()方法显式检查布局类,仅适用于ScrollView和Horizo​​ntalScrollView。 在内部它使用View.requestRectangleOnScreen(…)所以我希望它实际上适用于许多布局。

我对NestedScrollView的解决方法是采用ScrollToAction并修改该约束。 修改后的操作适用于具有该更改的NestedScrollView。

ScrollToAction类中的已更改方法:

 public Matcher getConstraints() { return allOf(withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(anyOf( isAssignableFrom(ScrollView.class), isAssignableFrom(HorizontalScrollView.class), isAssignableFrom(NestedScrollView.class)))); } 

便利方法:

 public static ViewAction betterScrollTo() { return ViewActions.actionWithAssertions(new NestedScrollToAction()); } 

我有这个问题与CoordinatorLayout-> ViewPager-> NestedScrollView一个简单的工作从我得到相同的scrollTo()行为只是在屏幕上向上滑动:

 onView(withId(android.R.id.content)).perform(ViewActions.swipeUp()); 

已报告此问题(可能由OP?),请参阅问题203684

该问题的一个评论建议在NestedScrollView位于CoordinatorLayout内时解决问题:

您需要删除ScrollingView的@string/appbar_scrolling_view_behavior布局行为或此ScrollingView包含在内的任何父视图

以下是该解决方案的实现:

  activity.runOnUiThread(new Runnable() { @Override public void run() { // remove CoordinatorLayout.LayoutParams from NestedScrollView NestedScrollView nestedScrollView = (NestedScrollView)activity.findViewById(scrollViewId); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)nestedScrollView.getLayoutParams(); params.setBehavior(null); nestedScrollView.requestLayout(); } }); 

我能够通过以下方式进行测试:

  1. 进行自定义scrollTo()动作(由OP和Turnsole引用)
  2. 删除NestedScrollView的布局参数,如下所示

我做了一个NestedScrollViewScrollToAction类。

我认为最好在那里制作特定活动的东西。

唯一值得一提的是代码搜索父nestedScrollView并删除它的CoordinatorLayout行为。

https://gist.github.com/miszmaniac/12f720b7e898ece55d2464fe645e1f36

Mido先生的解决方案可能在某些情况下有效,但并非总是如此。 如果您在屏幕底部有一些视图,则不会发生RecyclerView的滚动,因为点击将从RecyclerView外部开始。

解决此问题的一种方法是编写自定义SwipeAction。 喜欢这个:

1 – 创建CenterSwipeAction

 public class CenterSwipeAction implements ViewAction { private final Swiper swiper; private final CoordinatesProvider startCoordProvide; private final CoordinatesProvider endCoordProvide; private final PrecisionDescriber precDesc; public CenterSwipeAction(Swiper swiper, CoordinatesProvider startCoordProvide, CoordinatesProvider endCoordProvide, PrecisionDescriber precDesc) { this.swiper = swiper; this.startCoordProvide = startCoordProvide; this.endCoordProvide = endCoordProvide; this.precDesc = precDesc; } @Override public Matcher getConstraints() { return withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE); } @Override public String getDescription() { return "swipe from middle of screen"; } @Override public void perform(UiController uiController, View view) { float[] startCoord = startCoordProvide.calculateCoordinates(view); float[] finalCoord = endCoordProvide.calculateCoordinates(view); float[] precision = precDesc.describePrecision(); // you could try this for several times until Swiper.Status is achieved or try count is reached try { swiper.sendSwipe(uiController, startCoord, finalCoord, precision); } catch (RuntimeException re) { throw new PerformException.Builder() .withActionDescription(this.getDescription()) .withViewDescription(HumanReadables.describe(view)) .withCause(re) .build(); } // ensures that the swipe has been run. uiController.loopMainThreadForAtLeast(ViewConfiguration.getPressedStateDuration()); } } 

2 – 创建返回ViewAction的方法

  private static ViewAction swipeFromCenterToTop() { return new CenterSwipeAction(Swipe.FAST, GeneralLocation.CENTER, view -> { float[] coordinates = GeneralLocation.CENTER.calculateCoordinates(view); coordinates[1] = 0; return coordinates; }, Press.FINGER); } 

3 – 然后用它来滚动屏幕:

 onView(withId(android.R.id.content)).perform(swipeFromCenterToTop()); 

就是这样! 这样您就可以控制滚动在屏幕中的显示方式。

Barista的scrollTo(R.id.button)适用于所有types的可滚动视图,也适用于NestedScrollView

使用Espresso解决此类问题非常有用。 我们开发和使用它只是为了快速可靠地编写Espresso测试。 这是一个链接: https : //github.com/SchibstedSpain/Barista

以下是我和@miszmaniac在Kotlin做过同样的事情。 随着在Kotlin的代表团 ,它更清洁,更容易,因为我不必覆盖我不需要的方法。

 class ScrollToAction( private val original: android.support.test.espresso.action.ScrollToAction = android.support.test.espresso.action.ScrollToAction() ) : ViewAction by original { override fun getConstraints(): Matcher = anyOf( allOf( withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(isAssignableFrom(NestedScrollView::class.java))), original.constraints ) }