59

我从服务器获取数据,然后对其进行解析并将其存储在列表中。我将此列表用于 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()应该再次调用适配器吗?

4

8 回答 8

78

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

您要么需要:

  • 再次调用recyclerView.setAdapter(mBusinessAdapter)以更新 RecyclerView 的适配器引用以指向您的新适配器
  • 或者只是删除mBusinessAdapter = new BusinessAdapter(mBusinesses);以继续使用现有的适配器。由于您没有更改mBusinesses引用,因此适配器仍将使用该数组列表,并且在您调用notifyDataSetChanged().
于 2014-07-14T16:00:17.567 回答
31

试试这个方法:

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

有点费时,但它应该工作。

于 2014-07-14T16:03:51.540 回答
14

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

据我所知,由于您 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 参考和相关官方开发指南都没有明确提到目前的主线程要求(现在是 2017 年),而且 Android Studio lint 检查规则似乎也没有涉及这个问题。

但是,这是作者本人对此的解释

于 2017-10-30T11:12:42.970 回答
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();
}

愿这对你有所帮助。

于 2015-11-07T16:12:20.627 回答
2

虽然有点奇怪,但notifyDataSetChanged如果不为适配器设置新值,它并不能真正起作用。所以,你应该这样做:

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

这对我有用。

于 2019-10-15T13:43:45.337 回答
1

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

于 2018-08-29T09:48:14.710 回答
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;}

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

但是等等,还有更多……

执行 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 算法,来自接口的areContentsTheSame(@NonNull T oldItem, @NonNull T newItem)回调ItemCallback<T>将在到达索引时抛出“true” 1. 基本上,说“RecyclerView 中的索引 1(在以前的版本中应该是 2)总是 4”,并且布局传递仍然不会执行。

因此,在这种情况下要走的路是:

List<Integer> trulyDereferenced = new ArrayList<>(Arrays.asList(localInts));
adapter.submitList(trulyDereferenced);
于 2021-07-30T02:48:11.283 回答
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();
            }
        });
    }
于 2020-11-11T10:49:16.273 回答