我尝试使用ListView以及LoaderManager.LoaderCallbacks和自定义CursorAdapter加载列表,它工作正常。但是我正在尝试使用RecyclerView和自定义RecyclerView.Adapter来实现相同目的,但是我遇到了这个问题:
我是第一次显示该列表,但是当我旋转设备时,该列表消失了。
这是代码,请看一看。
CatalogActivity
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();
}
}
}
我无法弄清楚确切的问题。请帮忙。
答案 0 :(得分:2)
简而言之,这里的问题是,轮换后,您将获得以前在轮换之前循环过的Cursor
,但您没有考虑其当前位置。
Cursor
会跟踪并保持其在记录集中的位置,因为我敢肯定您是从其中包含的各种move*()
方法中收集到的。首次创建时,Cursor
的位置将设置为第一条记录之前的位置;即,其位置将设置为-1
。
首次启动应用程序时,LoaderManager
调用onCreateLoader()
,在其中实例化CursorLoader
,然后使之加载并交付其Cursor
, Cursor
在-1
的位置。此时,while (cursor.moveToNext())
循环将按预期工作,因为第一个moveToNext()
调用会将其移动到第一个位置(索引0
),然后再移动到每个可用位置,直到最后。
但是,在旋转时,LoaderManager
确定它已经具有请求的Loader
(由ID确定),它本身认为它已经加载了适当的Cursor
,因此它只需立即再次提供相同的Cursor
对象。 (这是Loader
框架的主要功能-不管配置如何更改,它都不会重新加载已经拥有的资源。)这是问题的症结所在。 Cursor
已留在旋转之前最后移动到的位置;即在结尾处。因此,Cursor
无法moveToNext()
,因此while
循环在初始
onLoadFinished()
,然后再旋转。
使用给定的设置,最简单的解决方法是自己手动重新放置Cursor
。例如,在getItems()
中,如果if
不为null,则将moveToFirst()
更改为Cursor
,然后将while
更改为do-while
,因此我们不会无意间跳过第一条记录。那就是:
if (cursor != null && cursor.moveToFirst()) {
do {
int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
...
} while (cursor.moveToNext());
}
这样,当重新传递相同的Cursor
对象时,其位置有点“重置”为位置0
。由于该位置直接在第一个记录上,而不是在第一个记录之前(请记住,最初是-1
),因此我们更改为do-while
,这样第一个moveToNext()
调用不会跳过Cursor
中的第一条记录。
注意:
我要提到的是,可以实现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
构造的好方法,同时仍然受益于最近的架构改进。
答案 1 :(得分:0)
代替LoaderManager.initLoader()
调用LoaderManager.restartLoader()