RecyclerView-특정 위치에서 항목 위로 부드럽게 스크롤하는 방법은 무엇입니까?


125

RecyclerView에서 다음을 사용하여 갑자기 선택한 항목의 맨 위로 스크롤 할 수 있습니다.

((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);

그러나 이것은 갑자기 항목을 맨 위 위치로 이동합니다. 매끄럽게 항목의 맨 위로 이동하고 싶습니다 .

나는 또한 시도했다 :

recyclerView.smoothScrollToPosition(position);

그러나 항목을 맨 위로 선택한 위치로 이동하지 않기 때문에 잘 작동하지 않습니다. 위치의 항목이 표시 될 때까지 목록을 스크롤 할뿐입니다.

답변:


225

RecyclerView확장 가능하도록 설계되었으므로 스크롤을 수행하기 위해 LayoutManager( droidev가 제안한대로 ) 하위 클래스를 만들 필요가 없습니다 .

대신 SmoothScroller기본 설정으로을 만드십시오 SNAP_TO_START.

RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) {
  @Override protected int getVerticalSnapPreference() {
    return LinearSmoothScroller.SNAP_TO_START;
  }
};

이제 스크롤하려는 위치를 설정합니다.

smoothScroller.setTargetPosition(position);

SmoothScroller를 LayoutManager에 전달합니다.

layoutManager.startSmoothScroll(smoothScroller);

9
이 짧은 솔루션에 감사드립니다. 구현시 고려해야 할 사항이 두 가지뿐입니다. 가로 스크롤보기가 있으므로 protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. 또한 추상적 인 방법을 구현해야했습니다 public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
AustrianDude

2
RecyclerView는 확장 가능하도록 설계 될 수 있지만 이와 같은 단순한 것은 누락되어 게으르다 고 느낍니다. 좋은 대답! 감사합니다.
Michael

8
시작하도록 스냅 할 수있는 방법이 있지만 X dp 오프셋이 있습니까?
Mark Buikema

5
속도를 늦출 수 있습니까?
Alireza Noorali

3
또한 스크롤이 발생하는 속도를 수정할 수 있는지 궁금합니다 (느림) @AlirezaNoorali
vikzilla

112

이를 위해 사용자 정의 LayoutManager를 만들어야합니다.

public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {

    public LinearLayoutManagerWithSmoothScroller(Context context) {
        super(context, VERTICAL, false);
    }

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

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                       int position) {
        RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private class TopSnappedSmoothScroller extends LinearSmoothScroller {
        public TopSnappedSmoothScroller(Context context) {
            super(context);

        }

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

        @Override
        protected int getVerticalSnapPreference() {
            return SNAP_TO_START;
        }
    }
}

RecyclerView에 이것을 사용하고 smoothScrollToPosition을 호출하십시오.

예 :

 recyclerView.setLayoutManager(new LinearLayoutManagerWithSmoothScroller(context));
 recyclerView.smoothScrollToPosition(position);

지정된 위치의 RecyclerView 항목의 맨 위로 스크롤됩니다.

도움이 되었기를 바랍니다.


제안 된 LayoutManager를 구현하는 데 문제가있었습니다. 이 대답은 여기에 나를 위해 훨씬 더 쉽게 일 : stackoverflow.com/questions/28025425/...
arberg

3
몇 시간 동안 고통을 겪은 후에 만 ​​유용한 답변입니다. 이것은 설계된대로 작동합니다!
wblaschko

1
@droidev 부드러운 스크롤로 예제를 사용했으며 일반적으로 SNAP_TO_START가 필요하지만 몇 가지 문제가 있습니다. 때때로 스크롤은 내가 전달하는 1,2,3 또는 4 위치를 더 일찍 중지합니다 smoothScrollToPosition. 이 문제가 발생하는 이유는 무엇입니까? 감사.
Natasha

어떻게 든 우리에게 코드를 전달할 수 있습니까? 나는 그것이 당신의 코드 문제라고 확신합니다.
droidev

1
이것은 허용 하나에도 불구하고 정답
알레

26

이것은 Kotlin 에서 (@Paul Woitaschek 답변을 기반으로) 사용하기 위해 작성한 확장 기능입니다 .RecyclerView

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) {
  val smoothScroller = object : LinearSmoothScroller(this.context) {
    override fun getVerticalSnapPreference(): Int = snapMode
    override fun getHorizontalSnapPreference(): Int = snapMode
  }
  smoothScroller.targetPosition = position
  layoutManager?.startSmoothScroll(smoothScroller)
}

다음과 같이 사용하십시오.

myRecyclerView.smoothSnapToPosition(itemPosition)

작동합니다! 당신이 큰 목록 다음 아래로 스크롤하거나 상단이 길고 시간이 오래 걸리는있을 때 부드러운 스크롤 문제는
portfoliobuilder을

