RecyclerView SnapHelper находит точное положение привязанного элемента

Как я могу найти индекс привязанного элемента? Я пытался использовать:

recyclerView.setOnFlingListener(new RecyclerView.OnFlingListener() {
        @Override
        public boolean onFling(int velocityX, int velocityY) {
            Log.i(TAG, "initRecyclerView: SNAPPING TO:" +snapHelper.findTargetSnapPosition(recyclerView.getLayoutManager(),velocityX,velocityY)%songs.size());
            return false;
        }
    });

но это действительно не точно и иногда возвращает -1.

Я старался :

snapHelper.findSnapView(recyclerView.getLayoutManager());

но он возвращает представление, и я не думаю, что смогу получить индекс представления внутри recyclerview. (Я ошибаюсь?)

У меня нет предпочтения, когда метод возвращает индекс (до завершения броска или после).

ps: у моего recyclerview есть собственный ZoomingLayoutManager (для эффекта карусели). Может быть, он меняет размер представления, и это делает snapHelper неисправным? Я знаю, что snapHelper работает со средней высотой и шириной видимых дочерних элементов.

public class ZoomingLayoutManager extends LinearLayoutManager {
    private final float mShrinkAmount = 0.5f;
    private final float mShrinkDistance = 0.9f;

    public ZoomingLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int orientation = getOrientation();
        if (orientation == HORIZONTAL) {
            int scrolled = super.scrollHorizontallyBy(dx, recycler, state);

            float midpoint = getWidth() / 2.f;
            float d0 = 0.f;
            float d1 = mShrinkDistance * midpoint;
            float s0 = 1.f;
            float s1 = 1.f - mShrinkAmount;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                float childMidpoint =
                        (getDecoratedRight(child) + getDecoratedLeft(child)) / 2.f;
                float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
                float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
                child.setScaleX(scale);
                child.setScaleY(scale);
            }
            return scrolled;
        } else {
            return 0;
        }

    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int orientation = getOrientation();
        if (orientation == VERTICAL) {
            int scrolled = super.scrollVerticallyBy(dy, recycler, state);
            float midpoint = getHeight() / 2.f;
            float d0 = 0.f;
            float d1 = mShrinkDistance * midpoint;
            float s0 = 1.f;
            float s1 = 1.f - mShrinkAmount;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                float childMidpoint =
                        (getDecoratedBottom(child) + getDecoratedTop(child)) / 2.f;
                float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
                float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
                child.setScaleX(scale);
                child.setScaleY(scale);
            }
            return scrolled;
        } else {
            return 0;
        }
    }
}

Если это правда, как я могу переопределить методы snapHelper, чтобы найти более точную позицию? Или я просто пропустил что-то очевидное!?


person Apptic    schedule 05.08.2018    source источник


Ответы (2)


Вы можете попробовать что-то вроде этого; в моем случае только один элемент привязан к центру экрана, левый и правый элементы видны лишь частично, и это работает отлично.

Добавьте onScrollListener и дождитесь состояния SCROLL_STATE_SETTLING, затем получите FirstCompletelyVisibleItemPosition.

ПРИМЕЧАНИЕ. Я использовал Handler с задержкой в ​​1 секунду, как вы видите ниже, вы можете удалить эту часть и использовать только findFirstCompletelyVisibleItemPosition(). Использовал его для уверенности в состоянии прокрутки.

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);

            if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
                new Handler().postDelayed(() -> {
                    int pos = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
                    if (pos >= 0) {
                        // do your thing, the pos value must be your first fully visible or snapped view
                    }
                }, 1000);

            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

        }
    });
person Oğuzhan Döngül    schedule 05.08.2018
comment
Если пользователь прокручивает только 1 элемент, это занимает около полсекунды, поэтому он работает, и я могу установить время на 500 мс. но если пользователь прокручивает три или более элементов, это занимает, скажем, 2 секунды! и он просто возвращает позицию следующего элемента. Однако, если я установлю время, например, 2000 мс, оно задержится, если пользователь прокручивает только один элемент. - person Apptic; 05.08.2018

Итак, благодаря подсказке Огужана Донгюля я понял позицию, используя findFirstCompletelyVisibleItemPosition(), но там, «когда» вы вызываете метод, важно. Когда прокрутка завершена, состояние recyclerview изменяется на «ожидание», поэтому спасибо, когда вы можете использовать предоставленный метод, чтобы получить точную позицию первого полностью видимого представления. но что, если положение первого видимого представления не то, что вам нужно, и вам нужно положение представления в середине экрана?

ну, вы можете добавить смещение, рассчитав, сколько просмотров полностью видно на экране между средним элементом и границей экрана (на самом деле это довольно просто)

Итак, код:

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if(newState==RecyclerView.SCROLL_STATE_IDLE){
                int pos = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
                if (pos >= 0) {
                    int position=pos+ bigScreenOffset;
                    // do your thing, the position value is the exact index of the middle item
                }
            }
        }
    });

где bigScreenOffset рассчитывается следующим образом:

        bigScreenOffset= ( Utility.getDisplaySize(this,true,true)/200)/2;

и мой собственный метод в моем служебном классе, который обрабатывает ширину | высоту в dp | px:

 public static int getDisplaySize(Context context, boolean width, boolean dp) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    if (width) {
        if (dp)
            return (int) convertPixelsToDp(displayMetrics.widthPixels, context);
        else
            return displayMetrics.widthPixels;
    } else {
        if (dp)
            return (int) convertPixelsToDp(displayMetrics.heightPixels, context);
        else
            return displayMetrics.heightPixels;
    }
}
person Apptic    schedule 10.08.2018