La vista del riciclatore all'interno di NestedScrollView fa iniziare lo scorrimento nel mezzo


129

Ricevo uno strano comportamento di scorrimento quando aggiungo un RecyclerView all'interno di un NestedScrollView.

Quello che succede è che ogni volta che la vista di scorrimento ha più righe di quelle che possono essere visualizzate sullo schermo, non appena viene avviata l'attività, NestedScrollView inizia con un offset dall'alto (immagine 1). Se ci sono pochi elementi nella vista di scorrimento in modo che possano essere mostrati tutti in una volta, ciò non accade (immagine 2).

Sto usando la versione 23.2.0 della libreria di supporto.

Immagine 1 : SBAGLIATO: inizia con l'offset dall'alto

Immagine 1

Immagine 2 : CORRETTO: alcuni elementi nella vista del riciclaggio

Immagine 2

Sto incollando sotto il mio codice di layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp"
                android:orientation="vertical">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Title:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="@dimen/bodyPadding"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:text="Neque porro quisquam est qui dolorem ipsum"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Subtitle:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:padding="@dimen/bodyPadding"
                    android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

            </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:focusable="false"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

Mi sto perdendo qualcosa? Qualcuno ha idea di come risolvere questo problema?

Aggiornamento 1

Funziona correttamente se inserisco il seguente codice durante l'inizializzazione della mia attività:

sv.post(new Runnable() {
        @Override
        public void run() {
            sv.scrollTo(0,0);
        }
});

Dove sv è un riferimento a NestedScrollView, tuttavia sembra un bel trucco.

Aggiornamento 2

Come richiesto, ecco il mio codice adattatore:

public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {

    private List<T> mObjects;

    public ArrayAdapter(final List<T> objects) {
        mObjects = objects;
    }

    /**
     * Adds the specified object at the end of the array.
     *
     * @param object The object to add at the end of the array.
     */
    public void add(final T object) {
        mObjects.add(object);
        notifyItemInserted(getItemCount() - 1);
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        final int size = getItemCount();
        mObjects.clear();
        notifyItemRangeRemoved(0, size);
    }

    @Override
    public int getItemCount() {
        return mObjects.size();
    }

    public T getItem(final int position) {
        return mObjects.get(position);
    }

    public long getItemId(final int position) {
        return position;
    }

    /**
     * Returns the position of the specified item in the array.
     *
     * @param item The item to retrieve the position of.
     * @return The position of the specified item.
     */
    public int getPosition(final T item) {
        return mObjects.indexOf(item);
    }

    /**
     * Inserts the specified object at the specified index in the array.
     *
     * @param object The object to insert into the array.
     * @param index  The index at which the object must be inserted.
     */
    public void insert(final T object, int index) {
        mObjects.add(index, object);
        notifyItemInserted(index);

    }

    /**
     * Removes the specified object from the array.
     *
     * @param object The object to remove.
     */
    public void remove(T object) {
        final int position = getPosition(object);
        mObjects.remove(object);
        notifyItemRemoved(position);
    }

    /**
     * Sorts the content of this adapter using the specified comparator.
     *
     * @param comparator The comparator used to sort the objects contained in this adapter.
     */
    public void sort(Comparator<? super T> comparator) {
        Collections.sort(mObjects, comparator);
        notifyItemRangeChanged(0, getItemCount());
    }
}

Ed ecco il mio ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {
    private TextView txt;
    public ViewHolder(View itemView) {
        super(itemView);
        txt = (TextView) itemView;
    }

    public void render(String text) {
        txt.setText(text);
    }
}

Ed ecco il layout di ogni elemento in RecyclerView (è solo android.R.layout.simple_spinner_item- questa schermata è solo per mostrare un esempio di questo bug):

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:textAlignment="inherit"/>

provato con android:focusableInTouchMode="false"per RecyclerView? smth chiaramente sta "forzando il layout ad andare in fondo ... (dove inizia quando hai 1295 elementi? in basso o solo un piccolo offset superiore come il primo schermo)
snachmsm

Imposta android: clipToPadding = “true” su NestedScrollView.
natario

anche: mantenere una vista scorrevole all'interno di un'altra vista scorrevole allo stesso modo non è un modello molto buono ... spero che tu stia usando correttamenteLayoutManager
snachmsm

