Làm mới dữ liệu trong RecyclerView và giữ nguyên vị trí cuộn của nó


96

Làm cách nào để làm mới dữ liệu được hiển thị trong RecyclerView(gọi notifyDataSetChangedtrên bộ điều hợp của nó) và đảm bảo rằng vị trí cuộn được đặt lại về chính xác vị trí của nó?

Trong trường hợp tốt, ListViewtất cả những gì cần làm là truy xuất getChildAt(0), kiểm tra getTop()và gọi setSelectionFromTopvới cùng một dữ liệu chính xác sau đó.

Nó dường như không thể thực hiện được trong trường hợp RecyclerView.

Tôi đoán tôi phải sử dụng nó LayoutManagermà thực sự cung cấp scrollToPositionWithOffset(int position, int offset), nhưng cách thích hợp để truy xuất vị trí và bù đắp là gì?

layoutManager.findFirstVisibleItemPosition()layoutManager.getChildAt(0).getTop()?

Hay có cách nào thanh lịch hơn để hoàn thành công việc?


Nó là về các giá trị chiều rộng và chiều cao RecyclerView hoặc LayoutManager của bạn. Kiểm tra giải pháp này: stackoverflow.com/questions/28993640/…
serefakyuz

@Konard, Trong trường hợp của tôi, tôi có danh sách tệp âm thanh có triển khai Seekbar và đang chạy với sự cố sau: 1) Đối với một số mục được lập chỉ mục cuối cùng ngay khi nó được kích hoạt thông báo với mục đích làtifyItemChanged (pos), nó sẽ tự động đẩy chế độ xem ở dưới cùng. 2) Trong khi tiếp tục thông báo với mục tiêu (pos), nó bị kẹt danh sách và không cho phép cuộn dễ dàng. Mặc dù tôi sẽ hỏi như một câu hỏi nhưng hãy cho tôi biết nếu bạn có bất kỳ cách tiếp cận nào tốt hơn để khắc phục sự cố như vậy.
CoDe

Câu trả lời:


139

Tôi sử dụng cái này. ^ _ ^

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

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

Nó là đơn giản hơn, hy vọng nó sẽ giúp bạn!


1
Đây thực sự là câu trả lời đúng imo. Nó trở nên phức tạp chỉ vì tải dữ liệu không đồng bộ, nhưng bạn có thể trì hoãn việc khôi phục.
EpicPandaForce

hoàn thiện 2 chính xác những gì tôi đang tìm kiếm
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m Tôi đang cố gắng triển khai điều này nhưng nó không hoạt động, tôi đang làm gì sai?
Kaustubh Bhagwat

@KaustubhBhagwat Có lẽ bạn nên kiểm tra "RecyclerViewState! = Null" trước khi sử dụng.
DawnYu

4
Chính xác thì tôi nên sử dụng mã này ở đâu? trong phương thức onResume?
Lucky_girl

47

Tôi có vấn đề khá tương tự. Và tôi đã đưa ra giải pháp sau đây.

Sử dụng notifyDataSetChangedlà một ý tưởng tồi. Bạn nên cụ thể hơn, sau đó RecyclerViewsẽ lưu trạng thái cuộn cho bạn.

Ví dụ: nếu bạn chỉ cần làm mới, hay nói cách khác, bạn muốn từng chế độ xem được liên kết lại, chỉ cần thực hiện như sau:

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

2
Tôi đã thử adapter.notifyItemRangeChanged (0, adapter.getItemCount ()) ;, điều đó cũng hoạt động như .notifyDataSetChanged ()
Mani

4
Điều này đã giúp trong trường hợp của tôi. Tôi đang chèn một số mục ở vị trí 0 và với notifyDataSetChangedvị trí cuộn sẽ thay đổi hiển thị các mục mới. Tuy nhiên, nếu sử dụng notifyItemRangeInserted(0, newItems.size() - 1), RecyclerViewgiữ trạng thái cuộn.
Carlosph

câu trả lời rất tốt, tôi đang tìm kiếm giải pháp của câu trả lời đó cả một ngày. cảm ơn bạn, rất nhiều ..
Gundu Bandgar

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); nên tránh, như được chỉ ra trong Google I / O mới nhất (xem phần trình bày về tái chế từ đầu đến cuối), tốt hơn (nếu bạn có kiến ​​thức) nên cho người xem biết chính xác những mục nào đã thay đổi thay vì làm mới toàn bộ của tất cả các chế độ xem.
A. Steenbergen

3
nó không làm việc, recyclerview mới lạ cho đầu mặc dù tôi đã thêm
Shaahul

21

CHỈNH SỬA: Để khôi phục lại vị trí biểu kiến chính xác , như trong, làm cho nó trông giống hệt như lúc trước, chúng ta cần làm điều gì đó khác một chút (Xem bên dưới cách khôi phục giá trị scrollY chính xác):

