การรีเฟรชข้อมูลใน RecyclerView และรักษาตำแหน่งการเลื่อน


103

วิธีการรีเฟรชข้อมูลที่แสดงในRecyclerView(เรียกnotifyDataSetChangedใช้อะแดปเตอร์) และตรวจสอบให้แน่ใจว่าตำแหน่งการเลื่อนถูกรีเซ็ตเป็นตำแหน่งที่ตรงกันหรือไม่?

ในกรณีของสิ่งที่ดีListViewทั้งหมดก็คือการดึงข้อมูลการgetChildAt(0)ตรวจสอบgetTop()และการโทรsetSelectionFromTopด้วยข้อมูลที่ตรงกันในภายหลัง

RecyclerViewมันไม่ได้ดูเหมือนจะเป็นไปได้ในกรณีของ

ฉันเดาว่าฉันควรจะใช้มันLayoutManagerที่ให้scrollToPositionWithOffset(int position, int offset)มา แต่วิธีที่เหมาะสมในการดึงตำแหน่งและออฟเซ็ตคืออะไร?

layoutManager.findFirstVisibleItemPosition()และlayoutManager.getChildAt(0).getTop()?

หรือมีวิธีที่หรูหรากว่านี้ในการทำงานให้ลุล่วง?


เป็นค่าความกว้างและความสูง RecyclerView หรือ LayoutManager ตรวจสอบวิธีนี้: stackoverflow.com/questions/28993640/…
serefakyuz

@Konard ในกรณีของฉันฉันมีรายการไฟล์เสียงที่มีการใช้งาน Seekbar และทำงานโดยมีปัญหาต่อไปนี้: 1) สำหรับรายการที่จัดทำดัชนีล่าสุดไม่กี่รายการทันทีที่ทริกเกอร์การแจ้งเตือนรายการเปลี่ยนแปลง (pos) มันจะผลักดันมุมมองที่ด้านล่างอัตโนมัติ 2) ในขณะที่ยังคงแจ้งเตือน (pos) รายการจะติดอยู่และไม่อนุญาตให้เลื่อนได้อย่างง่ายดาย แม้ว่าฉันจะถามเป็นคำถาม แต่โปรดแจ้งให้เราทราบหากคุณมีแนวทางที่ดีกว่าในการแก้ไขปัญหาดังกล่าว
CoDe

คำตอบ:


148

ฉันใช้อันนี้ ^ _ ^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

มันง่ายกว่านี้หวังว่ามันจะช่วยคุณได้!


1
นี่เป็นคำตอบที่ถูกต้องจริงๆ มันยุ่งยากเพียงเพราะการโหลดข้อมูลแบบอะซิงโครนัส แต่คุณสามารถเลื่อนการกู้คืนได้
EpicPandaForce

สมบูรณ์แบบ +2 สิ่งที่ฉันกำลังมองหา
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4mฉันพยายามใช้สิ่งนี้ แต่ไม่ได้ผลฉันทำอะไรผิด
Kaustubh Bhagwat

@KaustubhBhagwat บางทีคุณควรตรวจสอบ "RecyclerViewState! = null" ก่อนใช้งาน
DawnYu

4
ฉันควรใช้รหัสนี้ที่ไหน ในเมธอด onResume?
Lucky_girl

47

ฉันมีปัญหาที่ค่อนข้างคล้ายกัน และฉันคิดวิธีแก้ปัญหาต่อไปนี้

การใช้notifyDataSetChangedเป็นความคิดที่ไม่ดี คุณควรเจาะจงมากขึ้นจากนั้นRecyclerViewจะบันทึกสถานะการเลื่อนให้คุณ

ตัวอย่างเช่นหากคุณต้องการเพียงรีเฟรชหรือกล่าวอีกนัยหนึ่งคุณต้องการให้แต่ละมุมมองถูก rebinded ให้ทำดังนี้

adapter.notifyItemRangeChanged(0, adapter.getItemCount());

2
ฉันลองใช้ adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); มันก็ใช้งานได้เหมือนกัน .notifyDataSetChanged ()
มณี

