【问题标题】:Problem using LoaderManager.LoaderCallbacks<Cursor> with custom RecyclerView.Adapter将 LoaderManager.LoaderCallbacks<Cursor> 与自定义 RecyclerView.Adapter 一起使用时出现问题
【发布时间】:2018-11-10 11:58:44
【问题描述】:

我尝试使用 ListView 以及 LoaderManager.LoaderCallbacks 和自定义 CursorAdapter 加载列表,它工作正常。但我正在尝试使用 RecyclerView 和自定义 RecyclerView.Adapter 来完成同样的任务,但我遇到了这个问题:

我第一次显示列表,但是当我旋转设备时,列表消失了。

这是代码,请看一下。

目录活动

public class CatalogActivity extends AppCompatActivity implements ItemAdapter.OnItemClickListener,
        LoaderManager.LoaderCallbacks<Cursor> {
    private static final int ITEMS_LOADER_ID = 1;
    public static final String EXTRA_ITEM_NAME = "extra_item_name";
    public static final String EXTRA_ITEM_STOCK = "extra_item_stock";

    @BindView(R.id.list_items)
    RecyclerView mListItems;

    private ItemAdapter mItemAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_catalog);
        ButterKnife.bind(this);

        setupListItems();

        getLoaderManager().initLoader(ITEMS_LOADER_ID, null, this);
    }

    private void setupListItems() {
        mListItems.setHasFixedSize(true);
        LayoutManager layoutManager = new LinearLayoutManager(this);
        mListItems.setLayoutManager(layoutManager);
        mListItems.setItemAnimator(new DefaultItemAnimator());
        mListItems.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
        mItemAdapter = new ItemAdapter(getApplicationContext(), this);
        mListItems.setAdapter(mItemAdapter);
    }

    @Override
    public void OnClickItem(int position) {
        Intent intent = new Intent(this, EditorActivity.class);

        Item item = mItemAdapter.getItems().get(position);
        intent.putExtra(EXTRA_ITEM_NAME, item.getName());
        intent.putExtra(EXTRA_ITEM_STOCK, item.getStock());

        startActivity(intent);
    }

    private ArrayList<Item> getItems(Cursor cursor) {
        ArrayList<Item> items = new ArrayList<>();

        if (cursor != null) {
            while (cursor.moveToNext()) {
                int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
                int columnIndexName = cursor.getColumnIndex(ItemEntry.COLUMN_NAME);
                int columnIndexStock = cursor.getColumnIndex(ItemEntry.COLUMN_STOCK);

                int id = cursor.getInt(columnIndexId);
                String name = cursor.getString(columnIndexName);
                int stock = Integer.parseInt(cursor.getString(columnIndexStock));

                items.add(new Item(id, name, stock));
            }
        }

        return items;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
        switch (loaderId) {
            case ITEMS_LOADER_ID: {
                String[] projection = {
                        ItemEntry._ID,
                        ItemEntry.COLUMN_NAME,
                        ItemEntry.COLUMN_STOCK
                };

                return new CursorLoader(
                        this,
                        ItemEntry.CONTENT_URI,
                        projection,
                        null,
                        null,
                        null
                );
            }
            default:
                return null;
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        mItemAdapter.setItems(getItems(cursor));
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    }
}

ItemAdapter

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
    private ArrayList<Item> mItems;
    private OnItemClickListener mOnItemClickListener;
    private Context mContext;

    public ItemAdapter(Context context, OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
        mContext = context;
    }

    public void setItems(ArrayList<Item> items) {
        if (items != null) {
            mItems = items;
            notifyDataSetChanged();
        }
    }

    public ArrayList<Item> getItems() {
        return mItems;
    }

    public interface OnItemClickListener {
        void OnClickItem(int position);
    }

    public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        @BindView(R.id.tv_item)
        TextView tv_item;

        @BindView(R.id.tv_stock)
        TextView tv_stock;

        public ItemViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            int position = getAdapterPosition();
            mOnItemClickListener.OnClickItem(position);
        }
    }

    @NonNull
    @Override
    public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_inventory, parent, false);
        return new ItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ItemViewHolder itemViewHolder, int position) {
        final Item item = mItems.get(position);

        itemViewHolder.tv_item.setText(item.getName());
        itemViewHolder.tv_stock.setText(mContext.getString(R.string.display_stock, item.getStock()));
    }

    @Override
    public int getItemCount() {
        if (mItems == null) {
            return 0;
        } else {
            return mItems.size();
        }
    }
}

我无法弄清楚确切的问题。请帮忙。