Lưu vị trí và bù đắp như thế này:

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

Và sau đó khôi phục cuộn như thế này:

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

Điều này khôi phục danh sách về vị trí rõ ràng chính xác của nó . Rõ ràng vì nó sẽ trông giống nhau đối với người dùng, nhưng nó sẽ không có cùng giá trị scrollY (vì có thể có sự khác biệt về kích thước bố cục ngang / dọc).

Lưu ý rằng điều này chỉ hoạt động với LinearLayoutManager.

--- Dưới đây làm thế nào để khôi phục lại chính xác scrollY, có thể sẽ làm cho danh sách trông khác ---

  1. Áp dụng một OnScrollListener như vậy:

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

Điều này sẽ lưu trữ vị trí cuộn chính xác mọi lúc trong mScrollY.

  1. Lưu trữ biến này trong Gói của bạn và khôi phục nó ở trạng thái khôi phục thành một biến khác , chúng tôi sẽ gọi nó là mStateScrollY.

  2. Sau khi khôi phục trạng thái và sau khi RecyclerView của bạn đã đặt lại tất cả dữ liệu của nó, hãy đặt lại cuộn với điều này:

    mRecyclerView.scrollBy(0, mStateScrollY);

Đó là nó.

Lưu ý rằng bạn khôi phục cuộn sang một biến khác, điều này rất quan trọng, vì OnScrollListener sẽ được gọi với .scrollBy () và sau đó sẽ đặt mScrollY thành giá trị được lưu trữ trong mStateScrollY. Nếu bạn không làm điều này, mScrollY sẽ có giá trị cuộn gấp đôi (vì OnScrollListener hoạt động với delta, không phải cuộn tuyệt đối).

Tiết kiệm nhà nước trong các hoạt động có thể đạt được như sau:

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

Và để khôi phục, hãy gọi điều này trong onCreate () của bạn:

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

Lưu trạng thái trong các đoạn hoạt động theo cách tương tự, nhưng việc lưu trạng thái thực tế cần thêm một chút công việc, nhưng có rất nhiều bài viết giải quyết vấn đề đó, vì vậy bạn sẽ không gặp khó khăn khi tìm hiểu cách thức, nguyên tắc lưu cuộn Y và khôi phục nó vẫn như cũ.


tôi không hiểu bạn có thể đăng đầy đủ ví dụ không?

Đây thực sự là càng gần đến một ví dụ đầy đủ như bạn có thể nhận được nếu bạn không muốn tôi gửi toàn bộ hoạt động của tôi
A. Steenbergen

Tôi không hiểu gì cả. Nếu có các mục được thêm vào đầu danh sách, thì việc giữ nguyên vị trí cuộn sẽ không sai, vì thực sự bây giờ bạn sẽ xem xét các mục khác nhau đã chiếm lĩnh khu vực này?
nhà phát triển android

Có, trong trường hợp đó, bạn phải điều chỉnh vị trí khi khôi phục, tuy nhiên, đây không phải là một phần của câu hỏi, vì thường trong khi xoay bạn không thêm hoặc bớt các mục. Đó sẽ là hành vi và hành vi kỳ lạ và có lẽ không có lợi cho người dùng ngoại trừ một số trường hợp đặc biệt có thể tồn tại mà tôi không thể hình dung được, thành thật mà nói.
A. Steenbergen

9

Có, bạn có thể giải quyết vấn đề này bằng cách tạo bộ khởi tạo bộ điều hợp chỉ một lần, tôi đang giải thích phần mã hóa ở đây:

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

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

Bây giờ bạn có thể thấy tôi đã kiểm tra bộ điều hợp là null hay không và chỉ khởi tạo khi nó là null.

Nếu bộ điều hợp không null thì tôi yên tâm rằng tôi đã khởi tạo bộ điều hợp của mình ít nhất một lần.

Vì vậy, tôi sẽ chỉ thêm danh sách vào bộ điều hợp và gọi thông báo đã thay đổi.

RecyclerView luôn giữ vị trí cuối cùng được cuộn, do đó bạn không phải lưu trữ vị trí cuối cùng, chỉ cần gọi thông báo đã thay đổi, chế độ xem tái chế luôn làm mới dữ liệu mà không cần lên đầu trang.

Cảm ơn Happy Coding


Tôi nghĩ rằng đây sẽ là câu trả lời được chấp nhận @ Konrad Morawski
Jithin Jude

5

Giữ vị trí cuộn bằng cách sử dụng câu trả lời @DawnYu để quấn notifyDataSetChanged()như thế này:

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

hoàn hảo ... câu trả lời hay nhất
jlandyr

Tôi đã tìm kiếm câu trả lời này trong một thời gian dài. Cảm ơn rât nhiều.
Alaa AbuZarifa

1

Câu trả lời hàng đầu của @DawnYu hoạt động, nhưng chế độ xem tái chế trước tiên sẽ cuộn lên trên cùng, sau đó quay trở lại vị trí cuộn dự định gây ra phản ứng "nhấp nháy như" không dễ chịu.