4
สิ่งนี้ช่วยในกรณีของฉัน ฉันกำลังแทรกหลายรายการที่ตำแหน่ง 0 และด้วยnotifyDataSetChangedตำแหน่งการเลื่อนจะเปลี่ยนไปแสดงรายการใหม่ อย่างไรก็ตามหากใช้notifyItemRangeInserted(0, newItems.size() - 1)สถานะRecyclerViewการเลื่อนจะคงอยู่
Carlosph

คำตอบที่ดีมากฉันค้นหาคำตอบของคำตอบนั้นทั้งวัน ขอบคุณมาก ..
Gundu Bandgar

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); ควรหลีกเลี่ยงตามที่ระบุไว้ใน Google I / O ล่าสุด (ดูการนำเสนอเกี่ยวกับการรีไซเคิลการดูทั้งในและนอก) จะเป็นการดีกว่า (ถ้าคุณมีความรู้) ในการบอกมุมมองรีไซเคิลว่ารายการใดเปลี่ยนแปลงไปแทนที่จะรีเฟรชแบบจับทั้งหมด ของการดูทั้งหมด
A. Steenbergen

3
มันไม่ทำงานรีไซเคิลดูรีเฟรชไปด้านบนแม้ว่าฉันจะเพิ่ม
Shaahul

21

แก้ไข:ในการคืนค่าตำแหน่งที่ชัดเจนเหมือนเดิมให้เหมือนเดิมทุกประการเราจำเป็นต้องทำอะไรบางอย่างที่แตกต่างออกไปเล็กน้อย (ดูวิธีคืนค่าค่า scrollY ที่แน่นอนด้านล่าง):

บันทึกตำแหน่งและชดเชยดังนี้:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();

outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);

จากนั้นเรียกคืนการเลื่อนดังนี้:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

คืนนี้รายการเพื่อมันแน่นอนชัดเจนตำแหน่ง เห็นได้ชัดเนื่องจากผู้ใช้จะมีลักษณะเหมือนกัน แต่จะไม่มีค่า scrollY เท่ากัน (เนื่องจากความแตกต่างที่เป็นไปได้ในขนาดเค้าโครงแนวนอน / แนวตั้ง)

โปรดทราบว่าสิ่งนี้ใช้ได้กับ LinearLayoutManager เท่านั้น

- ด้านล่างวิธีการคืนค่าการเลื่อนที่แน่นอน Y ซึ่งอาจทำให้รายการดูแตกต่างออกไป ---

  1. ใช้ OnScrollListener ดังนี้:

    private int mScrollY;
    private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrollY += dy;
        }
    };
    

สิ่งนี้จะจัดเก็บตำแหน่งการเลื่อนที่แน่นอนตลอดเวลาใน mScrollY

  1. เก็บตัวแปรนี้ไว้ใน Bundle ของคุณและเรียกคืนในการกู้คืนสถานะเป็นตัวแปรอื่นเราจะเรียกมันว่า mStateScrollY

  2. หลังจากการกู้คืนสถานะและหลังจาก RecyclerView ของคุณรีเซ็ตข้อมูลทั้งหมดรีเซ็ตการเลื่อนด้วยสิ่งนี้:

    mRecyclerView.scrollBy(0, mStateScrollY);
    

แค่นั้นแหละ.

ระวังว่าคุณเรียกคืนการเลื่อนเป็นตัวแปรอื่นสิ่งนี้มีความสำคัญเนื่องจาก OnScrollListener จะถูกเรียกด้วย. scrollBy () จากนั้นจะตั้งค่า mScrollY เป็นค่าที่เก็บไว้ใน mStateScrollY หากคุณไม่ทำ mScrollY นี้จะมีค่าการเลื่อนเป็นสองเท่า (เนื่องจาก OnScrollListener ทำงานร่วมกับเดลต้าไม่ใช่การเลื่อนแบบสัมบูรณ์)

การประหยัดของรัฐในกิจกรรมสามารถทำได้ดังนี้:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

และเพื่อเรียกคืนการเรียกสิ่งนี้ใน onCreate () ของคุณ:

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

