web-dev-qa-db-ja.com

Espresso、NestedScrollViewまたはRecyclerViewがCoordinatorLayoutにあるときにスクロールが機能しない

CoordinatorLayoutは、scrollTo()RecyclerViewActions.scrollToPosition()などのEspressoアクションの動作を中断するように見えます。

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>
_

ViewActions.scrollTo()を使用してNestedScrollView内のビューにスクロールしようとすると、最初にPerformExceptionが表示されるという問題があります。これは、このアクションはScrollViewのみをサポートし、NestedScrollViewはそれを拡張しないためです。この問題の回避策について説明します ここ 、基本的にはscrollTo()にコードをコピーし、NestedScrollViewをサポートするように制約を変更できます。これは、NestedScrollViewCoordinatorLayout内にない場合に機能すると思われますが、CoordinatorLayout内に配置するとすぐに、スクロールアクションが失敗します。

RecyclerViewの問題

同じレイアウトの場合、NestedScrollViewRecyclerViewに置き換えると、スクロールの問題も発生します。

この場合、私はRecyclerViewAction.scrollToPosition(position)を使用しています。 NestedScrollViewとは異なり、ここではスクロールが行われているのがわかります。ただし、間違った位置にスクロールするようです。たとえば、最後の位置までスクロールすると、最後から2番目の位置が表示されますが、最後の位置は表示されません。 RecyclerViewCoordinatorLayoutから移動すると、スクロールは正常に機能します。

現時点では、この問題のため、CoordinatorLayoutを使用する画面用のEspressoテストを作成できません。同じ問題が発生したり、回避策を知っている人はいますか?

36
ivacf

これは、Espresso scrollTo()メソッドがレイアウトクラスを明示的にチェックし、ScrollViewおよびHorizo​​ntalScrollViewでのみ機能するために発生します。内部的には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());
}
26
Turnsole

これは、@ 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<View> = anyOf(
      allOf(
          withEffectiveVisibility(Visibility.VISIBLE),
          isDescendantOfA(isAssignableFrom(NestedScrollView::class.Java))),
      original.constraints
  )
}
16
tasomaniac

CoordinatorLayout-> ViewPager-> NestedScrollViewでこの問題が発生しました。同じscrollTo()動作を取得する簡単な回避策は、画面を上にスワイプするだけでした。

onView(withId(Android.R.id.content)).perform(ViewActions.swipeUp());
11
MR Mido

この問題は(おそらくOPによって?)報告されています。参照 問題203684

その問題へのコメントの1つは、NestedScrollViewがCoordinatorLayout内にある場合の問題の回避策を示唆しています。

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

次の方法でテストを機能させることができました。

  1. カスタムのscrollTo()アクションを作成する(OPおよびターンソールによって参照される)
  2. ここに示すように、NestedScrollViewのレイアウトパラメータを削除する
4
Clo Knibbe

バリスタのscrollTo(R.id.button)は、あらゆる種類のスクロール可能なビューで動作し、NestedScrollViewでも動作します。

Espressoでこの種の問題を修正すると便利です。 Espressoのテストを高速で信頼性の高い方法で作成するためだけに開発して使用しています。そしてここにリンクがあります: https://github.com/SchibstedSpain/Barista

3
Roc Boronat

ミド氏の解決策は、状況によってはうまくいくかもしれませんが、常にうまくいくとは限りません。画面の下部にビューがある場合、クリックがRecyclerViewの外側で開始されるため、RecyclerViewのスクロールは行われません。

この問題を回避する1つの方法は、カスタム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());

以上です!このようにして、画面でスクロールがどのように行われるかを制御できます。

NestedScrollViewScrollToActionクラスを作成しました。

代わりにアクティビティ固有のものを作成するのに適していると思います。

言及する価値がある唯一のものは、コードが親のnestedScrollViewを検索し、そのCoordinatorLayoutの動作を削除することです。

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

2
miszmaniac