@vovahost 원하는 위치를 어떻게 중앙에 배치 할 수 있습니까?
ysfcyln

@ysfcyln이 stackoverflow.com/a/39654328/1502079 답변 확인
vovahost

12

이렇게해볼 수 있어요

    recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(), recyclerView.getAdapter().getItemCount());

5

LinearSmoothScroller에서 calculateDyToMakeVisible / calculateDxToMakeVisible 함수를 재정 의하여 오프셋 Y / X 위치를 구현합니다.

override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int {
    return super.calculateDyToMakeVisible(view, snapPreference) - ConvertUtils.dp2px(10f)
}

이것이 제가 찾고 있던 것입니다. 구현 : X / Y의 오프셋 위치이 공유를위한 감사합니다
산 Xeeshi을

3

스크롤하는 가장 쉬운 방법 RecyclerView은 다음과 같습니다.

// Define the Index we wish to scroll to.
final int lIndex = 0;
// Assign the RecyclerView's LayoutManager.
this.getRecyclerView().setLayoutManager(this.getLinearLayoutManager());
// Scroll the RecyclerView to the Index.
this.getLinearLayoutManager().smoothScrollToPosition(this.getRecyclerView(), new RecyclerView.State(), lIndex);

2
해당 위치가 이미 표시되어 있으면 해당 위치로 스크롤되지 않습니다. 위치를 표시하는 데 필요한 최소한의 양만큼 스크롤합니다. 따라서 지정된 오프셋이 필요한 이유.
TatiOverflow

3

나는 이렇게 사용했다 :

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, new RecyclerView.State(), 5);

2

솔루션에 대해 @droidev에게 감사드립니다. Kotlin 솔루션을 찾는 사람은 다음을 참조하세요.

    class LinearLayoutManagerWithSmoothScroller: LinearLayoutManager {
    constructor(context: Context) : this(context, VERTICAL,false)
    constructor(context: Context, orientation: Int, reverseValue: Boolean) : super(context, orientation, reverseValue)

    override fun smoothScrollToPosition(recyclerView: RecyclerView?, state: RecyclerView.State?, position: Int) {
        super.smoothScrollToPosition(recyclerView, state, position)
        val smoothScroller = TopSnappedSmoothScroller(recyclerView?.context)
        smoothScroller.targetPosition = position
        startSmoothScroll(smoothScroller)
    }

    private class TopSnappedSmoothScroller(context: Context?) : LinearSmoothScroller(context){
        var mContext = context
        override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
            return LinearLayoutManagerWithSmoothScroller(mContext as Context)
                    .computeScrollVectorForPosition(targetPosition)
        }

        override fun getVerticalSnapPreference(): Int {
            return SNAP_TO_START
        }


    }

}

1
감사합니다 @pankaj, Kotlin에서 솔루션을 찾고있었습니다.
Akshay Kumar 모두

1
  1. "LinearLayout"클래스를 확장하고 필요한 기능을 재정의합니다.
  2. 조각 또는 활동에서 위 클래스의 인스턴스를 만듭니다.
  3. "recyclerView.smoothScrollToPosition (targetPosition)

CustomLinearLayout.kt :

class CustomLayoutManager(private val context: Context, layoutDirection: Int):
  LinearLayoutManager(context, layoutDirection, false) {

    companion object {
      // This determines how smooth the scrolling will be
      private
      const val MILLISECONDS_PER_INCH = 300f
    }

    override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State, position: Int) {

      val smoothScroller: LinearSmoothScroller = object: LinearSmoothScroller(context) {

        fun dp2px(dpValue: Float): Int {
          val scale = context.resources.displayMetrics.density
          return (dpValue * scale + 0.5f).toInt()
        }

        // change this and the return super type to "calculateDyToMakeVisible" if the layout direction is set to VERTICAL
        override fun calculateDxToMakeVisible(view: View ? , snapPreference : Int): Int {
          return super.calculateDxToMakeVisible(view, SNAP_TO_END) - dp2px(50f)
        }

        //This controls the direction in which smoothScroll looks for your view
        override fun computeScrollVectorForPosition(targetPosition: Int): PointF ? {
          return this @CustomLayoutManager.computeScrollVectorForPosition(targetPosition)
        }

        //This returns the milliseconds it takes to scroll one pixel.
        override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
          return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
        }
      }
      smoothScroller.targetPosition = position
      startSmoothScroll(smoothScroller)
    }
  }

참고 : 위의 예는 HORIZONTAL 방향으로 설정되어 있으며 초기화 중에 VERTICAL / HORIZONTAL을 전달할 수 있습니다.

방향을 VERTICAL로 설정하면 " computeDxToMakeVisible "을 " calculateDyToMakeVisible " 로 변경해야합니다 (또한 상위 유형 호출 반환 값에 유의하십시오).

Activity / Fragment.kt :

