RecyclerView.Adapter中是否有观察者可以知道数据集是否已更改?

20

我已经按照以下方式使用自定义适配器实现了我的RecyclerView

全局声明如下:

private LinearLayoutManager linearLayoutManager;
private int pastVisibleItems, visibleItemCount, totalItemCount;
private CustomRecyclerViewAdapter customRecyclerViewAdapter;

首先在onCreate()方法中创建了适配器实例,其中包含一个空的Array,并将其设置为recyclerView

linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
    Utility.ItemDecorationConst);
recyclerView.addItemDecoration(dividerItemDecoration);
customRecyclerViewAdapter = new CustomRecyclerViewAdapter(getActivity());

recyclerView.setAdapter(customRecyclerViewAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

    visibleItemCount = linearLayoutManager.getChildCount();
    totalItemCount = linearLayoutManager.getItemCount();
    pastVisibleItems = linearLayoutManager.findFirstVisibleItemPosition();
    if (loading) {
        if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
            loading = false;
            customRecyclerViewAdapter.addProgressBarEntry();
            controller.getNextPage(PublisherAppContainerFragment.this);
        }
    }
}
});

在完成视图渲染后,当我从 AsyncTask 中获取数据以填充 recyclerView 时,我调用 Adapter 的以下方法来填充数据

customRecyclerViewAdapter.addAll(myArray);

注意:addAll()不是任何重写的方法

以下是我的CustomRecyclerViewAdapter的代码

class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {
    ArrayList<MyModel> arrayList = new ArrayList<>();
    Context context;

    public CustomRecyclerViewAdapter(Context context) {
        this.context = context;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewHolder viewHolder = null;
        //inflated some view
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        //binded data to holder
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public void addAll(ArrayList myArray) {
        this.arrayList.addAll(myArray)
    }

    public void clear() {
        arrayList.clear();
    }
    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public CardView cardView;

        public ViewHolder(View view) {
        super(view);
            this.cardView = (CardView) view.findViewById(R.id.card_view);
            this.cardView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
        //handle operations
        }
    }
}

所以每当我从 AsynTask 获取数据时,我调用方法 addAll()recyclerView 就像魔法一样正常工作。

现在,我的问题是即使我从未在 adapter 上调用过 notifyDataSetChanged(),它为什么能够正常工作?是否有为适配器注册的观察者?观察者对在 public int getItemCount() 中返回的数据集是否发生更改进行观察?

根据我从 documentation 中读到的:

void notifyDataSetChanged ()

通知任何已注册的观察者数据集已更改。

这意味着即使有一些已注册的 observers,你也需要使用 notifyDataSetChanged()notify 它们。对吗?

我也调用了

boolean flag = customRecyclerViewAdapter.hasObservers();

想知道是否有任何观察者已注册?FlagTrue

所以请问有人可以帮我理解这些东西是如何工作的吗?


请粘贴您调用addAll()的AsyncTask代码。 - Pravin Divraniya
我也测试了同样的事情,并使用了与您相同的适配器架构。第一次它会将所有项目添加到RecyclerView中。但之后,除非我调用notifyDataSetChanged或notifyItemRangeInserted,否则addAll()不会刷新列表。 - Pravin Divraniya
5个回答

5
如果您查看RecyclerView setAdapter的源代码,您会发现一个方法setAdapterInternal(adapter, false, true);,该方法负责以下工作:

用新适配器替换当前适配器并触发监听器。

这个方法负责将旧适配器与新适配器交换,并在内部注册自定义 Data Observer 。这就是为什么您会得到标志为 true 的原因。

是的,它设置了 setAdapterInternal 并注册了“观察者”。我调试我的应用程序以确定它是否真正进入了RecyclerView的默认“观察者”,但它并没有进入任何“观察者”。 - Nikhil

2
根据我所看到的代码,我认为您的RecyclerView没有附加任何观察者来捕获更改并保持列表更新。更有可能的是,当您浏览列表时,布局管理器会不断调用适配器上的getItemCount()以确定是否应显示更多项目,这只是"幸运"发生的。每当您调用addAll()时,都会静默地更新项目计数,并且似乎观察者已收到更改的通知。
这绝对是一个错误,如果您依赖于特定的观察者来监视列表的某些方面,或者不仅仅是在底部追加新项目(例如修改或插入现有项目),则更有可能看到其影响。正如您指出的那样,正确的实现是在更新列表时调用notifyDataSetChanged(),或者如果可以更具体地说明更改,则更好。例如,您可以使用:
public void addAll(ArrayList myArray) {
    int positionStart = getItemCount() - 1;
    this.arrayList.addAll(myArray);
    notifyItemRangeInserted(positionStart, myArray.size());
}

