RecyclerView e SwipeRefreshLayout

? Lukas Olsen @ | Original: StackOverFlow
---

Sto utilizzando il nuovo RecyclerView - layout in un SwipeRefreshLayout e sperimentato un comportamento strano . Quando si scorre la lista torna a volte la vista sulla parte superiore viene tagliato in .

RecyclerView e SwipeRefreshLayout

Se provo a scorrere verso l'alto ora - il Pull - To - Refresh trigger .

RecyclerView e SwipeRefreshLayout

Se cerco di rimuovere il Swipe - Refresh - layout di tutto il Recycler -View il problema è andato. E la sua riproducibile su qualsiasi ( dispositivi non solo L - Preview) Telefono .

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/contentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/hot_fragment_recycler"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

Questo è il mio layout - le righe sono costruite dinamicamente dal RecyclerViewAdapter ( 2 Viewtypes in questo elenco ) .

public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter<GameRow> {

private static final int VIEWTYPE_GAME_TITLE = 0;
private static final int VIEWTYPE_GAME_TEAM = 1;

@Inject
Picasso picasso;

public HotRecyclerAdapter(Injector injector) {
    super(injector);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            TitleGameRowViewHolder holder = (TitleGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
        case VIEWTYPE_GAME_TEAM: {
            TeamGameRowViewHolder holder = (TeamGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            View view = inflater.inflate(R.layout.game_row_title, viewGroup, false);
            return new TitleGameRowViewHolder(view);
        }
        case VIEWTYPE_GAME_TEAM: {
            View view = inflater.inflate(R.layout.game_row_team, viewGroup, false);
            return new TeamGameRowViewHolder(view);
        }
    }
    return null;
}

@Override
public int getItemViewType(int position) {
    GameRow row = getItem(position);
    if (row.isTeamGameRow()) {
        return VIEWTYPE_GAME_TEAM;
    }
    return VIEWTYPE_GAME_TITLE;
}

Ecco l'adattatore .

   hotAdapter = new HotRecyclerAdapter(this);

    recyclerView.setHasFixedSize(false);
    recyclerView.setAdapter(hotAdapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

    contentView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            loadData();
        }
    });

    TypedArray colorSheme = getResources().obtainTypedArray(R.array.main_refresh_sheme);
    contentView.setColorSchemeResources(colorSheme.getResourceId(0, -1), colorSheme.getResourceId(1, -1), colorSheme.getResourceId(2, -1), colorSheme.getResourceId(3, -1));

E il codice del frammento che contiene il Recycler e SwipeRefreshLayout .

Se qualcun altro ha sperimentato questo comportamento e risolto, o almeno trovato la ragione ?

---

Top 5 Risposta

1krunal patel @

scrivere il seguente codice nel setOnScrollListener del RecyclerView

example:

        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(int i) {

        }

        @Override
        public void onScrolled(int i, int i2) {
            int topRowVerticalPosition =
                    (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
            swipeRefresh.setEnabled(topRowVerticalPosition >= 0);
        }
    });
2yigit @

purtroppo, questo è un bug noto in LinearLayoutManager . Non computeScrollOffset correttamente quando il primo elemento è visibile . sarà fissato quando viene rilasciato .

3Tom91136 @

Prima di utilizzare questa soluzione : RecyclerView non è ancora completa, NON CERCARE DI USARLO IN PRODUZIONE MENO siete come me !

Come per novembre 2014, ci sono ancora bug nel RecyclerView che potrebbero causare canScrollVertically per tornare falso prematuramente . Questa soluzione risolverà tutti i problemi di scorrimento .

Il calo in soluzione :

public class FixedRecyclerView extends RecyclerView {
    public FixedRecyclerView(Context context) {
        super(context);
    }

    public FixedRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FixedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean canScrollVertically(int direction) {
        // check if scrolling up
        if (direction < 1) {
            boolean original = super.canScrollVertically(direction);
            return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
        }
        return super.canScrollVertically(direction);

    }
}

Non c'è nemmeno bisogno di sostituire RecyclerView nel codice con FixedRecyclerView, sostituendo il tag XML sarebbe sufficiente ! ( Il assicura che quando RecyclerView è completa, la transizione sarebbe semplice e veloce )

Explanation:

In sostanza, canScrollVertically(boolean) restituisce false troppo presto, quindi controlliamo se la RecyclerView scorre tutto il senso alla parte superiore della prima vista (dove all'inizio del primo bambino sarebbe 0 ) e poi tornare .

EDIT : E se non si desidera estendere RecyclerView per qualche motivo, è possibile estendere SwipeRefreshLayout e sovrascrivere il metodo canChildScrollUp() e mettere la logica di controllo in là .

EDIT2 : RecyclerView è stato rilasciato e finora non c'è bisogno di utilizzare questa correzione .

4balachandarkm @

Mi sono imbattuto lo stesso problema da poco . Ho provato l'approccio suggerito da Krunal_Patel, ma ha funzionato la maggior parte delle volte nella mia Nexus 4 e non ha funzionato affatto in samsung galaxy s2 . Durante il debug, recyclerView.getChildAt ( 0 ) .getTop ( ) non è sempre corretta per RecyclerView . Così, dopo aver attraversato vari metodi, ho pensato che possiamo fare uso del metodo findFirstCompletelyVisibleItemPosition ( ) della LayoutManager di prevedere se il primo elemento del RecyclerView è visibile o meno, per consentire SwipeRefreshLayout.Find il codice qui sotto . Speranza che aiuta qualcuno che cerca di risolvere lo stesso problema . Applausi.

    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        }

        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
        }
    });
5Nikola Despotoski @

Ho sperimentato lo stesso problema . Ho risolto con l'aggiunta di scorrimento ascoltatore che aspettare fino atteso primo elemento visibile è disegnato sul RecyclerView . È possibile associare altri ascoltatori di scorrimento troppo, lungo questo. Previsto primo valore visibile viene aggiunto da utilizzare come posizione di soglia quando la SwipeRefreshLayout dovrebbe essere attivato nel caso in cui si utilizza la visualizzazione titolari di intestazione .

public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
        private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
        private int mExpectedVisiblePosition = 0;

        public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout mSwipeLayout) {
            this.mSwipeLayout = mSwipeLayout;
        }

        private SwipeRefreshLayout mSwipeLayout;
        public void addScrollListener(RecyclerView.OnScrollListener listener){
            mScrollListeners.add(listener);
        }
        public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
            return mScrollListeners.remove(listener);
        }
        public void setExpectedFirstVisiblePosition(int position){
            mExpectedVisiblePosition = position;
        }
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            notifyScrollStateChanged(recyclerView,newState);
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
            if(firstVisible != RecyclerView.NO_POSITION)
                mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);

        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            notifyOnScrolled(recyclerView, dx, dy);
        }
        private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrolled(recyclerView, dx, dy);
            }
        }
        private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrollStateChanged(recyclerView, newState);
            }
        }
    }

Usage:

SwipeRefreshLayoutToggleScrollListener listener = new SwipeRefreshLayoutToggleScrollListener(mSwiperRefreshLayout);
listener.addScrollListener(this); //optional
listener.addScrollListener(mScrollListener1); //optional
mRecyclerView.setOnScrollLIstener(listener);