Android Espresso,当NestedScrollView或RecyclerView处于协调布局时,滚动不起作用
它看起来像是Android Espresso,当NestedScrollView或RecyclerView处于协调布局时,滚动不起作用,android,android-recyclerview,android-espresso,coordinator-layout,android-nestedscrollview,Android,Android Recyclerview,Android Espresso,Coordinator Layout,Android Nestedscrollview,它看起来像是CoordinatorLayout破坏了浓缩咖啡操作的行为,例如scrollTo()或RecycleServiceActions.scrollToPosition() NestedScrollView存在问题 对于这样的布局: <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height=&q
CoordinatorLayout
破坏了浓缩咖啡操作的行为,例如scrollTo()
或RecycleServiceActions.scrollToPosition()
NestedScrollView存在问题
对于这样的布局:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
...
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
...
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
,基本上我们可以在scrollTo()
中复制代码,并更改约束以支持NestedScrollView
。如果NestedScrollView
不在CoordinatorLayout
中,但一旦将其放入CoordinatorLayout
中,滚动操作就会失败,则这似乎是可行的
回收视图问题
对于相同的布局,若我将NestedScrollView
替换为RecyclerView
,那个么滚动也会出现问题
在本例中,我使用的是RecyclServiceWaction.scrollToPosition(位置)
。与嵌套的ScrollView不同,在这里我可以看到一些滚动。但是,它看起来滚动到了错误的位置。例如,如果滚动到最后一个位置,则显示倒数第二个位置,但不显示最后一个位置。当我将RecyclerView
移出CoordinatorLayout
时,滚动会正常工作
由于此问题,目前我们无法为使用协调布局的屏幕编写任何浓缩咖啡测试。任何遇到相同问题或知道解决方法的人?之所以发生这种情况,是因为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))));
}
此问题已报告(可能由OP?),请参阅
对该问题的一条评论建议在NestedScrollView位于CoordinatorLayout内部时解决该问题:
您需要删除ScrollingView或该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();
}
});
我能够通过以下方式进行测试:
进行自定义scrollTo()操作(由OP和Turnsole引用)
删除NestedScrollView的布局参数,如下所示
我制作了一个嵌套的ScrollViewScrollToAction类
我认为在那里制作特定于活动的东西更好
唯一值得一提的是,代码搜索父nestedScrollView并删除它的CoordinatorLayout行为
我在CoordinatorLayout->ViewPager->NestedScrollView中遇到了这个问题。要获得相同的scrollTo()行为,我的一个简单方法是在屏幕上向上滑动:
onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());
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());
}
}
3-然后使用它滚动屏幕:
onView(withId(android.R.id.content)).perform(swipeFromCenterToTop());
就这样!通过这种方式,您可以控制屏幕上滚动的方式。咖啡师的scrollTo(R.id.button)
可用于各种可滚动视图,也可用于NestedScrollView
用浓缩咖啡解决这类问题很有用。我们开发和使用它只是为了快速可靠地编写浓缩咖啡测试。这里有一个链接:下面是我如何做@miszmaniac在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
)
}
类滚动动作(
private val original:android.support.test.espresso.action.ScrollToAction=android.support.test.espresso.action.ScrollToAction()
):查看原始操作{
override fun getConstraints():Matcher=anyOf(
全部(
具有有效的可见性(Visibility.VISIBLE),
IsDescendatofa(isAssignableFrom(NestedScrollView::class.java)),
原始约束条件
)
}
我不得不测试recyclerview项目。我的RecyclerView位于一个协调布局内的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
}
}
}
}
第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()
}
视图显示后,您可以执行测试用例。您的嵌套滚动视图是否位于协调布局中?我尝试了这个方法,但它似乎只有在NestedScrollView
不在CoordinatorLayout
中时才起作用@ivacf在CoordinatorLayout
中对我有效回答可能会帮助你滚动到嵌套的scrollview,因为它包含匹配程序的完整代码。在我的例子中,我有CoordinatorLayout->ViewPager->NestedScrollView和scrollTo()不起作用。我更新了脚本,现在可以滚动了,谢谢@miszmaniac。更新的脚本:您可以像下面那样使用ViewInteraction appCompatButton3=onView(allOf(withId(R.id.profile\u btn\u save),withText(“保存更改”);appCompatButton3.perform(嵌套的ScrollViewScrollToAction.scrollTo(),click());我有一个问题,RecycleView在NestedScrollview中。我不能使用recycleview.scrollToPosition(X),信息技术
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
}
}
}
}
fun ViewInteraction.isNotDisplayed(): Boolean {
return try {
check(matches(not(isDisplayed())))
true
} catch (e: Error) {
false
}
}
if (onView(atPositionOnView(R.id.rv_items, pos, R.id.tv_item_name)).isNotDisplayed()) {
val appViews = UiScrollable(UiSelector().scrollable(true))
appViews.scrollForward() //appViews.scrollBackward()
}