public void clear() {
    int oldSize = getItemCount();
    arrayList.clear();
    notifyItemRangeRemoved(0, oldSize);
}

我非常感谢你的回答。正如你所说,“更有可能的是,当你滚动列表时,布局管理器会不断调用适配器上的getItemCount()方法”,这可能是正确的,但第一次获取数据时,我显示了“进度条”,一旦我获取到数据,我隐藏了“进度条”并使“recyclerView”可见。所以第一次我根本没有滚动,但它仍然完美地加载了。这是怎么发生的? - Nikhil
嗯...在第一次获取数据之前,您是先将适配器设置在RecyclerView上,还是等到初始数据后再设置适配器? - happydude
我已经使用空初始化列表设置了我的适配器。然后获取数据并将其添加到适配器的数据集中,但不调用notifyDataSetChanged() - Nikhil
抱歉,我也被卡住了。我查看了大部分RecyclerView和LinearLayoutManager的代码,没有发现任何引起此行为的异常。你的hasObservers()方法返回true的原因是因为当你最初设置适配器时,RecyclerView本身会设置一个观察者,但除非你调用其中的一个“notify”方法,否则它似乎并不会实际执行任何操作。 - happydude

2
我的问题是,即使我从未在适配器上调用notifyDataSetChanged()方法,它也能很好地工作。这是因为addAll方法默认会调用notifyDataSetChanged()方法。
public void addAll(T ... items) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                Collections.addAll(mOriginalValues, items);
            } else {
                Collections.addAll(mObjects, items);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

And

public void addAll(@NonNull Collection<? extends T> collection) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.addAll(collection);
            } else {
                mObjects.addAll(collection);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

这是链接 - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java 编辑 - 我看到你有自己的addAll方法,它调用了ArrayList的addAll方法。 这是addAll方法的工作方式 -
 private ArrayList<String> ex1 = new ArrayList();
 private ArrayList<String> ex2 = new ArrayList();
 private ArrayList<String> ex3 = new ArrayList();


    ex1.add("one");
    ex2.add("two");
    ex3.addAll(ex1);
    ex3.addAll(ex2);

    System.out.println(ex3);

输出 - [一,二]

这就是在您的情况下正在发生的事情。


我已经尝试将“addAll()”更改为“AddAllItems()”。仍然可以正常工作。因此,我认为这与ArrayAdapteraddAll()方法无关,因为RecyclerView.Adapter没有被ArrayAdapter扩展。 - Nikhil

1
我已经展示了进度条,一旦获取到数据,我就隐藏进度条并将recyclerView设为可见。如果在布局或代码中将RecyclerView的可见性设置为GONE,则布局不会发生,这就是为令Adapter.getItemsCount()未被调用的原因。因此,如果您获取数据并用其填充适配器数组,然后将RecyclerView的可见性从GONE更改为VISIBLE,它将触发更新。
如果您没有调用notifyDataSetChanged(),则RecyclerView将不知道有关更新的信息。我猜您的代码中还有其他东西可以触发RecyclerView更新。为了澄清这种行为,让我们使用一些虚拟适配器:
private class DummyViewHolder extends RecyclerView.ViewHolder {
    public DummyViewHolder (View itemView) {
        super(itemView);
    }
}

private class Adapter extends RecyclerView.Adapter<DummyViewHolder> {
    private int mDummySize = 5;
    @Override
    public DummyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.dummy_view, parent, false);
        return new DummyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(DummyViewHolder holder, int position) {

    }

    void setSize(int size) { this.mDummySize = size; }

    @Override
    public int getItemCount() {
        return mDummySize;
    }
}

在onCreate()方法中:

并且代码如下:

    ViewHolder v = ...
    final Adapter adapter = ..
    ...
    //postpone adapter update
    (new Handler()).postDelayed(new Runnable() {
       @Override
       public void run() {
           adapter.setSize(10);//and nothing happend only 5 items on screen
       }
    }, 5000);

0
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
        override fun onChanged() {
            super.onChanged()
            //your logic
        }
    })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接