web-dev-qa-db-fra.com

Android: Comment obtenir le décalage X actuel de RecyclerView?

J'utilise une Scrollview pour un "carrousel de sélecteurs de temps" infini et j'ai découvert que ce n'était pas la meilleure approche ( dernière question )

Maintenant, j'ai trouvé le Recycler View mais je ne peux pas obtenir le décalage de défilement actuel dans la direction X du recyclerView? (Disons que chaque élément a une largeur de 100 pixels et que le deuxième élément n'est visible que jusqu'à 50%, le décalage de défilement est donc de 150 pixels)

  • tous les articles ont la même largeur, mais il y a un espace vide avant le premier, après le dernier article
  • recyclerView.getScrollX() renvoie 0 (les docs disent: valeur de défilement initiale)
  • la LayoutManager a findFirstVisibleItemPosition, mais je ne peux pas calculer le décalage X avec cela

METTRE À JOUR

Je viens de trouver un moyen de suivre la position X tout en mettant à jour la valeur avec le callback onScrolled, mais je préférerais obtenir la valeur réelle au lieu de la suivre tout le temps!

private int overallXScroll = 0;
//...
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            overallXScroll = overallXScroll + dx;

            Log.i("check","overallXScroll->" + overallXScroll);

        }
    });
47
longilong

enter image description here

Solution 1: setOnScrollListener

Enregistrez votre position X en tant que variable de classe et mettez à jour chaque modification dans la variable onScrollListener. Assurez-vous de ne pas réinitialiser overallXScroll (f.e. onScreenRotationChange)

 private int overallXScroll = 0;
 //...
 mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        overallXScroll = overallXScroll + dx;
        Log.i("check","overall X  = " + overallXScroll);

    }
 });

Solution 2: Calcule la position actuelle.

Dans mon cas, j'ai une liste horizontale qui contient des valeurs de temps (0:00, 0:30, 1:00, 1:30 ... 23:00, 23:30). Je calcule l'heure à partir de l'élément temporel, qui se trouve au milieu de l'écran (point de calcul). C'est pourquoi j'ai besoin de la position exacte de X-Scroll de ma RecycleView

  • Chaque élément de temps a la même largeur de 60dp (fyi: 60dp = 30min, 2dp = 1min)
  • Le premier élément (élément d'en-tête) a un rembourrage supplémentaire, pour définir 0min au centre

    private int mScreenWidth = 0;
    private int mHeaderItemWidth = 0;
    private int mCellWidth = 0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    
      //init recycle views
      //...
      LinearLayoutManager mLLM = (LinearLayoutManager) getLayoutManager();
      DisplayMetrics displaymetrics = new DisplayMetrics();
      this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
      this.mScreenWidth = displaymetrics.widthPixels;
    
      //calculate value on current device
      mCellWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources()
            .getDisplayMetrics());
    
      //get offset of list to the right (gap to the left of the screen from the left side of first item)
      final int mOffset = (this.mScreenWidth / 2) - (mCellWidth / 2);
    
      //HeaderItem width (blue rectangle in graphic)
      mHeaderItemWidth = mOffset + mCellWidth;
    
      mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
    
            //get first visible item
            View firstVisibleItem = mLLM.findViewByPosition(mLLM.findFirstVisibleItemPosition());
    
            int leftScrollXCalculated = 0;
            if (firstItemPosition == 0){
                   //if first item, get width of headerview (getLeft() < 0, that's why I Use Math.abs())
                leftScrollXCalculated = Math.abs(firstVisibleItem.getLeft());
            }
            else{
    
                   //X-Position = Gap to the right + Number of cells * width - cell offset of current first visible item
                   //(mHeaderItemWidth includes already width of one cell, that's why I have to subtract it again)
                leftScrollXCalculated = (mHeaderItemWidth - mCellWidth) + firstItemPosition  * mCellWidth + firstVisibleItem.getLeft();
            }
    
            Log.i("asdf","calculated X to left = " + leftScrollXCalculated);
    
        }
    });
    

    }