Để làm mới Chế độ xem tái chế, đặc biệt là sau khi đến từ một hoạt động khác, không bị nhấp nháy và duy trì vị trí cuộn, bạn cần thực hiện như sau.

  1. Đảm bảo rằng bạn đang cập nhật chế độ xem người tái chế bằng DiffUtil. Đọc thêm về điều đó tại đây: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Tiếp tục hoạt động của bạn hoặc tại thời điểm bạn muốn cập nhật hoạt động của mình, hãy tải dữ liệu vào chế độ xem lại của bạn. Sử dụng diffUtil, chỉ các cập nhật sẽ được thực hiện trên chế độ xem lại trong khi vẫn duy trì vị trí của nó.

Hi vọng điêu nay co ich.


0

Tôi chưa sử dụng Recyclerview nhưng tôi đã làm điều đó trên ListView. Mã mẫu trong Recyclerview:

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

Nó là người nghe khi người dùng đang cuộn. Hiệu suất chi phí không đáng kể. Và vị trí có thể nhìn thấy đầu tiên là chính xác theo cách này.


Được rồi, findFirstVisibleItemPositiontrả về "vị trí bộ điều hợp của chế độ xem có thể nhìn thấy đầu tiên", nhưng còn phần bù đắp.
Konrad Morawski

1
Có phương pháp nào tương tự cho Recyclerview với phương thức setSelection của ListView cho vị trí hiển thị không? Đây là những gì tôi đã làm cho ListView.
Android gốc

1
Làm thế nào về việc sử dụng phương thức setScrollY với tham số dy, một giá trị mà bạn sẽ lưu? Nếu nó hoạt động, tôi sẽ cập nhật câu trả lời của mình. Tôi chưa sử dụng Recyclerview nhưng tôi muốn sử dụng trong ứng dụng tương lai của mình.
Android gốc

Vui lòng xem câu trả lời của tôi dưới đây về cách lưu vị trí cuộn.
A. Steenbergen

1
Được rồi, tôi sẽ ghi nhớ điều đó vào lần sau, tôi không ác ý với bạn đâu. Tuy nhiên, tôi không đồng ý với những câu trả lời ngắn gọn và không đầy đủ vì khi tôi bắt đầu những câu trả lời ngắn trong đó các nhà phát triển khác cho rằng nhiều thông tin cơ bản không thực sự giúp ích cho tôi, vì chúng thường quá đầy đủ và tôi phải hỏi rất nhiều câu hỏi làm rõ, bạn thường không thể thực hiện trên các bài đăng stackoverflow cũ. Cũng lưu ý rằng không có câu trả lời nào được Konrad chấp nhận, điều này cho thấy rằng những câu trả lời này không giải quyết được vấn đề của anh ấy. Mặc dù tôi thấy điểm chung của bạn.
A. Steenbergen

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

Giải pháp ở đây là tiếp tục cuộn chế độ xem lại khi có tin nhắn mới.

Phương thức onChanged () phát hiện hành động được thực hiện trên chế độ xem lại.


-1

Điều đó làm việc cho tôi ở Kotlin.

  1. Tạo Bộ điều hợp và chuyển giao dữ liệu của bạn trong hàm tạo
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. thay đổi thuộc tính này và gọi thông báoDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Độ lệch cuộn không thay đổi.


-2

Tôi gặp sự cố này với danh sách các mục có thời gian tính bằng phút cho đến khi chúng 'đến hạn' và cần cập nhật. Tôi sẽ cập nhật dữ liệu và sau đó, hãy gọi

orderAdapter.notifyDataSetChanged();

và nó sẽ cuộn lên đầu mọi lúc. Tôi đã thay thế nó bằng

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

và nó đã ổn. Không có phương pháp nào khác trong chủ đề này phù hợp với tôi. Tuy nhiên, khi sử dụng phương pháp này, nó làm cho từng mục riêng lẻ nhấp nháy khi nó được cập nhật, vì vậy tôi cũng phải đặt điều này vào onCreateView của phân đoạn mẹ

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

-2

Nếu bạn có một hoặc nhiều EditTexts bên trong một mục recyclerview, vô hiệu hóa tự động lấy nét trong số này, đặt cấu hình này trong cha mẹ quan điểm của recyclerview:

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

Tôi đã gặp sự cố này khi bắt đầu một hoạt động khác được khởi chạy từ mục tái chế, khi tôi quay lại và đặt cập nhật cho một trường trong một mục với cuộn thông báo RV (vị trí) di chuyển RV và kết luận của tôi là, tính năng tự động lấy nét của EditText Các mục, mã trên đã giải quyết được vấn đề của tôi.

tốt.


-3

Chỉ cần quay lại nếu Vị trí và vị trí cũ giống nhau;

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.