Espresso,当 NestedScrollView 或 RecyclerView 在 CoordinatorLayout 中时滚动不起作用
Posted
技术标签:
【中文标题】Espresso,当 NestedScrollView 或 RecyclerView 在 CoordinatorLayout 中时滚动不起作用【英文标题】:Espresso, scrolling not working when NestedScrollView or RecyclerView is in CoordinatorLayout 【发布时间】:2016-05-18 07:27:48 【问题描述】:看起来CoordinatorLayout
破坏了 Espresso 操作的行为,例如 scrollTo()
或 RecyclerViewActions.scrollToPosition()
。
NestedScrollView 的问题
对于这样的布局:
<android.support.design.widget.CoordinatorLayout
android:layout_
android:layout_>
<android.support.v4.widget.NestedScrollView
android:layout_
android:layout_
app:layout_behavior="@string/appbar_scrolling_view_behavior">
...
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_
android:layout_ >
...
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
如果我尝试使用ViewActions.scrollTo()
滚动到NestedScrollView
中的任何视图,我发现的第一个问题是我得到了PerformException
。这是因为这个动作只支持ScrollView
而NestedScrollView
没有扩展它。 here解释了这个问题的解决方法,基本上我们可以复制scrollTo()
中的代码并更改约束以支持NestedScrollView
。如果NestedScrollView
不在CoordinatorLayout
中,这似乎可行,但只要将其放入CoordinatorLayout
中,滚动操作就会失败。
RecyclerView 的问题
对于相同的布局,如果我将NestedScrollView
替换为RecyclerView
,滚动也会出现问题。
在这种情况下,我使用的是RecyclerViewAction.scrollToPosition(position)
。与NestedScrollView
不同,在这里我可以看到发生了一些滚动。但是,它看起来像滚动到错误的位置。例如,如果我滚动到最后一个位置,它会显示倒数第二个而不是最后一个。当我将RecyclerView
移出CoordinatorLayout
时,滚动工作正常。
由于这个问题,目前我们无法为使用 CoordinatorLayout
的屏幕编写任何 Espresso 测试。有没有遇到同样问题或知道解决方法的人?
【问题讨论】:
我有一个问题,RecycleView 在 NestedScrollview 中。我不能使用 recycleview.scrollToPosition(X); ,它只是不起作用。在过去的 6 天里,我尝试了所有方法,但我可以克服它。有什么建议吗?非常感谢! 【参考方案1】:我必须测试 recyclerview 项目。我的 RecyclerView 位于 CoordinatorLayout 内的 NestedScrollView 中。
以下是对我有用的解决方案,我觉得这是在 NestedScrollView 中测试 RecyclerView 项目的最合适的解决方案。
第 1 步:复制并粘贴以下函数
以下将从我们即将测试的 recyclerView 返回所需的子视图。
fun atPositionOnView(recyclerViewId: Int, position: Int, childViewIdToTest: Int): Matcher<View?>?
return object : TypeSafeMatcher<View?>()
var resources: Resources? = null
var childView: View? = null
override fun describeTo(description: Description?)
var idDescription = Integer.toString(recyclerViewId)
if (resources != null)
idDescription = try
resources!!.getResourceName(recyclerViewId)
catch (var4: Resources.NotFoundException)
String.format("%s (resource name not found)",
*arrayOf<Any?>(Integer.valueOf(recyclerViewId)))
description?.appendText("with id: $idDescription")
override fun matchesSafely(view: View?): Boolean
resources = view?.getResources()
if (childView == null)
val recyclerView = view?.getRootView()?.findViewById(recyclerViewId) as RecyclerView
childView = if (recyclerView != null && recyclerView.id == recyclerViewId)
recyclerView.findViewHolderForAdapterPosition(position)!!.itemView
else
return false
return if (viewId == -1)
view === childView
else
val targetView = childView!!.findViewById<View>(viewId)
view === targetView
第 2 步:现在复制并粘贴以下函数
以下将检查您的孩子在 recyclerView 中是否正在显示。
fun ViewInteraction.isNotDisplayed(): Boolean
return try
check(matches(not(isDisplayed())))
true
catch (e: Error)
false
第 3 步:测试您的 recyclerView 项目并滚动它们是否不在屏幕上
以下将滚动未显示的孩子并使其出现在屏幕上。
if (onView(atPositionOnView(R.id.rv_items, pos, R.id.tv_item_name)).isNotDisplayed())
val appViews = UiScrollable(UiSelector().scrollable(true))
appViews.scrollForward() //appViews.scrollBackward()
一旦您的视图显示出来,您就可以执行您的测试用例了。
【讨论】:
变量viewId
从何而来?它不在第一个函数的范围内。【参考方案2】:
这就是我在 Kotlin 中所做的与 @miszmaniac 所做的相同的事情。 使用delegation in 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<View> = anyOf(
allOf(
withEffectiveVisibility(Visibility.VISIBLE),
isDescendantOfA(isAssignableFrom(NestedScrollView::class.java))),
original.constraints
)
【讨论】:
这对我有用。我还包括了一个scrollTo()
方法。 fun scrollTo(): ViewAction = actionWithAssertions(ScrollToAction())
对于 androidx 变种:private val original: androidx.test.espresso.action.ScrollToAction = androidx.test.espresso.action.ScrollToAction()
【参考方案3】:
Barista 的 scrollTo(R.id.button)
适用于各种可滚动视图,也适用于 NestedScrollView
。
使用 Espresso 解决此类问题非常有用。我们开发和使用它只是为了以快速可靠的方式编写 Espresso 测试。这是一个链接:https://github.com/SchibstedSpain/Barista
【讨论】:
【参考方案4】: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<View> 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());
就是这样!通过这种方式,您可以控制屏幕上的滚动方式。
【讨论】:
【参考方案5】:我在使用 CoordinatorLayout->ViewPager->NestedScrollView 时遇到了这个问题,我很容易获得相同的 scrollTo() 行为,只需在屏幕上向上滑动即可:
onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());
【讨论】:
如果屏幕底部有按钮,此解决方案将不起作用 =/ 这个解决方法对我有用,但只能与另一个结合使用。我复制了ActionOnItemAtPositionViewAction
并禁用了滚动删除行new ScrollToPositionViewAction(position).perform(uiController, view);
所以我有:onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());
onView(withId(R.id.my_list)).perform(new CopyOfActionOnItemAtPositionViewAction(13, click()));
【参考方案6】:
我已经创建了一个 NestedScrollViewScrollToAction 类。
我认为在那里制作特定于活动的东西会更好。
唯一值得一提的是代码搜索父级nestedScrollView 并移除了它的CoordinatorLayout 行为。
https://gist.github.com/miszmaniac/12f720b7e898ece55d2464fe645e1f36
【讨论】:
在我的情况下,我有 CoordinatorLayout->ViewPager->NestedScrollView 和 scrollTo() 不起作用。我更新了脚本,它现在可以滚动了,谢谢@miszmaniac。更新脚本:gist.github.com/maydin/677c983c11d6a75c90186c09366fef2f 您可以像下面这样使用 ViewInteraction appCompatButton3 = onView( allOf(withId(R.id.profile_btn_save), withText("Save Changes"))); appCompatButton3.perform(NestedScrollViewScrollToAction.scrollTo(),click());【参考方案7】:此问题已报告(可能由 OP?),请参阅 Issue 203684
当 NestedScrollView 位于 CoordinatorLayout 内部时,该问题的其中一个 cmets 建议解决该问题:
您需要删除 ScrollingView 或包含此 ScrollingView 的任何父视图的
@string/appbar_scrolling_view_behavior
布局行为
这是该解决方法的一个实现:
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();
);
我能够通过以下方式进行测试:
-
制作自定义 scrollTo() 操作(由 OP 和 Turnsole 引用)
删除 NestedScrollView 的布局参数,如下所示
【讨论】:
这对我来说效果很好。我在NestedScrollView
和我的CoordinatorLayout
之间有另一个视图,所以我不得不删除这个视图的布局参数,CoordinatorLayout
的直接子视图。【参考方案8】:
这是因为 Espresso scrollTo() 方法显式检查布局类并且仅适用于 ScrollView 和 HorizontalScrollView。在内部它使用 View.requestRectangleOnScreen(...) 所以我希望它实际上适用于许多布局。
我对 NestedScrollView 的解决方法是采用 ScrollToAction 并修改该约束。修改后的操作对 NestedScrollView 的更改效果很好。
ScrollToAction 类中的更改方法:
public Matcher<View> 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());
【讨论】:
您的NestedScrollView
在CoordinatorLayout
内吗?我试过这个,它似乎只有在 NestedScrollView
不在 CoordinatorLayout
中时才有效。
@ivacf 在CoordinatorLayout
中为我工作
This 答案可能会帮助您滚动到嵌套滚动视图,因为它包含匹配器的完整代码。以上是关于Espresso,当 NestedScrollView 或 RecyclerView 在 CoordinatorLayout 中时滚动不起作用的主要内容,如果未能解决你的问题,请参考以下文章
Android Marshmallow:使用 Espresso 测试权限?
Android Espresso,RecyclerViewActions 请求权限