notifyDataSetChanged无法在RecyclerView上运行

Vam*_*lla 51 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;
Run Code Online (Sandbox Code Playgroud)

我的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;
}
Run Code Online (Sandbox Code Playgroud)

从服务器获取数据后,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();
}
Run Code Online (Sandbox Code Playgroud)

我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是我没有在视图中显示数据.我究竟做错了什么?

这是我的日志,

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
Run Code Online (Sandbox Code Playgroud)

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

Tan*_*.7x 64

在你parseResponse()所创建的一个新的实例BusinessAdapter类,但你实际上并没有使用它的任何地方,所以你RecyclerView不知道新的实例存在.

你要么需要:

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

  • 这对我不起作用,我不得不重新调整适配器并将其重新设置为recylerview (3认同)
  • 只要您没有静态引用任何内容或以其他方式保留引用,您就可以放弃旧引用。 (2认同)

NiV*_*VeR 25

试试这个方法:

List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification
Run Code Online (Sandbox Code Playgroud)

有点耗时,但它应该工作.

  • 本声明:List <Business> mBusinesses2 = mBusinesses;**不要**制作清单的深层副本.所以你不是克隆列表 - 这是一回事,第二件事是我看不出这怎么能解决原来的问题.... (3认同)

Iva*_*sov 10

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

从我收集的信息来看,由于您notifyDataSetChanged()在同一个块中有解析过程和对 我相信你知道)。所以正确的方法是:

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();
        }
    });
Run Code Online (Sandbox Code Playgroud)

}

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

此外,RecyclerView.Adapter api 参考和相关官方开发指南目前都没有明确提到主线程要求(目前是 2017 年),而且 Android Studio lint 检查规则似乎也没有涉及这个问题。

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