การบันทึกสถานะเป็นเศษเล็กเศษน้อยทำงานในลักษณะเดียวกัน แต่การประหยัดสถานะจริงต้องการการทำงานเพิ่มเติมเล็กน้อย แต่มีบทความมากมายที่เกี่ยวข้องกับเรื่องนี้ดังนั้นคุณจึงไม่ควรมีปัญหาในการค้นหาว่าหลักการของการบันทึกการเลื่อน Y และการคืนค่ายังคงเหมือนเดิม


ฉันไม่เข้าใจคุณสามารถโพสต์ตัวอย่างเต็มได้หรือไม่?

นี่เป็นตัวอย่างที่ใกล้เคียงกับตัวอย่างทั้งหมดที่คุณจะได้รับหากคุณไม่ต้องการให้ฉันโพสต์กิจกรรมทั้งหมดของฉัน
A. Steenbergen

ฉันไม่ได้รับบางสิ่งบางอย่าง หากมีการเพิ่มรายการในตอนต้นของรายการจะไม่อยู่ในตำแหน่งการเลื่อนเดียวกันจะผิดเพราะตอนนี้คุณกำลังมองหารายการต่างๆที่เข้ามาในพื้นที่นี้หรือไม่?
นักพัฒนา Android

ใช่ในกรณีนี้คุณต้องปรับตำแหน่งเมื่อทำการกู้คืนอย่างไรก็ตามนี่ไม่ใช่ส่วนหนึ่งของคำถามเนื่องจากโดยปกติแล้วในขณะหมุนคุณจะไม่เพิ่มหรือลบรายการ นั่นจะเป็นพฤติกรรมที่แปลกและอาจไม่อยู่ในความสนใจของผู้ใช้ยกเว้นกรณีพิเศษบางอย่างที่อาจมีอยู่ซึ่งฉันไม่สามารถนึกภาพออกได้โดยสุจริต
A. Steenbergen

9

ใช่คุณสามารถแก้ไขปัญหานี้ได้โดยสร้างตัวสร้างอะแดปเตอร์เพียงครั้งเดียวฉันกำลังอธิบายส่วนการเข้ารหัสที่นี่:

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

ตอนนี้คุณจะเห็นว่าฉันได้ตรวจสอบว่าอะแด็ปเตอร์เป็นโมฆะหรือไม่และเริ่มต้นเมื่อเป็นโมฆะเท่านั้น

หากอะแด็ปเตอร์ไม่เป็นโมฆะฉันมั่นใจได้ว่าฉันได้เตรียมใช้งานอะแด็ปเตอร์ของฉันอย่างน้อยหนึ่งครั้ง

ดังนั้นฉันจะเพิ่มรายการในอะแดปเตอร์และโทรแจ้งชุดข้อมูลที่เปลี่ยนแปลง

RecyclerView จะเลื่อนตำแหน่งสุดท้ายเสมอดังนั้นคุณไม่จำเป็นต้องจัดเก็บตำแหน่งสุดท้ายเพียงแค่โทรแจ้งชุดข้อมูลที่เปลี่ยนแปลงมุมมองรีไซเคิลจะรีเฟรชข้อมูลเสมอโดยไม่ต้องขึ้นไปด้านบน

ขอบคุณ Happy Coding


ฉันคิดว่านี่จะเป็นคำตอบที่ได้รับการยอมรับ @Konrad Morawski
Jithin Jude

7

รักษาตำแหน่งการเลื่อนโดยใช้คำตอบ @DawnYu เพื่อห่อnotifyDataSetChanged()ดังนี้:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)

สมบูรณ์แบบ ... คำตอบที่ดีที่สุด
jlandyr

ฉันหาคำตอบนี้มานานแล้ว ขอบคุณมาก.
Alaa AbuZarifa

3

คำตอบยอดนิยมโดย @DawnYu ใช้ได้ผล แต่มุมมองรีไซเคิลจะเลื่อนไปด้านบนก่อนจากนั้นกลับไปที่ตำแหน่งการเลื่อนที่ตั้งใจไว้ทำให้เกิดปฏิกิริยา "กะพริบเหมือน" ซึ่งไม่เป็นที่พอใจ

