RecyclerView et SwipeRefreshLayout

J’utilise le nouveau RecyclerView-Layout dans un SwipeRefreshLayout et SwipeRefreshLayout eu un comportement étrange. Lorsque vous faites défiler la liste vers le haut, la vue du haut est parfois masquée.

Liste déroulée vers le haut

Si j’essaie de faire défiler vers le haut maintenant – les déclencheurs Pull-To-Refresh.

ligne coupée

Si j’essaie de supprimer la mise en page Swipe-Refresh-Layout autour du Recycler-View, le problème a disparu. Et c’est reproductible sur n’importe quel téléphone (pas seulement les appareils L-Preview).

     

C’est ma mise en page – les lignes sont construites dynamicment par RecyclerViewAdapter (2 types de vues dans cette liste).

 public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter { 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; } 

Voici l’adaptateur.

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

Et le code du Fragment contenant le Recycler et le SwipeRefreshLayout .

Si quelqu’un d’autre a connu ce comportement et l’a résolu ou du moins en a trouvé la raison?

Avant d’utiliser cette solution: RecyclerView n’est pas encore terminé, ESSAYEZ DE NE PAS L’UTILISER EN PRODUCTION SAUF SI VOUS ÊTES COMME MOI!

En novembre 2014, il y a encore des bogues dans RecyclerView qui provoqueraient le retour prématuré de false de la canScrollVertically de canScrollVertically . Cette solution résoudra tous les problèmes de défilement.

La goutte en solution:

 public class FixedRecyclerView extends RecyclerView { public FixedRecyclerView(Context context) { super(context); } public FixedRecyclerView(Context context, AtsortingbuteSet attrs) { super(context, attrs); } public FixedRecyclerView(Context context, AtsortingbuteSet 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); } } 

Vous n'avez même pas besoin de remplacer RecyclerView dans votre code avec FixedRecyclerView , le remplacement de la balise XML serait suffisant! (Cela garantit que lorsque RecyclerView sera terminé, la transition sera simple et rapide)

Explication:

Fondamentalement, canScrollVertically(boolean) renvoie false trop tôt, donc nous vérifions si le RecyclerView défile complètement en haut de la première vue (où le premier sumt de l'enfant serait 0), puis retourne.

EDIT: Et si vous ne voulez pas étendre RecyclerView pour une raison quelconque, vous pouvez étendre SwipeRefreshLayout et remplacer la méthode canChildScrollUp() et y placer la logique de vérification.

EDIT2: RecyclerView a été publié et jusqu'à présent, il n'est pas nécessaire d'utiliser ce correctif.

écrire le code suivant dans addOnScrollListener du RecyclerView

Comme ça:

  recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){ @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { int topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop(); swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0); } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } }); 

Je suis tombé sur le même problème récemment. J’ai essayé l’approche suggérée par @Krunal_Patel, mais cela a fonctionné la plupart du temps dans mon Nexus 4 et n’a pas fonctionné du tout dans samsung galaxy s2. Lors du débogage, recyclerView.getChildAt (0) .getTop () est toujours incorrect pour RecyclerView. Donc, après avoir passé en revue différentes méthodes, j’ai pensé que nous pouvions utiliser la méthode findFirstCompletelyVisibleItemPosition () du LayoutManager pour prédire si le premier élément de RecyclerView est visible ou non, pour activer SwipeRefreshLayout.Trouvez le code ci-dessous. J’espère que cela aide quelqu’un à essayer de résoudre le même problème. À votre santé.

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

