20

Sembra CoordinatorLayout rompe il comportamento delle azioni Espresso quali scrollTo() o RecyclerViewActions.scrollToPosition().Espresso, lo scorrimento non funziona quando NestedScrollView o RecyclerView è in CoordinatorLayout

Problema con NestedScrollView

Per un layout come questo:

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

Se provo a scorrere a qualsiasi vista all'interno del NestedScrollView utilizzando ViewActions.scrollTo() il primo problema che ho trovato è che ho un PerformException. Questo perché questa azione supporta solo ScrollView e NestedScrollView non la estende. Una soluzione per questo problema è spiegata here, in pratica possiamo copiare il codice in scrollTo() e modificare i vincoli per supportare NestedScrollView. Questo sembra funzionare se lo NestedScrollView non è in uno CoordinatorLayout ma non appena lo si inserisce in uno CoordinatorLayout l'azione di scorrimento non riesce.

Problema con RecyclerView

Per lo stesso layout, se si sostituisce la NestedScrollView con un RecyclerView c'è anche problemi con il lo scorrimento.

In questo caso sto usando RecyclerViewAction.scrollToPosition(position). A differenza del NestedScrollView, qui posso vedere alcuni eventi di scorrimento. Tuttavia, sembra che scorra nella posizione sbagliata. Ad esempio, se si scorre fino all'ultima posizione, rende visibile la penultima ma non l'ultima. Quando sposto lo RecyclerView dello CoordinatorLayout, lo scorrimento funziona come dovrebbe.

Al momento non è possibile scrivere alcun test Espresso per gli schermi che utilizzano CoordinatorLayout a causa di questi problemi. Qualcuno sta vivendo gli stessi problemi o conosce una soluzione alternativa?

+0

Ho un problema, in cui RecycleView si trova all'interno di NestedScrollview. Non riesco a utilizzare recycleview.scrollToPosition (X); , semplicemente non funziona. Ho provato tutto negli ultimi 6 giorni, ma posso superarlo. qualche suggerimento? Sarei molto grato! – Karoly

risposta

16

Questo accade perché il metodo scrollTo() Espresso controlla esplicitamente la classe di layout e funziona solo per ScrollView & HorizontalScrollView. Internamente utilizza View.requestRectangleOnScreen (...) quindi mi aspetto che funzioni davvero bene per molti layout.

La mia soluzione alternativa per NestedScrollView era di prendere ScrollToAction e modificare tale vincolo. L'azione modificata ha funzionato bene per NestedScrollView con quella modifica.

metodo

Cambiato in classe ScrollToAction:

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

Metodo pratico:

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

Il tuo 'NestedScrollView' all'interno di un' CoordinatorLayout'? Ho provato questo e sembra funzionare solo se il 'NestedScrollView' non è in un' CoordinatorLayout'. – ivacf

+0

@ivacf Lavora per me in un 'CoordinatorLayout' –

3

Questo problema è stato segnalato (forse dal PO?), Vedi Issue 203684

Uno dei commenti a questo tema suggerisce un work-around per il problema quando il NestedScrollView si trova all'interno di un CoordinatorLayout:

è necessario rimuovere il comportamento @string/appbar_scrolling_view_behavior layout del ScrollingView o qualsiasi vista genitore questa ScrollingView è incluso nel

Ecco un'implementazione di questo work-around:

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

sono stato in grado di ottenere le mie prove di lavoro da:

  1. Fare un'azione personalizzata scrollTo() (come riferimento il PO e Turnsole)
  2. Rimozione params di layout del NestedScrollView come mostrato qui
2

Ho creato una classe NestedScrollViewScrollToAction.

Penso che sia il posto migliore per fare attività specifiche per attività lì invece.

L'unica cosa che vale la pena menzionare è che il codice cerca il parent nestedScrollView e rimuove il suo comportamento di CoordinatorLayout.

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

+0

Nel mio caso ho CoordinatorLayout-> ViewPager-> NestedScrollView e scrollTo() non funziona. Ho aggiornato lo script e ora scorre, grazie a @miszmaniac. script aggiornato: https://gist.github.com/maydin/677c983c11d6a75c90186c09366fef2f È possibile utilizzare come qui di seguito ViewInteraction appCompatButton3 = OnView ( allof (withId (R.id.profile_btn_save), withText ("Salva modifiche"))); appCompatButton3.perform (NestedScrollViewScrollToAction.scrollTo(), click()); – Murat

5

ho avuto questo problema con CoordinatorLayout-> ViewPager-> NestedScrollView un lavoro facile in giro da me per ottenere lo stesso comportamento scrollTo() è stato quello di strisciare solo sullo schermo:

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

Non è la soluzione migliore ... ma funziona! –

+0

Se nella parte inferiore dello schermo è presente un pulsante, questa soluzione non funzionerà = / –

0

La soluzione di Mr Mido potrebbe funzionare in alcune situazioni, ma non sempre. Se si dispone di una vista nella parte inferiore dello schermo, lo scorrimento di RecyclerView non si verificherà perché il clic inizierà all'esterno di RecyclerView.

Un modo per ovviare a questo problema è scrivere una SwipeAction personalizzata. Come questo:

1 - Creare il 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 - Creare il metodo per restituire il 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 - quindi utilizzarlo per scorrere la schermata:

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

E questo è tutto! In questo modo puoi controllare il modo in cui lo scrolling avverrà sullo schermo.

1

Barista's scrollTo(R.id.button) funziona su tutti i tipi di viste scorrevoli, anche su NestedScrollView.

È utile risolvere questo tipo di problemi con Espresso. Lo sviluppiamo e lo usiamo solo per scrivere test Espresso in modo rapido e affidabile. Ed ecco un link: https://github.com/SchibstedSpain/Barista

0

Ecco come ho fatto la stessa cosa che @miszmaniac ha fatto in Kotlin. Con delegation in Kotlin, è molto più semplice e pulito perché non devo sovrascrivere i metodi che non è necessario.

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