ในการรีเฟรช RecyclerView โดยเฉพาะอย่างยิ่งหลังจากมาจากกิจกรรมอื่นโดยไม่กะพริบและรักษาตำแหน่งการเลื่อนคุณต้องทำสิ่งต่อไปนี้

  1. ตรวจสอบให้แน่ใจว่าคุณกำลังอัปเดตมุมมองรีไซเคิลโดยใช้ DiffUtil อ่านเพิ่มเติมได้ที่นี่: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. เริ่มต้นกิจกรรมของคุณหรือเมื่อถึงจุดที่คุณต้องการอัปเดตกิจกรรมของคุณโหลดข้อมูลลงในมุมมองรีไซเคิลของคุณ การใช้ diffUtil จะทำการอัปเดตเฉพาะในมุมมองรีไซเคิลในขณะที่ยังคงรักษาตำแหน่งไว้

หวังว่านี่จะช่วยได้


1

ฉันไม่ได้ใช้ Recyclerview แต่ฉันทำบน ListView โค้ดตัวอย่างใน Recyclerview:

setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        rowPos = mLayoutManager.findFirstVisibleItemPosition();

เป็นผู้ฟังเมื่อผู้ใช้กำลังเลื่อน ค่าใช้จ่ายด้านประสิทธิภาพไม่สำคัญ และตำแหน่งแรกที่มองเห็นได้แม่นยำด้วยวิธีนี้


เอาล่ะfindFirstVisibleItemPositionส่งคืน "ตำแหน่งอะแดปเตอร์ของมุมมองแรกที่มองเห็นได้" แต่สิ่งที่เกี่ยวกับออฟเซ็ต
Konrad Morawski

1
มีวิธีการที่คล้ายกันสำหรับ Recyclerview to setSelection method ของ ListView สำหรับตำแหน่งที่มองเห็นได้หรือไม่? นี่คือสิ่งที่ฉันทำกับ ListView
Android ดั้งเดิม

1
วิธีใช้ setScrollY กับพารามิเตอร์ dy ค่าที่คุณจะบันทึกเป็นอย่างไร ถ้าได้ผลฉันจะอัปเดตคำตอบ ฉันไม่ได้ใช้ Recyclerview แต่ฉันต้องการใช้แอปในอนาคต
Android ดั้งเดิม

โปรดดูคำตอบของฉันด้านล่างเกี่ยวกับวิธีบันทึกตำแหน่งการเลื่อน
A. Steenbergen

1
เอาล่ะฉันจะจำไว้ในครั้งต่อไปฉันไม่ได้ลงคะแนนให้คุณด้วยเจตนาร้าย อย่างไรก็ตามฉันไม่เห็นด้วยกับคำตอบที่สั้นและไม่สมบูรณ์เพราะเมื่อฉันเริ่มหาคำตอบสั้น ๆ ที่นักพัฒนาคนอื่นคิดว่าข้อมูลพื้นหลังจำนวนมากไม่ได้ช่วยฉันได้จริงๆเพราะพวกเขามักจะไม่สมบูรณ์เกินไปและฉันต้องถามคำถามที่ชี้แจงจำนวนมากสิ่งที่คุณ มักจะไม่สามารถทำได้ในโพสต์ stackoverflow แบบเก่า สังเกตด้วยว่า Konrad ไม่ยอมรับคำตอบซึ่งบ่งชี้ว่าคำตอบเหล่านี้ไม่สามารถแก้ปัญหาของเขาได้ แม้ว่าฉันจะเห็นประเด็นทั่วไปของคุณ
A. Steenbergen

0

นี่คือตัวเลือกสำหรับผู้ที่ใช้DataBindingสำหรับRecyclerView. ฉันมีvar recyclerViewState: Parcelable?ในอะแดปเตอร์ของฉัน และฉันใช้ a BindingAdapterกับรูปแบบของคำตอบของ @ DawnYu เพื่อตั้งค่าและอัปเดตข้อมูลในRecyclerView:

