A visualização Recycler dentro do NestedScrollView faz com que a rolagem inicie no meio

129

Estou tendo um comportamento estranho de rolagem quando adiciono um RecyclerView dentro de um NestedScrollView.

O que acontece é que sempre que a rolagem tem mais linhas do que as mostradas na tela, assim que a atividade é iniciada, o NestedScrollView começa com um deslocamento da parte superior (imagem 1). Se houver poucos itens na exibição de rolagem para que todos possam ser exibidos de uma só vez, isso não acontece (imagem 2).

Estou usando a versão 23.2.0 da biblioteca de suporte.

Imagem 1 : ERRADO - começa com o deslocamento do topo

Imagem 1

Imagem 2 : CORRETO - alguns itens na exibição do reciclador

Imagem 2

Estou colando abaixo do meu código de 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>

Estou esquecendo de algo? Alguém tem alguma ideia de como resolver isto?

Atualização 1

Funciona corretamente se eu inserir o seguinte código ao inicializar minha Atividade:

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

Onde sv é uma referência ao NestedScrollView, no entanto, parece um hack.

Atualização 2

Conforme solicitado, aqui está o código do meu adaptador:

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

E aqui está o meu 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);
    }
}

E aqui está o layout de cada item no RecyclerView (é apenas android.R.layout.simple_spinner_item- essa tela serve apenas para mostrar um exemplo desse 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"/>
Luccas Correa
fonte
tentou com android:focusableInTouchMode="false"para RecyclerView? smth claramente está "forçando a sua disposição para ir para baixo ... (onde começar quando você tem 1295 itens de topo inferior ou apenas pequena compensação como primeira tela?)
snachmsm
Defina android: clipToPadding = "true" para o seu NestedScrollView.
Natario 30/03
também: mantendo vista de rolagem dentro de outro view-mesmo-way de rolagem não é muito bom padrão ... espero que você está usando adequadaLayoutManager
snachmsm
Tentei as duas sugestões e infelizmente nem funcionou. @snachmsm, não importa o número de itens na exibição da recicladora, o deslocamento é sempre o mesmo. Para saber se a colocação de um RecyclerView dentro de um NestedScrollView é um bom padrão, isso foi realmente recomendado por um engenheiro do Google em plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa
1
Mesmo eu tenho o mesmo problema ao usar o RecyclerView dentro do NestedScrollView. É um padrão ruim porque o próprio padrão de reciclador não funciona. Todas as vistas serão desenhadas por vez (já que WRAP_CONTENT precisa da altura da vista do reciclador). Não haverá exibição de reciclagem em segundo plano, portanto o principal objetivo da exibição do reciclador não está funcionando. Mas é fácil gerenciar dados e desenhar o layout usando a exibição do reciclador, esse é o único motivo pelo qual você pode usar esse padrão. Então é melhor não usá-lo até que você precise com certeza.
Ashok Varma

Respostas:

253

Resolvi esse problema definindo:

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

na minha visão acima do RecyclerView (que estava oculto após rolagem indesejada). Tente definir essa propriedade como LinearLayout acima de RecyclerView ou LinearLayout, que é o contêiner de RecyclerView (me ajudou em outro caso).

Como vejo na fonte NestedScrollView, ele tenta focar o primeiro filho possível em onRequestFocusInDescendants e, se apenas o RecyclerView estiver focável, ele vence.

Editar (graças a Waran): e para uma rolagem suave, não se esqueça de definir yourRecyclerView.setNestedScrollingEnabled(false);

Dmitry Gavrilko
fonte
12
A propósito, você precisa adicionar yourRecyclerView.setNestedScrollingEnabled (false); a fim de tornar a rolagem suave
Waran-
1
@Kenji apenas para visualizar pela primeira vez dentro do LinearLayout onde o RecyclerView está localizado. Ou para o próprio LinearLayout (contêiner RecyclerView) se a primeira alteração não ajudar.
Dmitry Gavrilko
1
@DmitryGavrilko Tenho um problema, em que o RecycleView está dentro de um NestedScrollview. Não consigo usar recycleview.scrollToPosition (X); , simplesmente não funciona. Eu tentei de tudo nos últimos 6 dias, mas posso superar isso. alguma sugestão? Eu ficaria muito agradecido !
Karoly
3
Você também pode definir android:focusableInTouchMode="false"o recyclerView para não precisar definir true para todas as outras visualizações.
Aksiom
1
Concordo com Dmitry Gavrilko, trabalhou depois de definir o android: focusableInTouchMode = "true" para o layout linear que contém a visão de reciclagem. @Aksiom setting android: focusableInTouchMode = "false" para o recyclerView não funcionou para mim.
Shendre Kiran
103

No seu LinearLayoutimediato NestedScrollView, use android:descendantFocusabilityda seguinte maneira

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

EDITAR

Uma vez que muitos deles obtêm essa resposta útil, também fornecerão explicações.

O uso de descendantFocusabilityé dado aqui . E a partir de focusableInTouchModecima aqui . Portanto, usar blocksDescendantsin descendantFocusabilitynão permite que a criança obtenha foco enquanto toca e, portanto, o comportamento não planejado pode ser interrompido.

Quanto a focusInTouchMode, AbsListViewe RecyclerViewchama o método setFocusableInTouchMode(true);em seu construtor por padrão, portanto, não é necessário usar esse atributo em seus layouts XML.

E para o NestedScrollViewseguinte método é usado:

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

Aqui, o setFocusable()método é usado em vez de setFocusableInTouchMode(). Mas, de acordo com este post , focusableInTouchModedeve ser evitado, a menos que em determinadas condições, pois quebra a consistência com o comportamento normal do Android. Um jogo é um bom exemplo de aplicativo que pode fazer bom uso da propriedade focalizável no modo de toque. O MapView, se usado em tela cheia como no Google Maps, é outro bom exemplo de onde você pode usar o foco no modo de toque corretamente.

Jimit Patel
fonte
Obrigado! Isso funciona no meu caso. Infelizmente o android: focusableInTouchMode = "true" não funcionou para mim.
Mikhail
1
Isso funcionou para mim. Eu adicionei essa linha de código à visualização pai da minha visão de reciclagem (no meu caso, era um LinearLayout) e funcionou como um encanto.
Amzer
Eu estava usando NestedScrollView seguido por LinearLayout, que contém RecyclerView e EditText. O comportamento da rolagem foi corrigido usando o android: descendantFocusability = "blocksDescendants" dentro do LinearLayout, mas o EditText não pode obter foco. Alguma ideia do que está acontecendo?
Sagar Chapagain
@SagarChapagain É propriedade de blocksDescendantsbloquear o foco de todos os descendentes. Não tenho a certeza, mas tentebeforeDescendants
Jimit Patel
2
@JimitPatel meu problema foi resolvido usandorecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain
16
android:descendantFocusability="blocksDescendants"

inside LinearLayout Trabalhou para mim.

Subash Bhattarai
fonte
6
Mas isso significa que o foco nas visualizações internas será bloqueado e os usuários não poderão alcançá-las?
desenvolvedor android
11

Eu tive o mesmo problema e resolvi estendendo o NestedScrollView e desativando o foco em crianças. Por alguma razão, o RecyclerView sempre solicitava foco, mesmo quando eu apenas abria e fechava a gaveta.

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;
}
}
Lubos Horacek
fonte
Ele funciona, mas também quebra o conceito de foco e você pode facilmente obter uma situação com dois campos focados na tela. Portanto, use-o apenas se você não tiver um EditText dentro dos RecyclerViewsuportes.
Andrey Chernoprudov 11/03
4

