我一直非常细致地遵循this指南在我的RecyclerView中实施分页。唯一的区别是我使用Retrofit从API中检索数据。
当我打开我的活动时,它会在一段时间后停止,当我检查Logcat时,它会说:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{31a22aa position=4 id=-1, oldPos=2,
...这就是我的RecyclerView适配器的样子:
public class CuratedSectionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static int VIEW_TYPE_HEADER = 0;
private static int VIEW_TYPE_ITEM = 1;
private static int VIEW_TYPE_LOADING = 2;
private RecyclerView mRecyclerView;
private OnLoadMoreListener mOnLoadMoreListener;
private boolean isLoading;
private int visibleThreshold = 2;
private int lastVisibleItem, totalItemCount;
private List<Object> itemList;
public CuratedSectionAdapter(List<Object> itemList, RecyclerView recyclerView, LinearLayoutManager layoutManager) {
this.itemList = itemList;
this.mRecyclerView = recyclerView;
final LinearLayoutManager linearLayoutManager = layoutManager;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener.onLoadMore();
}
isLoading = true;
}
}
});
}
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
this.mOnLoadMoreListener = mOnLoadMoreListener;
}
private class ItemViewHolder extends RecyclerView.ViewHolder {
RecyclerView itemRecyclerView;
CuratedSectionNestedAdapter nestedAdapter;
LinearLayoutManager layoutManager;
ItemViewHolder(View view) {
super(view);
itemRecyclerView = view.findViewById(R.id.recyclerView_nested);
layoutManager = new LinearLayoutManager(view.getContext(), LinearLayoutManager.HORIZONTAL, false);
}
}
private class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView textViewHeader;
Typeface montserratMedium = Typeface.createFromAsset(getApplicationContext().getAssets(), "fonts/Montserrat-Medium.ttf");
HeaderViewHolder(View view) {
super(view);
textViewHeader = view.findViewById(R.id.textView_header);
textViewHeader.setTypeface(montserratMedium);
}
}
private class LoadingViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
LoadingViewHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.progress_bar);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == VIEW_TYPE_HEADER) {
HeaderViewHolder viewHolder = (HeaderViewHolder) holder;
CuratedSectionHeader header = (CuratedSectionHeader) itemList.get(position);
viewHolder.textViewHeader.setText(header.getHeaderName());
} else if (holder.getItemViewType() == VIEW_TYPE_ITEM){
ItemViewHolder viewHolder = (ItemViewHolder) holder;
List<CuratedSectionItem> items = (List<CuratedSectionItem>) itemList.get(position);
if (viewHolder.nestedAdapter != null) {
viewHolder.nestedAdapter.setItems(items);
} else {
viewHolder.nestedAdapter = new CuratedSectionNestedAdapter(items);
viewHolder.itemRecyclerView.setLayoutManager(viewHolder.layoutManager);
viewHolder.itemRecyclerView.setAdapter(viewHolder.nestedAdapter);
}
} else if (holder.getItemViewType() == VIEW_TYPE_LOADING){
LoadingViewHolder viewHolder = (LoadingViewHolder) holder;
viewHolder.progressBar.setIndeterminate(true);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.main_explore_header_row, parent, false));
} else if (viewType == VIEW_TYPE_ITEM) {
return new ItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.main_explore_row, parent, false));
} else if (viewType == VIEW_TYPE_LOADING) {
return new LoadingViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_loading_item, parent, false));
}
throw new RuntimeException("Adapter " + viewType + "not found");
}
@Override
public int getItemCount() {
return itemList.size();
}
@Override
public int getItemViewType(int position) {
if(isHeader(position)) {
return VIEW_TYPE_HEADER;
} else if(isLoading(position)) {
return VIEW_TYPE_LOADING;
} else {
return VIEW_TYPE_ITEM;
}
}
public boolean isHeader(int position) {
return itemList.get(position) instanceof CuratedSectionHeader;
}
public boolean isLoading(int position) {
return position > itemList.size();
}
public void setLoaded() {
isLoading = false;
}
}
这可能与我构建LoadingViewHolder
的方式有关,但我认为我正确地做了那个。另一方面,这就是活动的样子:
public class ExploreFeedActivity extends Activity {
private RecyclerView recyclerView;
private CuratedSectionAdapter curatedSectionAdapter;
private LinearLayoutManager layoutManager;
private Sections curated;
int mStart = 0;
int mLimit = 2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_explore);
curated = new Sections();
curated.loadSections(mStart, mLimit);
recyclerView = findViewById(R.id.recycler_view_main);
layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
}
private class Sections {
ArrayList<CuratedSection> sections = new ArrayList<>();
public Thread[] thread;
private Sections() {}
public void setSections(ArrayList<CuratedSection> sections) {
this.sections = sections;
}
public void setSectionStories(String sectionId, List<CuratedSectionItem> stories) {
for(CuratedSection s : sections){
if(s.id != null && s.id.equals(sectionId)) {
s.stories = stories;
}
}
}
public void loadStories(String sessionKey) {
thread = new Thread[sections.size()];
for( int s = 0; s < sections.size(); s++) {
thread[s] = new Thread(new LoadStories(sessionKey, sections.get(s)));
thread[s].start();
}
for( int f = 0; f < sections.size(); f++) {
try {
thread[f].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
curatedSectionAdapter = new CuratedSectionAdapter(this.getAdapterInfo(), recyclerView, layoutManager);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(curatedSectionAdapter);
curatedSectionAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
Log.e("haint", "Load More");
curated.sections.add(null);
curatedSectionAdapter.notifyItemInserted(curated.sections.size() - 1);
// Load more data for recyclerview
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.e("haint", "Load Further");
// Remove loading item
curated.sections.remove(curated.sections.size() - 1);
curatedSectionAdapter.notifyItemRemoved(curated.sections.size());
// Load data
curated.loadSections(2, 2);
curatedSectionAdapter.notifyDataSetChanged();
curatedSectionAdapter.setLoaded();
}
}, 5000);
}
});
}
public void loadSections(int start, int limit) {
swipeRefreshLayout.setRefreshing(false);
LoadSections load = new LoadSections(start, limit);
load.run();
}
public List<Object> getAdapterInfo() {
List<Object> list = new ArrayList<>();
for (int i = 0; i < sections.size(); i++) {
CuratedSection section = sections.get(i);
CuratedSectionHeader header = new CuratedSectionHeader();
header.setHeaderName(section.header);
list.add(header);
list.add(section.stories);
}
return list;
}
}
private class LoadSections implements Runnable {
int start, limit;
LoadSections(int start, int limit) {
this.start = start;
this.limit = limit;
}
@Override
public void run() {
SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE);
final String sessionKey = prefs.getString("session_key", null);
Call<JsonArray> call;
call = TravelersApi.endpoint().getCuratedSections(sessionKey);
call.enqueue(new Callback<JsonArray>() {
@Override
public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
if(response.code() != 200) {
Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
return;
}
JsonArray rawSections = response.body();
if(rawSections.size() == 0) {
//TODO: show placeholder
return;
}
for(int i = start; i < start+limit; i++) {
JsonObject jSection = rawSections.get(i).getAsJsonObject();
final CuratedSection section = new CuratedSection();
section.id = jSection.get("id").getAsString();
section.header = jSection.get("section_header").getAsString();
section.isShown = jSection.get("is_shown").getAsBoolean();
section.stories = new ArrayList<>();
curated.sections.add(section);
}
curated.setSections(curated.sections);
curated.loadStories(sessionKey);
}
@Override
public void onFailure(Call<JsonArray> call, Throwable t) {
Log.d("ERROR!", t.toString());
t.printStackTrace();
}
});
}
}
private class LoadStories implements Runnable {
String sessionKey;
CuratedSection section;
LoadStories(String sessionKey, CuratedSection section) {
this.sessionKey = sessionKey;
this.section = section;
}
@Override
public void run() {
Call<JsonArray> subCall;
subCall = TravelersApi.endpoint().getCuratedSectionTopics(sessionKey, section.id);
try {
Response<JsonArray> response = subCall.execute();
if(response.code() != 200) {
Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
return;
}
JsonArray rawStories = response.body();
if(rawStories.size() == 0) {
//TODO: show placeholder
return;
}
ArrayList<CuratedSectionItem> stories = new ArrayList<>();
for(int i = 0; i < rawStories.size(); i++) {
JsonObject jStories = rawStories.get(i).getAsJsonObject();
JSONObject temp = new JSONObject(jStories.toString());
JsonObject author = jStories.get("author").getAsJsonObject();
CuratedSectionItem story = new CuratedSectionItem();
story.title = jStories.get("title").getAsString();
story.avatar = author.get("profile_photo").getAsString();
stories.add(story);
}
section.stories = stories;
} catch (IOException e) {
Log.d("ERROR!", e.toString());
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
值得注意的是,我的RecyclerView适配器curatedSectionAdapter
已在私有类loadStories
内的方法Sections
中实例化。我已经尝试将该部分移动到onCreate
,但随后活动不会显示任何内容。我还怀疑我在LoadSections
的回调中构造for循环的方式与崩溃有关。