【问题讨论】:

  • 你做过调试吗?究竟是什么消失了?整个RecyclerView,还是只是项目?您是否检查过 LoaderCallbacks 方法在轮换时正确运行?你检查过交付的Cursor 的大小和内容吗? ArrayList 正在构建中吗?
  • @MikeM。整个 RecyclerView 明显消失了。你没看到上面的截图和解释吗?我调试发现在getItems()方法while(cursor.moveToNext)在旋转后不起作用。加载器方法工作正常。
  • 请准确解释整个RecyclerView 消失的原因。你完全没有提到这一点,特别是在你的描述中,截图无法传达这一点,因为没有项目的透明RecyclerView 看起来与没有RecyclerView 完全一样——一个空白的白框。无论如何,我不认为这就是你的意思。
  • 那是模糊和模棱两可的,这就是我首先问这个的原因。什么是“清单”?是整个RecyclerView 本身在旋转后不显示,还是只是它没有任何项目?
  • @MikeM。是的,这行得通! :) 它也保留了正确的滚动位置。请发布您的答案,以便我可以将其标记为正确,包括我在内的其他人可以投票。你能详细解释一下这个while和do-while问题吗?为什么会发生这种情况?是否还有其他需要开发人员处理这种情况的场景(示例)?谢谢! :)

标签: android android-recyclerview android-cursorloader android-loadermanager


【解决方案1】:

简而言之,这里的问题是,在轮换之后,您收到的 Cursor 与您之前在轮换之前循环的相同,但您没有考虑到它的当前位置。

Cursor 在其记录集中跟踪并保持自己的位置,我相信您已经从它包含的各种 move*() 方法中收集到。首次创建时,Cursor 的位置将设置在第一条记录之前;即,其位置将设置为-1。

当您第一次启动您的应用程序时,LoaderManager 调用 onCreateLoader(),您的 CursorLoader 被实例化,然后使其加载并交付其 Cursor,Cursor 的位置在 @ 987654334@。此时,while (cursor.moveToNext()) 循环按预期工作,因为第一个moveToNext() 调用会将其移动到第一个位置(索引0),然后移动到之后的每个可用位置,直到结束。

然而,在旋转时,LoaderManager 确定它已经拥有请求的 Loader(由 ID 确定),它本身认为它已经加载了适当的 Cursor,因此它会立即提供相同的 @ 987654341@ 对象再次。 (这是Loader 框架的一个主要特性——它不会重新加载它已经拥有的资源,无论配置如何更改。)这是问题的症结所在。 Cursor 被留在了旋转前它移动到的最后一个位置;即,在其结束时。因此,Cursor 不能moveToNext(),因此while 循环在初始之后根本不会运行 onLoadFinished(),旋转前。

对于给定的设置,最简单的解决方法是自己手动重新定位Cursor。例如,在getItems() 中,如果Cursor 不为空,则将if 更改为moveToFirst(),并将while 更改为do-while,这样我们就不会无意中跳过第一条记录。那就是:

if (cursor != null && cursor.moveToFirst()) { 
    do {
        int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
        ...
    } while (cursor.moveToNext());
}

这样,当相同的Cursor 对象被重新传递时,它的位置有点“重置”到位置0。由于该位置直接在第一条记录上,而不是在它之前(记住,最初是-1),我们更改为do-while,这样第一个moveToNext() 调用不会跳过@ 中的第一条记录987654361@.


注意事项:

  • 我要提一下,可以实现RecyclerView.Adapter 直接获取Cursor,类似于旧的CursorAdapter。在这种情况下,Cursor 必须在onBindViewHolder() 方法中移动到每个项目的正确位置,而单独的ArrayList 将是不必要的。这需要一点努力,但将CursorAdapter 翻译成RecyclerView.Adapter 并不是很困难。或者,肯定有可用的解决方案。 (比如可能this one,虽然我不能保证,atm,我经常看到一个值得信赖的用户经常推荐它。)

  • 我还要提一下,原生的Loader 框架一直是deprecated, in favor of the newer ViewModel/LiveData architecture framework in support libraries。但是,最新的 androidx 库似乎有自己的内部改进的 Loader 框架,它是对上述 ViewModel/LiveData 设置的简单包装。这似乎是利用已知的Loader 构造的一种不错且简单的方法,同时仍然受益于最近的架构改进。

【讨论】:

  • 是的,在android中使用最新的东西是正确的,当我现在作为初学者学习android的基本构建块时,我会尽快来的:)谢谢!
【解决方案2】:

而不是LoaderManager.initLoader() 调用LoaderManager.restartLoader()

【讨论】:

  • 我试过用这个。我检查加载程序是否为空,然后我使用initLoader() 否则使用restartLoader()。但这会再次查询数据,最终不会保留列表的滚动位置。请注意!如果您正在刷新列表或尝试查询其他数据列表,则应使用restartLoader()。
猜你喜欢
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 2012-08-01
  • 1970-01-01
  • 2011-03-15
  • 2011-05-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多