J'utilise le nouveau RecyclerView-Layout
dans une SwipeRefreshLayout
et j'ai eu un comportement étrange. Lorsque vous faites défiler la liste vers le haut, la vue du dessus est parfois masquée.
Si j'essaie de faire défiler vers le haut maintenant - les déclencheurs Pull-To-Refresh.
Si j'essaie de supprimer la disposition de balayage dans la vue Recycler, le problème a disparu. Et sa reproductible sur n'importe quel téléphone (pas seulement les périphériques L-Preview).
<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>
C’est ma disposition: les lignes sont créées dynamiquement par RecyclerViewAdapter (2 types de vues dans cette liste).
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;
}
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 de la Fragment
contenant la Recycler
et la SwipeRefreshLayout
.
Si quelqu'un d'autre a vécu ce comportement et l'a résolu ou au moins en a trouvé la raison?
Avant d’utiliser cette solution: RecyclerView n’est pas encore complet, N'ESSAYEZ PAS DE L’UTILISER DANS LA PRODUCTION SAUF SI VOUS ÊTES COMME MOI!
En ce qui concerne novembre 2014, il existe encore des bogues dans RecyclerView qui pourraient causer le renvoi prématuré de canScrollVertically
. Cette solution résoudra tous les problèmes de défilement.
La solution en goutte:
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);
}
}
Vous n'avez même pas besoin de remplacer RecyclerView
dans votre code par FixedRecyclerView
, le remplacement de la balise XML serait suffisant! (Cela garantit que lorsque RecyclerView est terminé, la transition serait simple et rapide)
Explication:
Fondamentalement, canScrollVertically(boolean)
renvoie faux trop tôt, nous vérifions donc si la variable RecyclerView
est défilée jusqu'en haut de la première vue (où le haut du premier enfant serait 0), puis revient.
EDIT: .__ Et si vous ne voulez pas étendre RecyclerView
pour une raison quelconque, vous pouvez étendre SwipeRefreshLayout
, écraser la méthode canChildScrollUp()
et y insérer la logique de vérification.
EDIT2: RecyclerView a été publié et jusqu’à présent, il n’était pas nécessaire d’utiliser ce correctif.
écrire le code suivant dans addOnScrollListener
de la 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 pas du tout dans samsung galaxy s2. Lors du débogage, recyclerView.getChildAt (0) .getTop () est toujours incorrect pour RecyclerView. Ainsi, après avoir étudié différentes méthodes, j'ai pensé que nous pouvions utiliser la méthode findFirstCompletelyVisibleItemPosition () de LayoutManager pour prédire si le premier élément de RecyclerView est visible ou non, pour activer SwipeRefreshLayout.Recherchez le code ci-dessous. J'espère que cela aidera quelqu'un qui essaie 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);
}
});
Voici comment j'ai résolu ce problème dans mon cas. Cela pourrait être utile pour quelqu'un d'autre qui se retrouve ici pour chercher des solutions similaires à celle-ci.
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 pourra certainement aider quelqu'un qui recherche une solution similaire.
Code sourcehttps://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);
}
});
Si quelqu'un trouve cette question et n'est pas satisfait de la réponse:
Il semble que SwipeRefreshLayout n'est pas compatible avec les adaptateurs ayant plus d'un type d'élément.
La solution de krunal est bonne, mais cela fonctionne comme un correctif et ne couvre pas certains cas spécifiques, par exemple celui-ci:
Supposons que le RecyclerView contient un EditText au milieu de l'écran. Nous commençons l'application (topRowVerticalPosition = 0), tapotez sur le EditText. En conséquence, le clavier logiciel apparaît, la taille de RecyclerView est réduite, le système le fait défiler automatiquement pour que EditText reste 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, AttributeSet 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, un événement tactile est automatiquement envoyé à SupportSwipeRefreshLayout s'il est impossible de faire défiler RecyclerView et à RecyclerView sinon.
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,AttributeSet 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()<absListView.getPaddingTop());
}
else
return ViewCompat.canScrollVertically(target,-1);
}
}
malheureusement, il s'agit d'un bogue connu de LinearLayoutManager. Il ne compute pas correctementScrollOffset lorsque le premier élément est visible .
Je rencontre le même problème. Ma solution est de surcharger la méthode onScrolled
de OnScrollListener
.
La solution 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);
}
}
});
Solution simple ligne.
setOnScrollListener est obsolète.
Vous pouvez utiliser setOnScrollChangeListener pour le même but, comme ceci:
recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));
J'ai connu le même problème. Je l'ai résolu en ajoutant un écouteur de défilement qui attendra que le premier élément visible attendu soit dessiné sur la RecyclerView
. Vous pouvez également lier d'autres auditeurs de défilement, le long de celui-ci. La première valeur visible attendue est ajoutée pour l'utiliser comme position de seuil lorsque la variable SwipeRefreshLayout
doit être activée dans les cas où vous utilisez des détenteurs de vue d'en-tête.
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);