@BindingAdapter("items")
fun setRecyclerViewItems(
    recyclerView: RecyclerView,
    items: List<RecyclerViewItem>?
) {
    var adapter = (recyclerView.adapter as? RecyclerViewAdapter)
    if (adapter == null) {
        adapter = RecyclerViewAdapter()
        recyclerView.adapter = adapter
    }

    adapter.recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
    // the main idea is in this call with a lambda. It allows to avoid blinking on data update
    adapter.submitList(items.orEmpty()) {
        adapter.recyclerViewState?.let {
            recyclerView.layoutManager?.onRestoreInstanceState(it)
        }
    }
}

สุดท้ายส่วน XML มีลักษณะดังนี้:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/possible_trips_rv"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:items="@{viewState.yourItems}"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

0

ฉันทำพลาดแบบนี้บางทีมันอาจจะช่วยใครก็ได้ :)

หากคุณใช้recylerView.setAdapterทุกครั้งที่มีข้อมูลใหม่จะเรียกใช้clear()เมธอดอะแด็ปเตอร์ทุกครั้งที่ใช้ซึ่งทำให้recyclerviewรีเฟรชและเริ่มต้นใหม่ adapter.notiftyDatasetChanced()เพื่อกำจัดนี้คุณจำเป็นต้องใช้


-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

วิธีแก้ปัญหาคือการเลื่อนมุมมองรีไซเคิลไปเรื่อย ๆ เมื่อมีข้อความใหม่มา

เมธอด onChanged () ตรวจจับการดำเนินการที่ดำเนินการบนรีไซเคิล


-1

นั่นใช้ได้ผลสำหรับฉันใน Kotlin

  1. สร้างอะแดปเตอร์และส่งมอบข้อมูลของคุณในตัวสร้าง
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. เปลี่ยนคุณสมบัตินี้และโทรไปที่ alertDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

การชดเชยการเลื่อนไม่เปลี่ยนแปลง


-2

ฉันมีปัญหากับรายการซึ่งแต่ละรายการมีเวลาเป็นนาทีจนกว่าจะถึงกำหนด 'และจำเป็นต้องอัปเดต ฉันจะอัปเดตข้อมูลแล้วหลังจากนั้นโทร

orderAdapter.notifyDataSetChanged();

และจะเลื่อนขึ้นไปด้านบนทุกครั้ง ฉันแทนที่ด้วย

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

และมันก็สบายดี ไม่มีวิธีอื่นใดในชุดข้อความนี้ที่ใช้ได้ผลสำหรับฉัน ในการใช้วิธีนี้มันทำให้แต่ละรายการกะพริบเมื่อได้รับการอัปเดตดังนั้นฉันจึงต้องใส่สิ่งนี้ไว้ใน onCreateView ของส่วนหลักด้วย

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }

-2

หากคุณมี EditTexts อย่างน้อยหนึ่งรายการภายในรายการ Recyclerview ให้ปิดใช้งานออโต้โฟกัสของสิ่งเหล่านี้โดยวางการกำหนดค่านี้ในมุมมองหลักของการรีไซเคิลวิว

android:focusable="true"
android:focusableInTouchMode="true"

ฉันมีปัญหานี้เมื่อฉันเริ่มกิจกรรมอื่นที่เปิดตัวจากรายการรีไซเคิลมุมมองเมื่อฉันกลับมาและตั้งค่าการอัปเดตของหนึ่งเขตข้อมูลในรายการเดียวโดยมีการแจ้งเตือนรายการเปลี่ยน (ตำแหน่ง) การเลื่อนของ RV และข้อสรุปของฉันคือออโต้โฟกัสของ EditText รายการรหัสด้านบนช่วยแก้ปัญหาของฉันได้

ดีที่สุด.


-3

เพียงกลับมาถ้า oldPosition และ position เหมือนกัน

private int oldPosition = -1;

public void notifyItemSetChanged(int position, boolean hasDownloaded) {
    if (oldPosition == position) {
        return;
    }
    oldPosition = position;
    RLog.d(TAG, " notifyItemSetChanged :: " + position);
    DBMessageModel m = mMessages.get(position);
    m.setVideoHasDownloaded(hasDownloaded);
    notifyItemChanged(position, m);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.