program story

RecyclerView 가로 스크롤 스냅 중앙

inputbox 2020. 10. 20. 07:36
반응형

RecyclerView 가로 스크롤 스냅 중앙


RecyclerView를 사용하여 여기에서 회전식보기를 만들려고합니다. 한 번에 한 항목 씩 스크롤 할 때 항목이 화면 중앙에 스냅되도록하고 싶습니다. 나는 사용해 보았다recyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);

하지만 뷰는 여전히 부드럽게 스크롤되고 있으며 스크롤 리스너를 사용하여 내 자신의 논리를 다음과 같이 구현하려고 시도했습니다.

recyclerView.setOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    Log.v("Offset ", recyclerView.getWidth() + "");
                    if (newState == 0) {
                        try {
                               recyclerView.smoothScrollToPosition(layoutManager.findLastVisibleItemPosition());
                                recyclerView.scrollBy(20,0);
                            if (layoutManager.findLastVisibleItemPosition() >= recyclerView.getAdapter().getItemCount() - 1) {
                                Beam refresh = new Beam();
                                refresh.execute(createUrl());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

오른쪽에서 왼쪽으로 스 와이프는 이제 잘 작동하지만 그 반대는 아닙니다. 여기서 무엇을 놓치고 있습니까?


를 사용하면 LinearSnapHelper이제 매우 쉽게 할 수 있습니다.

다음과 같이하면됩니다.

SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);

최신 정보

25.1.0부터 사용 가능하며 유사한 효과를 PagerSnapHelper얻을 수 있습니다 ViewPager. 당신은을 사용로 사용 LinearSnapHelper.

이전 해결 방법 :

와 유사하게 작동하려면 ViewPager대신 다음을 시도하십시오.

LinearSnapHelper snapHelper = new LinearSnapHelper() {
    @Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
        View centerView = findSnapView(layoutManager);
        if (centerView == null) 
            return RecyclerView.NO_POSITION;

        int position = layoutManager.getPosition(centerView);
        int targetPosition = -1;
        if (layoutManager.canScrollHorizontally()) {
            if (velocityX < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        if (layoutManager.canScrollVertically()) {
            if (velocityY < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        final int firstItem = 0;
        final int lastItem = layoutManager.getItemCount() - 1;
        targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem));
        return targetPosition;
    }
};
snapHelper.attachToRecyclerView(recyclerView);

위의 구현은 크기에 관계없이 속도의 방향을 기준으로 현재 항목 (중앙) 옆의 위치를 ​​반환합니다.

전자는 지원 라이브러리 버전 24.2.0에 포함 된 자사 솔루션입니다. 앱 모듈에 이것을 추가 build.gradle하거나 업데이트해야한다는 의미입니다.

compile "com.android.support:recyclerview-v7:24.2.0"

Google I / O 2019 업데이트 :

ViewPager2 가 여기 있습니다!

Google은 방금 'Android의 새로운 기능'(일명 'Android 키 노트')에서 RecyclerView를 기반으로하는 새로운 ViewPager를 개발 중이라고 발표했습니다!

슬라이드에서 :

ViewPager와 비슷하지만 더 좋습니다.

  • ViewPager에서 쉽게 마이그레이션
  • RecyclerView 기반
  • 오른쪽에서 왼쪽으로 모드 지원
  • 수직 페이징 허용
  • 향상된 데이터 세트 변경 알림

당신은 최신 버전을 확인할 수 있습니다 여기에 와 릴리스 노트 여기 . 도 있습니다 공식 샘플 .

개인적 의견 : 이것이 정말로 필요한 추가라고 생각합니다. 나는 최근에 PagerSnapHelper 왼쪽 오른쪽이 무한정 진동 하는 데 많은 문제를 겪었 습니다. 내가 개봉 한 티켓을 보십시오 .


새로운 답변 (2016) :

이제 SnapHelper를 사용할 수 있습니다 .

ViewPager 와 유사한 가운데 정렬 된 스냅 동작을 원하면 PagerSnapHelper 를 사용하십시오 .

SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

도있다 LinearSnapHelper은 . 나는 그것을 시도했고 당신이 에너지로 날뛰면 그것은 1 날 뛰기로 2 항목을 스크롤합니다. 개인적으로 나는 그것을 좋아하지 않았지만 스스로 결정하십시오.


원문 답변 (2016) :

여기에서 찾은 3 가지 다른 솔루션을 여러 시간 시도한 마침내 ViewPager.

이 솔루션은 @eDizzle 솔루션을 기반으로합니다.이 솔루션은 거의 ViewPager.

중요 : RecyclerView항목 너비가 화면과 정확히 동일합니다. 다른 크기로는 시도하지 않았습니다. 또한 수평으로 사용합니다 LinearLayoutManager. 세로 스크롤을 원하면 코드를 수정해야한다고 생각합니다.

여기에 코드가 있습니다.

public class SnappyRecyclerView extends RecyclerView {

    // Use it with a horizontal LinearLayoutManager
    // Based on https://stackoverflow.com/a/29171652/4034572

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

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

    public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean fling(int velocityX, int velocityY) {

        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

        int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;

        // views on the screen
        int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
        View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
        int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
        View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

        // distance we need to scroll
        int leftMargin = (screenWidth - lastView.getWidth()) / 2;
        int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
        int leftEdge = lastView.getLeft();
        int rightEdge = firstView.getRight();
        int scrollDistanceLeft = leftEdge - leftMargin;
        int scrollDistanceRight = rightMargin - rightEdge;

        if (Math.abs(velocityX) < 1000) {
            // The fling is slow -> stay at the current page if we are less than half through,
            // or go to the next page if more than half through

            if (leftEdge > screenWidth / 2) {
                // go to next page
                smoothScrollBy(-scrollDistanceRight, 0);
            } else if (rightEdge < screenWidth / 2) {
                // go to next page
                smoothScrollBy(scrollDistanceLeft, 0);
            } else {
                // stay at current page
                if (velocityX > 0) {
                    smoothScrollBy(-scrollDistanceRight, 0);
                } else {
                    smoothScrollBy(scrollDistanceLeft, 0);
                }
            }
            return true;

        } else {
            // The fling is fast -> go to next page

            if (velocityX > 0) {
                smoothScrollBy(scrollDistanceLeft, 0);
            } else {
                smoothScrollBy(-scrollDistanceRight, 0);
            }
            return true;

        }

    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);

        // If you tap on the phone while the RecyclerView is scrolling it will stop in the middle.
        // This code fixes this. This code is not strictly necessary but it improves the behaviour.

        if (state == SCROLL_STATE_IDLE) {
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

            int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;

            // views on the screen
            int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
            View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
            int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
            View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

            // distance we need to scroll
            int leftMargin = (screenWidth - lastView.getWidth()) / 2;
            int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
            int leftEdge = lastView.getLeft();
            int rightEdge = firstView.getRight();
            int scrollDistanceLeft = leftEdge - leftMargin;
            int scrollDistanceRight = rightMargin - rightEdge;

            if (leftEdge > screenWidth / 2) {
                smoothScrollBy(-scrollDistanceRight, 0);
            } else if (rightEdge < screenWidth / 2) {
                smoothScrollBy(scrollDistanceLeft, 0);
            }
        }
    }

}

즐겨!


목표가 RecyclerView모방 을 만드는 것이라면 ViewPager매우 쉬운 접근 방식이 있습니다.

RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);

LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
SnapHelper snapHelper = new PagerSnapHelper();
recyclerView.setLayoutManager(layoutManager);
snapHelper.attachToRecyclerView(mRecyclerView);

사용 PagerSnapHelper하면 다음과 같은 동작을 얻을 수 있습니다.ViewPager


반대 방향으로 가려면 findFirstVisibleItemPosition을 사용해야합니다. 그리고 스 와이프가 어느 방향에 있었는지 감지하려면 플링 속도 또는 x의 변화를 가져와야합니다. 나는 당신이 가지고있는 것과 약간 다른 각도에서이 문제에 접근했습니다.

RecyclerView 클래스를 확장하는 새 클래스를 만든 다음 RecyclerView의 fling 메서드를 다음과 같이 재정의합니다.

@Override
public boolean fling(int velocityX, int velocityY) {
    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

//these four variables identify the views you see on screen.
    int lastVisibleView = linearLayoutManager.findLastVisibleItemPosition();
    int firstVisibleView = linearLayoutManager.findFirstVisibleItemPosition();
    View firstView = linearLayoutManager.findViewByPosition(firstVisibleView);
    View lastView = linearLayoutManager.findViewByPosition(lastVisibleView);

//these variables get the distance you need to scroll in order to center your views.
//my views have variable sizes, so I need to calculate side margins separately.     
//note the subtle difference in how right and left margins are calculated, as well as
//the resulting scroll distances.
    int leftMargin = (screenWidth - lastView.getWidth()) / 2;
    int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
    int leftEdge = lastView.getLeft();
    int rightEdge = firstView.getRight();
    int scrollDistanceLeft = leftEdge - leftMargin;
    int scrollDistanceRight = rightMargin - rightEdge;

//if(user swipes to the left) 
    if(velocityX > 0) smoothScrollBy(scrollDistanceLeft, 0);
    else smoothScrollBy(-scrollDistanceRight, 0);

    return true;
}

내 솔루션 :

/**
 * Horizontal linear layout manager whose smoothScrollToPosition() centers
 * on the target item
 */
class ItemLayoutManager extends LinearLayoutManager {

    private int centeredItemOffset;

    public ItemLayoutManager(Context context) {
        super(context, LinearLayoutManager.HORIZONTAL, false);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller linearSmoothScroller = new Scroller(recyclerView.getContext());
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

    public void setCenteredItemOffset(int centeredItemOffset) {
        this.centeredItemOffset = centeredItemOffset;
    }

    /**
     * ********** Inner Classes **********
     */

    private class Scroller extends LinearSmoothScroller {

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

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return ItemLayoutManager.this.computeScrollVectorForPosition(targetPosition);
        }

        @Override
        public int calculateDxToMakeVisible(View view, int snapPreference) {
            return super.calculateDxToMakeVisible(view, SNAP_TO_START) + centeredItemOffset;
        }
    }
}

I pass this layout manager to the RecycledView and set the offset required to center items. All my items have the same width so constant offset is ok


Just add padding and margin to recyclerView and recyclerView item:

recyclerView item:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parentLayout"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginLeft="8dp" <!-- here -->
    android:layout_marginRight="8dp" <!-- here  -->
    android:layout_width="match_parent"
    android:layout_height="200dp">

   <!-- child views -->

</RelativeLayout>

recyclerView:

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="8dp" <!-- here -->
    android:paddingRight="8dp" <!-- here -->
    android:clipToPadding="false" <!-- important!-->
    android:scrollbars="none" />

and set PagerSnapHelper :

int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4;
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

dp to px:

public static int dpToPx(int dp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}

result:

enter image description here


PagerSnapHelper doesn't work with GridLayoutManager with spanCount > 1, so my solution under this circumstance is:

class GridPagerSnapHelper : PagerSnapHelper() {
    override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
        val forwardDirection = if (layoutManager?.canScrollHorizontally() == true) {
            velocityX > 0
        } else {
            velocityY > 0
        }
        val centerPosition = super.findTargetSnapPosition(layoutManager, velocityX, velocityY)
        return centerPosition +
            if (forwardDirection) (layoutManager as GridLayoutManager).spanCount - 1 else 0
    }
}

참고URL : https://stackoverflow.com/questions/29134094/recyclerview-horizontal-scroll-snap-in-center

반응형