45
longilong

RecyclerView a déjà une méthode pour obtenir un décalage de défilement horizontal et vertical 

mRecyclerView.computeHorizontalScrollOffset()
mRecyclerView.computeVerticalScrollOffset()

Cela fonctionnera pour RecyclerViews contenant des cellules de même hauteur (pour le décalage de défilement vertical) et de même largeur (pour le décalage de défilement horizontal).

49
Umar Qureshi

Merci à @ umar-qureshi pour la bonne avance! Il semble que vous puissiez déterminer le pourcentage de défilement avec Offset, Extent et Range tels que

percentage = 100 * offset / (range - extent)

Par exemple (à mettre dans une OnScrollListener):

int offset = recyclerView.computeVerticalScrollOffset();
int extent = recyclerView.computeVerticalScrollExtent();
int range = recyclerView.computeVerticalScrollRange();

int percentage = (int)(100.0 * offset / (float)(range - extent));

Log.i("RecyclerView, "scroll percentage: "+ percentage + "%");
26
jbohren
public class CustomRecyclerView extends RecyclerView {

    public CustomRecyclerView(Context context) {
        super(context);
    }

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

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

    public int getHorizontalOffset() {
        return super.computeHorizontalScrollOffset();
    }
}

Là où vous devez obtenir le décalage:

recyclerView.getHorizontalOffset()
7
SemaphoreMetaphor

Si vous avez besoin de connaître la page actuelle et son décalage par rapport à la largeur de RecyclerView, vous pouvez essayer quelque chose comme ceci:

recyclerView.addOnScrollListener(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);

        final int scrollOffset = recyclerView.computeHorizontalScrollOffset();
        final int width = recyclerView.getWidth();
        final int currentPage = scrollOffset / width;
        final float pageOffset = (float) (scrollOffset % width) / width;

        // the following lines just the example of how you can use it
        if (currentPage == 0) {
            leftIndicator.setAlpha(pageOffset);
        }

        if (adapter.getItemCount() <= 1) {
            rightIndicator.setAlpha(pageOffset);
        } else if (currentPage == adapter.getItemCount() - 2) {
            rightIndicator.setAlpha(1f - pageOffset);
        }
    }
});

Si currentPage a pour index 0 alors leftIndicator (flèche) sera transparent, il en sera de même pour rightIndicator s'il n'y a qu'une page (dans l'exemple, l'adaptateur a toujours au moins un élément à afficher, de sorte qu'il n'y a pas de contrôle pour la valeur vide).

De cette façon, vous aurez pratiquement les mêmes fonctionnalités que si vous utilisiez le callback pageScrolled from ViewPager.OnPageChangeListener.

2
Sébastien

en remplaçant RecyclerView onScrolled(int dx,int dy), vous pouvez obtenir le décalage de défilement. mais lorsque vous ajoutez ou supprimez l'élément, la valeur peut être fausse. 

public class TempRecyclerView extends RecyclerView {
   private int offsetX = 0;
   private int offsetY = 0;
   public TempRecyclerView(Context context) {
       super(context);
   }

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

   public TempRecyclerView(Context context, @Nullable AttributeSet attrs,int defStyle){
      super(context, attrs, defStyle);
   }
/**
 * Called when the scroll position of this RecyclerView changes. Subclasses 
   should usethis method to respond to scrolling within the adapter's data 
   set instead of an explicit listener.
 * <p>This method will always be invoked before listeners. If a subclass 
   needs to perform any additional upkeep or bookkeeping after scrolling but 
   before listeners run, this is a good place to do so.</p>
 */
   @Override
   public void onScrolled(int dx, int dy) {
  //  super.onScrolled(dx, dy);
      offsetX+=dx;
      offsetY+=dy;
     }
 }
0
chen