Ho provato entrambi i tuoi suggerimenti e sfortunatamente non ha funzionato. @snachmsm, indipendentemente dal numero di elementi nella vista del riciclaggio, l'offset è sempre lo stesso. Se posizionare un RecyclerView in un NestedScrollView è un buon modello, questo è stato in realtà raccomandato da un ingegnere di Google all'indirizzo plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
Anche io ho lo stesso problema quando utilizzo RecyclerView in NestedScrollView. È un modello negativo perché il modello di riciclaggio stesso non funzionerà. Tutte le viste verranno disegnate alla volta (poiché WRAP_CONTENT richiede l'altezza della vista del riciclatore). Non ci sarà alcun riciclaggio delle viste in background, quindi lo scopo principale della vista del riciclatore stesso non funziona. Ma è facile gestire i dati e disegnare il layout utilizzando la vista Riciclatore, questa è l'unica ragione per cui puoi usare questo modello. Quindi è meglio non usarlo fino a quando non lo si richiede di sicuro.
Ashok Varma,

Risposte:


253

Ho risolto tale problema impostando:

<ImageView ...
android:focusableInTouchMode="true"/>

a mio avviso sopra RecyclerView (che era nascosto dopo scorrimento indesiderato). Prova a impostare questa proprietà su LinearLayout sopra RecyclerView o su LinearLayout che è il contenitore di RecyclerView (mi ha aiutato in un altro caso).

Come vedo nella fonte NestedScrollView, tenta di focalizzare il primo figlio possibile su onRequestFocusInDescendants e se solo RecyclerView è focalizzabile vince.

Modifica (grazie a Waran): e per uno scorrimento uniforme non dimenticare di impostare yourRecyclerView.setNestedScrollingEnabled(false);


12
A proposito, è necessario aggiungere yourRecyclerView.setNestedScrollingEnabled (false); per facilitare lo scorrimento
Waran-

1
@Kenji solo per la prima visualizzazione all'interno di LinearLayout in cui è posizionato RecyclerView. O a quel LinearLayout (contenitore RecyclerView) stesso se la prima modifica non aiuta.
Dmitry Gavrilko,

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

3
Puoi anche semplicemente impostare android:focusableInTouchMode="false"su recyclerView in modo da non dover impostare true per tutte le altre viste.
Aksiom,

1
Concordo con Dmitry Gavrilko, ha funzionato dopo aver impostato android: focusableInTouchMode = "true" per il linearlayout che contiene recyclerview. @Aksiom setting android: focusableInTouchMode = "false" su recyclerView non ha funzionato per me.
Shendre Kiran,

103

Nel vostro LinearLayoutimmediato NestedScrollViewutilizzo, utilizzate android:descendantFocusabilitynel modo seguente

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp"
        android:descendantFocusability="blocksDescendants">

MODIFICARE

Dal momento che molti di loro ottengono questa risposta utile, forniranno anche spiegazioni.

L'uso di descendantFocusabilityè dato qui . E come di focusableInTouchModesopra qui . Quindi usando blocksDescendantsindescendantFocusability non consente al figlio di ottenere la messa a fuoco mentre si tocca e quindi il comportamento non pianificato può essere fermato.

Per quanto riguarda focusInTouchMode, entrambi AbsListViewe RecyclerViewchiama il metodosetFocusableInTouchMode(true); nel loro costruttore per impostazione predefinita, quindi non è necessario utilizzare tale attributo nei layout XML.

E per il NestedScrollViewseguente metodo viene utilizzato:

private void initScrollView() {
        mScroller = ScrollerCompat.create(getContext(), null);
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

Qui, al setFocusable()posto di viene usato il metodo setFocusableInTouchMode(). Ma secondo questo post ,focusableInTouchMode dovrebbe essere evitato a meno che per determinate condizioni in quanto rompe la coerenza con il normale comportamento di Android. Un gioco è un buon esempio di un'applicazione che può fare buon uso della proprietà focalizzabile in modalità touch. MapView, se utilizzato a schermo intero come in Google Maps, è un altro buon esempio di dove è possibile utilizzare correttamente la messa a fuoco in modalità touch.


Grazie! Funziona nel mio caso. Sfortunatamente android: focusableInTouchMode = "true" non ha funzionato per me.
Mikhail,

1
Questo ha funzionato per me. Ho aggiunto questa riga di codice alla vista principale del mio recyclerview (nel mio caso, era un LinearLayout) e ha funzionato come un fascino.
amzer

Stavo usando NestedScrollView seguito da LinearLayout che contiene RecyclerView e EditText. Il comportamento di scorrimento è stato risolto utilizzando android: descendantFocusability = "blocksDescendants" all'interno di LinearLayout ma EditText non può ottenere lo stato attivo. Qualche idea di cosa stia succedendo?
Sagar Chapagain,

@SagarChapagain È la proprietà di blocksDescendantsbloccare l'attenzione di tutti i discendenti. Non ne sono sicuro, ma provabeforeDescendants
Jimit Patel, il

2
@JimitPatel il mio problema è stato risolto utilizzandorecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain il

16
android:descendantFocusability="blocksDescendants"

inside LinearLayout Ha funzionato per me.


6
Significa che il focus sulle viste all'interno sarà bloccato e gli utenti non saranno in grado di raggiungerli?
sviluppatore Android

11

Ho avuto lo stesso problema e mi sono comportato estendendo NestedScrollView e disabilitando la focalizzazione dei bambini. Per qualche motivo, RecyclerView ha sempre richiesto la messa a fuoco anche quando ho appena aperto e chiuso il cassetto.

public class DummyNestedScrollView extends NestedScrollView {
public DummyNestedScrollView(Context context) {
    super(context);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

/**
 * Fixind problem with recyclerView in nested scrollview requesting focus
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param child
 * @param focused
 */
@Override
public void requestChildFocus(View child, View focused) {
    Log.d(getClass().getSimpleName(), "Request focus");
    //super.requestChildFocus(child, focused);

}


/**
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param direction
 * @param previouslyFocusedRect
 * @return
 */
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    Log.d(getClass().getSimpleName(), "Request focus descendants");
    //return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    return false;
}
}

Funziona ma rompe anche il concetto di messa a fuoco e puoi facilmente ottenere la situazione con due campi focalizzati sullo schermo. Quindi usalo solo se non hai EditText all'interno dei RecyclerViewsupporti.
Andrey Chernoprudov l'

4

Nel mio caso questo codice risolve il mio problema

RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);

recyclerView.setFocusable(false);
nestedScrollView.requestFocus();

//populate recyclerview here

Il mio layout contiene un layout padre come NestedScrollView che ha un figlio LinearLayout. LinearLayout ha orientamento "verticale" e RecyclerView e EditText secondari. Riferimento


2

Ho due ipotesi.

Primo: prova a inserire questa riga su NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Secondo: utilizzare

<android.support.design.widget.CoordinatorLayout

come vista dei tuoi genitori In questo modo

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:orientation="vertical"
                      android:padding="16dp">

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Title:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Subtitle:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

        </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:focusable="false"/>

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

La mia ultima possibile soluzione. Lo giuro :)


