Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/234.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android Espresso,当NestedScrollView或RecyclerView处于协调布局时,滚动不起作用_Android_Android Recyclerview_Android Espresso_Coordinator Layout_Android Nestedscrollview - Fatal编程技术网

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()
            }