notifyDataSetChanged无法在RecyclerView上运行

时间:2014-07-14 15:49:36

标签: android android-5.0-lollipop android-recyclerview

我从服务器获取数据,然后解析它并将其存储在List中。我将此列表用于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();
}

我的BusinessAdapter:

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()吗?

8 个答案:

答案 0 :(得分:59)

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

您需要:

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

答案 1 :(得分:21)

试试这个方法:

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

耗费一点时间,但它应该有效。

答案 2 :(得分:4)

我有同样的问题。我刚刚在课程adapter之前宣布onCreate公开来解决这个问题。

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

这可以帮助你。

答案 3 :(得分:3)

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

从我收集的内容中,既然您在同一个块中有解析过程和对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

答案 4 :(得分:1)

清除旧的视图模型并将新数据设置为适配器,然后调用notifyDataSetChanged()

答案 5 :(得分:1)

我只添加到@pradeep kumar的答案中。是的,array = getNewItems(); ((MyAdapter) mAdapter).setValues(array); // pass the new list to adapter !!! mAdapter.notifyDataSetChanged(); 在没有为适配器设置新值的情况下才真正起作用。因此,您应该:

git config core.sharedRepository 0660

这对我有用。

答案 6 :(得分:0)

就我而言,在主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();
            }
        });
    }

答案 7 :(得分:0)

我总是遇到这个问题,我忘记了每次为适配器提供数据时 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;}

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

等等,还有更多……

执行 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 中的 List,这意味着在下一次提交时,(newList == mList) 将返回“false”,允许 DiffUtils 触发 Mayers 算法,但是 areContentsTheSame(@NonNull T oldItem, @NonNull T newItem) 接口的 ItemCallback<T> 回调在到达索引 1 时将抛出“true”。基本上,说“RecyclerView 中的索引 1(在之前的版本中应该是 2)总是4",布局传递仍然无法执行。

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

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