notifyDataSetChanged 不适用于 RecyclerView

Posted

技术标签:

【中文标题】notifyDataSetChanged 不适用于 RecyclerView【英文标题】:notifyDataSetChanged not working on RecyclerView 【发布时间】:2014-09-04 14:34:09 【问题描述】:

我正在从服务器获取数据,然后对其进行解析并将其存储在列表中。我将此列表用于 RecyclerView 的适配器。我正在使用片段。

我正在使用带有 KitKat 的 Nexus 5。我正在为此使用支持库。这会有所作为吗?

这是我的代码:(对问题使用虚拟数据)

成员变量:

List<Business> mBusinesses = new ArrayList<Business>();

RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;

我的onCreateView()

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) 

    // Getting data from server
    getBusinessesDataFromServer();

    View view = inflater.inflate(R.layout.fragment_business_list,
            container, false);
    recyclerView = (RecyclerView) view
            .findViewById(R.id.business_recycler_view);
    recyclerView.setHasFixedSize(true);

    mLayoutManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(mLayoutManager);

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    recyclerView.setAdapter(mBusinessAdapter);

    return view;

从服务器获取数据后,调用parseResponse()

protected void parseResponse(JSONArray response, String url) 
    // insert dummy data for demo

    mBusinesses.clear();

    Business business;

    business = new Business();
    business.setName("Google");
    business.setDescription("Google HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Yahoo");
    business.setDescription("Yahoo HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Microsoft");
    business.setDescription("Microsoft HeadQuaters");
    mBusinesses.add(business);

    Log.d(Const.DEBUG, "Dummy Data Inserted\nBusinesses Length: "
            + mBusinesses.size());

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    mBusinessAdapter.notifyDataSetChanged();

我的业务适配器:

public class BusinessAdapter extends
    RecyclerView.Adapter<BusinessAdapter.ViewHolder> 

    private List<Business> mBusinesses = new ArrayList<Business>();

    // Provide a reference to the type of views that you are using
    // (custom viewholder)
    public static class ViewHolder extends RecyclerView.ViewHolder 
        public TextView mTextViewName;
        public TextView mTextViewDescription;
        public ImageView mImageViewLogo;

        public ViewHolder(View v) 
            super(v);
            mTextViewName = (TextView) v
                    .findViewById(R.id.textView_company_name);
            mTextViewDescription = (TextView) v
                    .findViewById(R.id.textView_company_description);
            mImageViewLogo = (ImageView) v
                    .findViewById(R.id.imageView_company_logo);
        
    

    // Provide a suitable constructor (depends on the kind of dataset)
    public BusinessAdapter(List<Business> myBusinesses) 

        Log.d(Const.DEBUG, "BusinessAdapter -> constructor");

        mBusinesses = myBusinesses;
    

    // Create new views (invoked by the layout manager)
    @Override
    public BusinessAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
            int viewType) 

        Log.d(Const.DEBUG, "BusinessAdapter -> onCreateViewHolder()");

        // create a new view
        View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_business_list, parent, false);

        ViewHolder vh = new ViewHolder(v);
        return vh;
    

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) 
        // - get element from your dataset at this position
        // - replace the contents of the view with that element

        Log.d(Const.DEBUG, "BusinessAdapter -> onBindViewHolder()");

        Business item = mBusinesses.get(position);
        holder.mTextViewName.setText(item.getName());
        holder.mTextViewDescription.setText(item.getDescription());
        holder.mImageViewLogo.setImageResource(R.drawable.ic_launcher);

    

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() 

        Log.d(Const.DEBUG, "BusinessAdapter -> getItemCount()");

        if (mBusinesses != null) 
            Log.d(Const.DEBUG, "mBusinesses Count: " + mBusinesses.size());
            return mBusinesses.size();
        
        return 0;
    

但我没有得到视图中显示的数据。我做错了什么?

这是我的日志,

07-14 21:15:35.669: D/xxx(2259): Dummy Data Inserted
07-14 21:15:35.669: D/xxx(2259): Businesses Length: 3
07-14 21:26:26.969: D/xxx(2732): BusinessAdapter -> constructor

在此之后我没有收到任何日志。不应该再次调用适配器中的getItemCount() 吗?

【问题讨论】:

【参考方案1】:

在您的parseResponse() 中,您正在创建BusinessAdapter 类的新实例,但您实际上并未在任何地方使用它,因此您的RecyclerView 不知道新实例存在。

您需要:

再次调用 recyclerView.setAdapter(mBusinessAdapter) 以更新 RecyclerView 的适配器引用以指向您的新适配器 或者只是删除mBusinessAdapter = new BusinessAdapter(mBusinesses); 以继续使用现有的适配器。由于您尚未更改 mBusinesses 引用,因此适配器仍将使用该数组列表,并且在您调用 notifyDataSetChanged() 时应正确更新。

【讨论】:

感谢您的帮助!!!明白了..刚刚删除了再次创建新实例的行。将在 8 分钟内接受您的回答。 这对我不起作用,我不得不重新创建适配器并将其重新设置为 recylerview 我遵循您的第二点,使用新的 BusinessAdapter(mBusinesses) 再次创建适配器。我必须以任何方式释放旧适配器吗?如果我每次都继续创建一个新适配器,这会导致内存泄漏吗? 只要您没有静态引用任何内容或以其他方式保留引用,您就可以放开旧的引用。 只需使用int index = list.indexOf(object)找到您在列表中添加或删除数据的位置,根据该索引您可以使用adapter.notifyItemChanged(index)通知数据【参考方案2】:

试试这个方法:

List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification

有点费时,但应该可以。

【讨论】:

此语句:List mBusinesses2 = mBusinesses; 制作列表的深层副本。所以你没有克隆列表 - 这是一件事,第二件事是我看不出这如何解决原来的问题.... 其实我也遇到过类似的问题,是这样解决的:solution @kosiara-BartoszKosarzycki 然而确实如此【参考方案3】:

只是为了补充其他答案,因为我认为这里没有人提到这一点:notifyDataSetChanged() 应该在主线程上执行RecyclerView.Adapter 的其他 notify&lt;Something&gt; 方法也是如此,当然)

据我所知,由于您在同一块中有解析过程和对notifyDataSetChanged() 的调用,因此您要么从工作线程调用它,要么在主线程上进行 JSON 解析(即我相信你也知道,这也是一个禁忌)。所以正确的方法是:

protected void parseResponse(JSONArray response, String url) 
    // insert dummy data for demo
    // <yadda yadda yadda>
    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    // or just use recyclerView.post() or [Fragment]getView().post()
    // instead, but make sure views haven't been destroyed while you were
    // parsing
    new Handler(Looper.getMainLooper()).post(new Runnable() 
        public void run() 
            mBusinessAdapter.notifyDataSetChanged();
        
    );

PS 奇怪的是,我认为您不会从 IDE 或运行时日志中获得有关主线程事物的任何迹象。这仅来自我个人的观察:如果我确实从工作线程调用 notifyDataSetChanged(),我不会得到强制只有创建视图层次结构的原始线程才能触及其视图消息或任何东西像那样 - 它只是默默地失败(在我的情况下,一个非主线程调用甚至可以阻止后续的主线程调用正常运行,可能是因为某种竞争条件)

此外,RecyclerView.Adapter api reference 和相关官方dev guide 都没有明确提到目前(现在是 2017 年)的主线程要求,android Studio lint 检查规则似乎也没有涉及这个问题。

但是,here is an explanation这是作者本人

【讨论】:

非常感谢!在我尝试过的所有方法中,你的工作完美无缺。 @Maximus 很高兴我能帮上忙,伙计 该死!不需要从主线程调用 notifyDataset 更改侦听器,完美,+1【参考方案4】:

我有同样的问题。我刚刚通过在 onCreate of class 之前声明 adapter public 来解决它。

PostAdapter postAdapter;

之后

postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);

我终于打电话了:

@Override
protected void onPostExecute(Void aVoid) 
    super.onPostExecute(aVoid);
    // Display the size of your ArrayList
    Log.i("TAG", "Size : " + posts.size());
    progressBar.setVisibility(View.GONE);
    postAdapter.notifyDataSetChanged();

希望这会对你有所帮助。

【讨论】:

【参考方案5】:

虽然有点奇怪,但是notifyDataSetChanged 在不为适配器设置新值的情况下并不能真正工作。所以,你应该这样做:

array = getNewItems();                    
((MyAdapter) mAdapter).setValues(array);  // pass the new list to adapter !!!
mAdapter.notifyDataSetChanged();       

这对我有用。

【讨论】:

【参考方案6】:

清除旧视图模型并将新数据设置到适配器并调用notifyDataSetChanged()

【讨论】:

【参考方案7】:

在我的情况下,在主 ui 线程中强制运行 #notifyDataSetChanged 将修复

public void refresh() 
        clearSelection();
        // notifyDataSetChanged must run in main ui thread, if run in not ui thread, it will not update until manually scroll recyclerview
        ((Activity) ctx).runOnUiThread(new Runnable() 
            @Override
            public void run() 
                adapter.notifyDataSetChanged();
            
        );
    

【讨论】:

【参考方案8】:

我总是有这个问题,我忘记了 RecyclerView 每次你提供适配器时都需要一个新的 List 实例。

List<X> deReferenced = new ArrayList(myList);
adapter.submitList(deReferenced);

拥有“相同”列表(参考)意味着即使列表大小发生变化也不声明“新”,因为对列表执行的更改也会传播到其他列表(当它们被简单地声明为this.localOtherList = myList时)强调关键字是“=”,通常比较集合的组件会在事后复制结果并将其存储为“旧”,而不是 Android DiffUtil。

因此,如果您的组件每次提交时都提供相同的 List,则 RecyclerView 不会触发新的布局传递。 原因是...... AFAIR,在 DiffUtil 甚至尝试应用 Mayers 算法之前,有一行代码:

 if (newList == mList)) return;

我不确定在同一系统中取消引用有多少“良好实践”实际上被定义为“良好”...... 特别是因为差异算法预计有一个新的(修订的)与旧的(原始)组件,理论上应该在进程结束后自行取消引用集合但是......谁知道......?

但是等等,还有更多……

执行 new ArrayList() 会取消对 List 的引用,但由于某种原因,Oracle 决定他们应该创建第二个具有相同名称但功能不同的“ArrayList”。

这个 ArrayList 在 Arrays 类中。

/**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with @link Collection#toArray.  The returned list is
     * serializable and implements @link RandomAccess.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) 
        return new ArrayList<>(a); //Here
    

这篇文章很有趣,因为如果你:

Integer[] localInts = new Integer[]1, 2, 8;
Consumer<List<Integer>> intObserver;

public void getInts(Consumer<List<Integer>> intObserver) 
    this.intObserver = intObserver;
    dispatch();


private void dispatch() 
    List<Integer> myIntegers = Arrays.asList(localInts);
    intObserver.accept(myIntegers);

    

...稍后:

getInts(
    myInts -> 
    adapter.submitList(myInts); //myInts = [1, 2, 8]
    
);
    

分发的 List 不仅遵守每次提交时的取消引用,而且当 localInts 变量被更改时,

public void set(int index, Integer value) 
    localInts[index] = value;
    dispatch(); // dispatch again

...

myModel.set(1, 4) // localInts = [1, 4, 8]

此更改也会传递到 RecyclerView 内的列表,这意味着在下一次提交时,(newList == mList) 将返回“false”,从而允许 DiffUtils 触发 Mayers 算法,但是来自ItemCallback&lt;T&gt; 接口的areContentsTheSame(@NonNull T oldItem, @NonNull T newItem) 回调将在到达索引1 时抛出“true”。基本上,说“RecyclerView 中的索引1(在以前的版本中应该是2)总是4”,并且布局pass 仍然不会执行。

所以,在这种情况下要走的路是:

List<Integer> trulyDereferenced = new ArrayList<>(Arrays.asList(localInts));
adapter.submitList(trulyDereferenced);

【讨论】:

以上是关于notifyDataSetChanged 不适用于 RecyclerView的主要内容,如果未能解决你的问题,请参考以下文章

ArrayAdapter.NotifyDataSetChanged() 不工作?

notifyDataSetChanged 示例

Android RecyclerView:notifyDataSetChanged() IllegalStateException

[完成]notifyDataSetChanged() 不会自动更新 ListActivity

notifyDataSetChanged() 上的 WinDeath

安卓:notifyDataSetChanged();不工作