No meu caso, esse código resolve meu problema

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

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

//populate recyclerview here

Meu layout contém um layout pai como NestedScrollView, que possui um LinearLayout filho. O LinearLayout tem a orientação "vertical" e os filhos RecyclerView e EditText. Referência

Sagar Chapagain
fonte
2

Eu tenho dois palpites.

Primeiro: tente colocar esta linha no seu NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Segundo: Uso

<android.support.design.widget.CoordinatorLayout

como seu pai vê assim

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

Minha última solução possível. Eu juro :)

Andre Haueisen
fonte
E adicionando o CoordinatorLayout como a vista pai não quer trabalhar ... Obrigado de qualquer maneira
Luccas Correa
2

Esse problema ocorre devido à reciclagem do foco da visualização.

Automaticamente todo o foco foi reciclado para a visualização se seu tamanho aumentasse o tamanho da tela.

adicionando android:focusableInTouchMode="true"a primeira ChildView como TextView, Buttone assim por diante (não em ViewGroupcomo Linear, Relativee assim por diante) faz sentido para resolver o problema, mas Nível API 25 e acima solução não funciona.

Basta adicionar estas 2 linhas no seu ChildView como TextView , Buttone assim por diante (não em ViewGroupcomo Linear, Relativee assim por diante)

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

Acabei de enfrentar esse problema no nível 25 da API. Espero que outras pessoas não percam tempo nisso.

Para uma rolagem suave ao reciclar, adicione esta linha

 android:nestedScrollingEnabled="false"

mas adicionar esses atributos só funciona com o nível 21 da API ou superior. Se você deseja que a rolagem de suavização funcione abaixo do nível 25 da API, adicione esta linha à sua classe

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

No código Java, após inicializar o recyclerView e configurar o adaptador, inclua esta linha:

recyclerView.setNestedScrollingEnabled(false)

Você também pode tentar agrupar o layout com um relativoLayout para que as visualizações permaneçam na mesma posição, mas recyclerView (qual rolagem) é o primeiro na hierarquia xml. A última sugestão, uma tentativa desesperada: p

johnny_crq
fonte
0

Como estou atrasado em responder, mas pode ajudar outra pessoa. Basta usar a versão abaixo ou superior no build.gradle no nível do aplicativo e o problema será removido.

compile com.android.support:recyclerview-v7:23.2.1
Amit
fonte
0

para rolar até o topo, basta chamar isso em setcontentview:

scrollView.SmoothScrollTo(0, 0);
user3844824
fonte
este não fornece resposta para a pergunta
Umar Ata
0

Basta adicionar android:descendantFocusability="blocksDescendants"o ViewGroup dentro do NestedScrollView.

Gilbert Parreno
fonte