...
smoothScrollerLayoutManager = CustomLayoutManager(context, LinearLayoutManager.HORIZONTAL)
recyclerView.layoutManager = smoothScrollerLayoutManager
.
.
.
fun onClick() {
  // targetPosition passed from the adapter to activity/fragment
  recyclerView.smoothScrollToPosition(targetPosition)
}

0

아마도 @droidev 접근 방식이 올바른 방법이지만 기본적으로 동일한 작업을 수행하고 LayoutManager의 확장이 필요하지 않은 약간 다른 것을 게시하고 싶습니다.

여기에 있는 참고 사항-항목 (목록 상단에서 스크롤하려는 항목)이 화면에 표시되고 자동으로 상단으로 스크롤하려는 경우 잘 작동합니다. 목록의 마지막 항목에 동일한 목록에 새 항목을 추가하는 작업이 있고 새로 추가 된 항목에 사용자를 집중하려는 경우에 유용합니다.

int recyclerViewTop = recyclerView.getTop();
int positionTop = recyclerView.findViewHolderForAdapterPosition(positionToScroll) != null ? recyclerView.findViewHolderForAdapterPosition(positionToScroll).itemView.getTop() : 200;
final int calcOffset = positionTop - recyclerViewTop; 
//then the actual scroll is gonna happen with (x offset = 0) and (y offset = calcOffset)
recyclerView.scrollBy(0, offset);

아이디어는 간단합니다. 1. recyclerview 요소의 최상위 좌표를 가져와야합니다. 2. 맨 위로 스크롤하려는 뷰 항목의 맨 위 좌표를 가져와야합니다. 3. 계산 된 오프셋으로 마지막에해야 할 일

recyclerView.scrollBy(0, offset);

200은 뷰 홀더 항목이 존재하지 않는 경우에도 사용할 수있는 하드 코딩 된 정수 값의 예일뿐입니다.


0

이전 답변을 선택하면 실제로 현재 위치에서 대상 위치에 도달하는 데 필요한 스크롤 양에 따라 크게 달라지는 스크롤 지속 시간 문제를 더 자세히 다루고 싶습니다 .

균일 한 스크롤 지속 시간을 얻으려면 속도 (밀리 초당 픽셀 수)가 각 개별 항목의 크기를 고려해야합니다. 항목이 비표준 차원이면 완전히 새로운 수준의 복잡성이 추가됩니다.

이것이 RecyclerView 개발자 가 부드러운 스크롤링의 중요한 측면을 위해 너무 단단한 바구니를 배포 한 이유 일 수 있습니다 .

반 균일 스크롤 지속 시간을 원하고 목록에 반 균일 항목이 포함되어 있다고 가정하면 다음과 같은 것이 필요합니다.

/** Smoothly scroll to specified position allowing for interval specification. <br>
 * Note crude deceleration towards end of scroll
 * @param rv        Your RecyclerView
 * @param toPos     Position to scroll to
 * @param duration  Approximate desired duration of scroll (ms)
 * @throws IllegalArgumentException */
private static void smoothScroll(RecyclerView rv, int toPos, int duration) throws IllegalArgumentException {
    int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;     // See androidx.recyclerview.widget.LinearSmoothScroller
    int itemHeight = rv.getChildAt(0).getHeight();  // Height of first visible view! NB: ViewGroup method!
    itemHeight = itemHeight + 33;                   // Example pixel Adjustment for decoration?
    int fvPos = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
    int i = Math.abs((fvPos - toPos) * itemHeight);
    if (i == 0) { i = (int) Math.abs(rv.getChildAt(0).getY()); }
    final int totalPix = i;                         // Best guess: Total number of pixels to scroll
    RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(rv.getContext()) {
        @Override protected int getVerticalSnapPreference() {
            return LinearSmoothScroller.SNAP_TO_START;
        }
        @Override protected int calculateTimeForScrolling(int dx) {
            int ms = (int) ( duration * dx / (float)totalPix );
            // Now double the interval for the last fling.
            if (dx < TARGET_SEEK_SCROLL_DISTANCE_PX ) { ms = ms*2; } // Crude deceleration!
            //lg(format("For dx=%d we allot %dms", dx, ms));
            return ms;
        }
    };
    //lg(format("Total pixels from = %d to %d = %d [ itemHeight=%dpix ]", fvPos, toPos, totalPix, itemHeight));
    smoothScroller.setTargetPosition(toPos);
    rv.getLayoutManager().startSmoothScroll(smoothScroller);
}

추신 : 나는 ListViewRecyclerView무차별 적으로 변환 하기 시작한 날을 저주합니다 .


-1

list.reverse()마지막으로 전화를 걸어 목록을 되돌릴 수 있습니다.RecylerView.scrollToPosition(0)

    list.reverse()
    layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)  
    RecylerView.scrollToPosition(0)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.