E l'aggiunta di CoordinatorLayout come vista genitore non ha funzionato neanche ... Grazie comunque
Luccas Correa

2

Questo problema si presenta a causa della vista di riciclaggio Focus.

Automaticamente tutta la messa a fuoco è andata a riciclare la vista se le sue dimensioni estendevano le dimensioni dello schermo.

aggiungendo android:focusableInTouchMode="true"alla prima ChildView come TextView, Buttone così via (non su ViewGroupcome Linear, Relativee così via) ha senso per risolvere il problema, ma API livello 25 e sopra soluzione non funziona.

Basta aggiungere questi 2 riga nel ChildView come TextView, Buttone così via (non su ViewGroupcome Linear, Relativee così via)

 android:focusableInTouchMode="true"
 android:focusable="true"

Ho appena affrontato questo problema al livello API 25. Spero che altre persone non perdano tempo in questo.

Per lo scorrimento uniforme su RecycleView aggiungi questa riga

 android:nestedScrollingEnabled="false"

ma aggiungendo questo attiributes funziona solo con livello API 21 o superiore. Se vuoi che lo scrolling scorrevole funzioni al di sotto del livello API 25, aggiungi questa riga nella tua classe

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);

0

Nel codice Java, dopo aver inizializzato recyclerView e impostato l'adattatore, aggiungere questa riga:

recyclerView.setNestedScrollingEnabled(false)

Puoi anche provare a avvolgere il layout con un relativo LAYout in modo che le viste rimangano nella stessa posizione ma recyclerView (quale scorrimento) sia il primo nella gerarchia XML. L'ultimo suggerimento è un tentativo disperato: p


0

Dato che sono in ritardo nel rispondere, ma può aiutare qualcun altro. Usa la versione precedente o successiva nel build.gradle a livello di app e il problema viene rimosso.

compile com.android.support:recyclerview-v7:23.2.1

0

per scorrere verso l'alto, basta chiamare questo in setcontentview:

scrollView.SmoothScrollTo(0, 0);

questo non fornisce una risposta alla domanda
Umar Ata,

0

Basta aggiungere android:descendantFocusability="blocksDescendants"il ViewGroup all'interno di NestedScrollView.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.