C’est comme ça que j’ai résolu ce problème dans mon cas. Cela peut être utile pour quelqu’un qui se retrouve ici pour chercher des solutions similaires.

 recyclerView.addOnScrollListener(new OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // TODO Auto-generated method stub super.onScrolled(recyclerView, dx, dy); } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { // TODO Auto-generated method stub //super.onScrollStateChanged(recyclerView, newState); int firstPos=linearLayoutManager.findFirstCompletelyVisibleItemPosition(); if (firstPos>0) { swipeLayout.setEnabled(false); } else { swipeLayout.setEnabled(true); } } }); 

J’espère que cela pourrait certainement aider quelqu’un qui cherche une solution similaire.

Code source https://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM

recyclerView.setOnScrollListener (nouveau RecyclerView.OnScrollListener () {

  public void onScrollStateChanged(RecyclerView recyclerView, int newState) { } public void onScrolled(RecyclerView recyclerView, int dx, int dy) { swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0); } }); 

Malheureusement, il s’agit d’un bogue connu dans LinearLayoutManager. Il ne calcule pas correctement ScrollOffset lorsque le premier élément est visible. sera corrigé quand il sera libéré.

J’ai vécu le même problème. Je l’ai résolu en ajoutant un listener de défilement qui attendra que le premier élément visible attendu soit dessiné sur RecyclerView . Vous pouvez également lier d’autres écouteurs de défilement, le long de celui-ci. La première valeur visible attendue est ajoutée pour être utilisée comme position de seuil lorsque SwipeRefreshLayout doit être activé dans les cas où vous utilisez des supports de vue d’en-tête.

 public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener { private List mScrollListeners = new ArrayList(); 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); 

Je rencontre le même problème. Ma solution onScrolled méthode OnScrollListener d’ OnScrollListener .

La solution de contournement est ici:

  recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int offset = dy - ydy;//to adjust scrolling sensitivity of calling OnRefreshListener ydy = dy;//updated old value boolean shouldRefresh = (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) && (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) && offset > 30; if (shouldRefresh) { swipeRefreshLayout.setRefreshing(true); } else { swipeRefreshLayout.setRefreshing(false); } } }); 

Voici un moyen de gérer cela, qui gère également ListView / GridView.

 public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout { public SwipeRefreshLayout(Context context) { super(context); } public SwipeRefreshLayout(Context context,AtsortingbuteSet attrs) { super(context,attrs); } @Override public boolean canChildScrollUp() { View target=getChildAt(0); if(target instanceof AbsListView) { final AbsListView absListView=(AbsListView)target; return absListView.getChildCount()>0 &&(absListView.getFirstVisiblePosition()>0||absListView.getChildAt(0) .getTop() 

La solution de krunal est bonne, mais elle fonctionne comme un correctif et ne couvre pas certains cas spécifiques, par exemple celui-ci:

Disons que RecyclerView contient un EditText au milieu de l’écran. Nous démarrons l’application (topRowVerticalPosition = 0), tapotons sur le EditText. En conséquence, le clavier du logiciel apparaît, la taille de RecyclerView est réduite, il est automatiquement défilé par le système pour garder le EditText visible et topRowVerticalPosition ne doit pas être 0, mais onScrolled n’est pas appelé et topRowVerticalPosition n’est pas recalculé.

Par conséquent, je suggère cette solution:

 public class SupportSwipeRefreshLayout extends SwipeRefreshLayout { private RecyclerView mInternalRecyclerView = null; public SupportSwipeRefreshLayout(Context context) { super(context); } public SupportSwipeRefreshLayout(Context context, AtsortingbuteSet attrs) { super(context, attrs); } public void setInternalRecyclerView(RecyclerView internalRecyclerView) { mInternalRecyclerView = internalRecyclerView; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mInternalRecyclerView.canScrollVertically(-1)) { return false; } return super.onInterceptTouchEvent(ev); } } 

Une fois que vous avez spécifié RecyclerView interne à SupportSwipeRefreshLayout, il enverra automatiquement l’événement tactile à SupportSwipeRefreshLayout si RecyclerView ne peut pas être affiché et recyclerView autrement.

Solution de ligne unique.

setOnScrollListener est obsolète.

Vous pouvez utiliser setOnScrollChangeListener pour le même object comme ceci:

 recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));