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
นี่คือคำตอบที่ถูกต้องแม้ว่าจะได้รับคำตอบก็ตาม
Alessio

26

นี่คือฟังก์ชั่นส่วนขยายที่ฉันเขียนในKotlinเพื่อใช้กับRecyclerView(ตามคำตอบของ @Paul Woitaschek):

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

แทนที่ฟังก์ชัน calculDyToMakeVisible / คำนวณDxToMakeVisibleใน LinearSmoothScroller เพื่อใช้ตำแหน่งออฟเซ็ต Y / X

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

นี่คือสิ่งที่ฉันกำลังมองหา ขอบคุณสำหรับการแบ่งปันสิ่งนี้เพื่อใช้ตำแหน่งออฟเซ็ตของ x / y :)
Shan Xeeshi

3

วิธีที่ง่ายที่สุดที่ฉันพบในการเลื่อน a 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


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)
    }
  }

หมายเหตุ:ตัวอย่างข้างต้นตั้งค่าเป็นทิศทางแนวนอนคุณสามารถส่งผ่านแนวตั้ง / แนวนอนในระหว่างการเริ่มต้น

หากคุณกำหนดทิศทางเป็นVERTICALคุณควรเปลี่ยน " คำนวณDxToMakeVisible " เป็น " คำนวณ DyToMakeVisible" (โปรดคำนึงถึงค่าส่งคืนการโทร supertype ด้วย)

กิจกรรม / 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. เราต้องได้รับพิกัดสูงสุดขององค์ประกอบรีไซเคิล 2. เราต้องได้รับพิกัดด้านบนของรายการมุมมองที่เราต้องการเลื่อนไปด้านบน 3. ในตอนท้ายด้วยค่าชดเชยที่คำนวณได้เราต้องทำ

recyclerView.scrollBy(0, offset);

200 เป็นเพียงตัวอย่างค่าจำนวนเต็มแบบฮาร์ดโค้ดที่คุณสามารถใช้ได้หากไม่มีไอเท็ม Viewholder เนื่องจากเป็นไปได้เช่นกัน


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);
}

PS:ผมสาปแช่งวันที่ฉันเริ่มกราดแปลงListViewเพื่อRecyclerView


-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.