Android无限滚动视图有时在分页期间从第二页开始(有时跳过第一页)
我正在开发一款Android应用程序,它在回收器视图中使用无限滚动条。该应用程序是我所在学院的新闻阅读器应用程序,允许学生阅读学院新闻报纸上的文章。我通过访问我们网站的RESTAPI来收集这些文章 我一直在使用从这里得到的无限滚动视图:,但继续遇到同样的问题。大约1/5次,当我刷新滚动视图时,它将从新闻结果的第二页开始,而不是第一页。我对此做了大量的研究,但没有找到任何东西,我一直在使用无止境卷轴监听器的源代码,但没有任何效果 下面我附上了我的项目的相关源代码和一系列显示该问题的图片 代码: 无限滚动侦听器。这是从上面的github链接获取的Android无限滚动视图有时在分页期间从第二页开始(有时跳过第一页),android,pagination,infinite-scroll,android-recyclerview,Android,Pagination,Infinite Scroll,Android Recyclerview,我正在开发一款Android应用程序,它在回收器视图中使用无限滚动条。该应用程序是我所在学院的新闻阅读器应用程序,允许学生阅读学院新闻报纸上的文章。我通过访问我们网站的RESTAPI来收集这些文章 我一直在使用从这里得到的无限滚动视图:,但继续遇到同样的问题。大约1/5次,当我刷新滚动视图时,它将从新闻结果的第二页开始,而不是第一页。我对此做了大量的研究,但没有找到任何东西,我一直在使用无止境卷轴监听器的源代码,但没有任何效果 下面我附上了我的项目的相关源代码和一系列显示该问题的图片 代码: 无
package hu.ait.macweekly.listeners;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
/**
* Code from https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
*/
public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
public static final String NO_SEARCH = "";
public static final int NO_CATEGORY = -1;
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 25;
// The current offset index of data you have loaded
private int currentPage = 0;
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// Sets the starting page index
private int startingPageIndex = 0;
// Sets category
private int categoryId = NO_CATEGORY;
// Sets search
private String searchString = NO_SEARCH;
RecyclerView.LayoutManager mLayoutManager;
public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
}
public int getLastVisibleItem(int[] lastVisibleItemPositions) {
int maxSize = 0;
for (int i = 0; i < lastVisibleItemPositions.length; i++) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i];
}
else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i];
}
}
return maxSize;
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition = 0;
int totalItemCount = mLayoutManager.getItemCount();
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
} else if (mLayoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore(currentPage, totalItemCount, view, categoryId, searchString);
loading = true;
}
}
// Call this method whenever performing new searches
public void resetState(RecyclerView view, int categoryId, String searchString) {
this.categoryId = categoryId;
this.searchString = searchString;
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = 0;
this.loading = true;
onScrolled(view, 0, 0);
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view, int categoryId, String searchString);
}
package hu.ait.macweekly.listeners;
导入android.support.v7.widget.GridLayoutManager;
导入android.support.v7.widget.LinearLayoutManager;
导入android.support.v7.widget.RecyclerView;
导入android.support.v7.widget.StaggedGridLayoutManager;
/**
*代码来自https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
*/
公共抽象类EndlessRecyclerViewScrollListener扩展了RecyclerView.OnScrollListener{
公共静态最终字符串NO_SEARCH=“”;
公共静态最终int NO_CATEGORY=-1;
//低于当前滚动位置的最小项目数
//在加载更多之前。
私有int visibleThreshold=25;
//已加载数据的当前偏移索引
private int currentPage=0;
//上次加载后数据集中的项目总数
private int previousTotalItemCount=0;
//如果仍在等待加载最后一组数据,则为True。
私有布尔加载=真;
//设置起始页索引
私有int startingPageIndex=0;
//集合类别
私有int categoryId=无类别;
//集合搜索
私有字符串searchString=无搜索;
RecyclerView.LayoutManager mllayoutmanager;
public endlessRecycleViewScrollListener(LinearLayoutManager布局管理器){
this.mLayoutManager=layoutManager;
}
public int getLastVisibleItem(int[]lastVisibleItemPositions){
int maxSize=0;
对于(int i=0;imaxSize){
maxSize=lastVisibleItemPositions[i];
}
}
返回最大大小;
}
//在滚动过程中,这种情况在一秒钟内会发生很多次,因此请小心放置在此处的代码。
//我们得到了一些有用的参数,以帮助我们确定是否需要加载更多数据,
//但首先我们检查是否在等待前一次加载完成。
@凌驾
已克隆的公共无效(RecyclerView视图、int dx、int dy){
int lastVisibleItemPosition=0;
int totalItemCount=mLayoutManager.getItemCount();
if(交错边缘布局管理器的mLayoutManager实例){
int[]lastVisibleItemPositions=((StaggedGridLayoutManager)mLayoutManager)。findLastVisibleItemPositions(null);
//获取列表中的最大元素
lastVisibleItemPosition=getLastVisibleItem(lastVisibleItemPositions);
}else if(网格布局管理器的mLayoutManager实例){
lastVisibleItemPosition=((GridLayoutManager)mLayoutManager).findLastVisibleItemPosition();
}else if(线性布局管理器的mLayoutManager实例){
lastVisibleItemPosition=((LinearLayoutManager)mLayoutManager)。findLastVisibleItemPosition();
}
//如果项目总数为零,而前一个不是,则假定
//列表无效,应重置回初始状态
if(totalItemCountpreviousTotalItemCount)){
加载=假;
previousTotalItemCount=totalItemCount;
}
//如果当前未加载,我们将检查是否已违反
//VisibleSThreshold已关闭,需要重新加载更多数据。
//如果确实需要重新加载一些数据,则执行onLoadMore来获取数据。
//阈值应反映出有多少个总列
如果(!loading&&(lastVisibleItemPosition+visibleThreshold)>totalItemCount){
currentPage++;
onLoadMore(currentPage、totalItemCount、view、categoryId、searchString);
加载=真;
}
}
//执行新搜索时调用此方法
公共void重置状态(RecyclerView视图、int categoryId、String searchString){
this.categoryId=categoryId;
this.searchString=searchString;
this.currentPage=this.startingPageIndex;
this.previousTotalItemCount=0;
这是。加载=真;
onScrolled(视图,0,0);
}
//定义根据页面实际加载更多数据的过程
公共抽象void onLoadMore(int page、int totalItemsCount、RecyclerView视图、int categoryId、String searchString);
}
主要内容:
(需要注意的重要事项是onLoadMore()的声明和mEn的初始化
package hu.ait.macweekly;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import hu.ait.macweekly.adapter.ArticleRecyclerAdapter;
import hu.ait.macweekly.data.Article;
import hu.ait.macweekly.data.GuestAuthor;
import hu.ait.macweekly.listeners.ArticleViewClickListener;
import hu.ait.macweekly.listeners.EndlessRecyclerViewScrollListener;
import hu.ait.macweekly.network.NewsAPI;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener,
ArticleViewClickListener {
boolean showingNewsFeed = false;
// Constants
private final String LOG_TAG = "MainActivity - ";
private final int ARTICLES_PER_CALL = 25;
// Members
private NewsAPI newsAPI;
private ArticleRecyclerAdapter mArticleAdapter;
private EndlessRecyclerViewScrollListener mEndlessScrollListener;
// Views
@BindView(R.id.main_content) RecyclerView mMainContent;
@BindView(R.id.refresh_view) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.newsFeedErrorView) LinearLayout mErrorView;
@BindView(R.id.errorButton) Button mButtonView;
// Code
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initContentViews();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
prepareDrawer(toolbar);
prepareNavView();
prepareNewsAPI();
prepareContentViews();
}
private void prepareContentViews() {
mArticleAdapter = new ArticleRecyclerAdapter(getApplicationContext(), this);
mArticleAdapter.setDataSet(new ArrayList<Article>());
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mMainContent.setLayoutManager(linearLayoutManager);
mMainContent.setAdapter(mArticleAdapter);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// callNewsAPI();
resetArticlesClear();
}
});
mSwipeRefreshLayout.post(new Runnable() { // TODO: 10/29/17 Need this?
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(true);
}
});
mEndlessScrollListener = new EndlessRecyclerViewScrollListener(linearLayoutManager) {
@Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view, int categoryId, String searchString) {
addArticles(page, categoryId, searchString);
}
};
mMainContent.addOnScrollListener(mEndlessScrollListener);
mButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resetArticlesClear();
}
});
}
private void initContentViews() {
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
private void prepareNavView() {
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
}
private void prepareDrawer(Toolbar toolbar) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
// drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); // TODO: 10/30/17 Turn this back on when feature finished
}
public void prepareNewsAPI() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://themacweekly.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
newsAPI = retrofit.create(NewsAPI.class);
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_refresh) {
resetArticlesWithSearch("Aarohi");
return true;
}else if (id == R.id.about_page) {
goToAboutPage();
return true;
}
return super.onOptionsItemSelected(item);
}
private void goToAboutPage() {
Intent aboutPageIntent = new Intent(this, AboutPage.class);
startActivity(aboutPageIntent);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_camera) {
// Handle the camera action
resetArticlesWithCategory(4);
} else if (id == R.id.nav_gallery) {
} else if (id == R.id.nav_slideshow) {
} else if (id == R.id.nav_manage) {
} else if (id == R.id.nav_send) {
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
public void showNewsFeed() {
mErrorView.setVisibility(View.GONE);
mMainContent.setVisibility(View.VISIBLE);
showingNewsFeed = true;
}
public void showErrorScreen() {
mMainContent.setVisibility(View.GONE);
mErrorView.setVisibility(View.VISIBLE);
showingNewsFeed = false;
}
public interface ArticleCallback {
void onSuccess(List<Article> articles);
void onFailure();
}
private void callNewsAPI(final int pageNum, int categoryId, String searchStr, final ArticleCallback articleCallback) {
final Call<List<Article>> articleCall;
if(categoryId != EndlessRecyclerViewScrollListener.NO_CATEGORY // Here we build our articleCall based on what information is passed to us
&& !searchStr.equals(EndlessRecyclerViewScrollListener.NO_SEARCH)) { // If we have category or search string, use those...
articleCall = newsAPI.getArticles(pageNum, ARTICLES_PER_CALL, categoryId, searchStr);
} else if(categoryId != EndlessRecyclerViewScrollListener.NO_CATEGORY) {
articleCall = newsAPI.getArticles(pageNum, ARTICLES_PER_CALL, categoryId);
} else if(!searchStr.equals(EndlessRecyclerViewScrollListener.NO_SEARCH)) {
articleCall = newsAPI.getArticles(pageNum, ARTICLES_PER_CALL, searchStr);
} else {
articleCall = newsAPI.getArticles(pageNum, ARTICLES_PER_CALL);
}
Log.d(LOG_TAG, "Sent article api call ----------------");
articleCall.enqueue(new Callback<List<Article>>() {
@Override
public void onResponse(Call<List<Article>> call, Response<List<Article>> response) {
mSwipeRefreshLayout.setRefreshing(false);
if (response.body() != null) {
Log.d(LOG_TAG, "Got response back. Page: "+pageNum+" -----------------");
List<Article> uncleanedResponse = response.body();
List<Article> cleanedResponse = cleanResponse(uncleanedResponse);
if(!showingNewsFeed) showNewsFeed();
articleCallback.onSuccess(cleanedResponse);
} else {
Log.e(LOG_TAG, "api response body is null. Page: "+pageNum);
articleCallback.onFailure();
}
}
@Override
public void onFailure(Call<List<Article>> call, Throwable t) {
Log.e(LOG_TAG, "call failed. Could not retrieve page. Page: "+pageNum);
mSwipeRefreshLayout.setRefreshing(false);
articleCallback.onFailure();
}
});
}
private void resetArticlesClear() {
resetArticles(EndlessRecyclerViewScrollListener.NO_CATEGORY, EndlessRecyclerViewScrollListener.NO_SEARCH);
}
private void resetArticlesWithCategory(int categoryId) {
resetArticles(categoryId, EndlessRecyclerViewScrollListener.NO_SEARCH);
}
private void resetArticlesWithSearch(String searchString) {
resetArticles(EndlessRecyclerViewScrollListener.NO_CATEGORY, searchString);
}
private void resetArticlesWithCatAndSearch(int categoryId, String searchString) {
resetArticles(categoryId, searchString);
}
private void resetArticles(int categoryId, String searchString) {
mArticleAdapter.clearDataSet();
mArticleAdapter.notifyDataSetChanged();
showNewsFeed();
mEndlessScrollListener.resetState(mMainContent, categoryId, searchString);
}
private void addArticles(int pageNum, int categoryId, String searchString) {
final int startSize = mArticleAdapter.getItemCount();
ArticleCallback articleCallback = new ArticleCallback() {
@Override
public void onSuccess(List<Article> articles) {
if (!showingNewsFeed) showNewsFeed();
mArticleAdapter.addToDataSet(articles);
mArticleAdapter.notifyItemRangeChanged(startSize, ARTICLES_PER_CALL);
}
@Override
public void onFailure() {
if (mArticleAdapter.getDataSet().size() == 0) showErrorScreen();
}
};
callNewsAPI(pageNum, categoryId, searchString, articleCallback);
}
private List<Article> cleanResponse(List<Article> uncleanedResponse) {
int MIN_CHAR_COUNT_FOR_ARTICLE = 1200; // Articles with char count < this val likely only have a video or audio link which our app doesn't handle.
//TODO: This also means however that we aren't loading things like comics or single images.
//Ultimately we want to be able to load videos or audio.
for (int i = uncleanedResponse.size() - 1; i >= 0; i--) {
Article article = uncleanedResponse.get(i);
if (MacWeeklyUtils.isTextEmpty(article.excerpt.rendered) || article.content.rendered.length() < MIN_CHAR_COUNT_FOR_ARTICLE) {
uncleanedResponse.remove(i);
}
}
return uncleanedResponse;
}
@Override
public void articleViewClicked(View view, int position) {
showFullArticle(mArticleAdapter.getDataSet().get(position));
}
private void showFullArticle(Article targetArticle) {
// These attributes might be null or missing
String authorBio = "";
String authorName = "";
String authorImgUrl = "";
if(targetArticle.guestAuthor != null) {
GuestAuthor gAuthor = targetArticle.guestAuthor;
if(gAuthor.name != null) {
authorName = targetArticle.guestAuthor.name;
}
if(!MacWeeklyUtils.isTextEmpty(gAuthor.imgUrl)) {
authorImgUrl = gAuthor.imgUrl;
}
if(!MacWeeklyUtils.isTextEmpty(gAuthor.bio)){
authorBio = gAuthor.bio;
}
}
Intent articleIntent = new Intent(this, ArticleActivity.class);
articleIntent.putExtra(ArticleActivity.ARTICLE_AUTHOR_KEY, "Author name here");
articleIntent.putExtra(ArticleActivity.ARTICLE_CONTENT_KEY, targetArticle.content
.rendered);
articleIntent.putExtra(ArticleActivity.ARTICLE_DATE_KEY, targetArticle.date);
articleIntent.putExtra(ArticleActivity.ARTICLE_TITLE_KEY, targetArticle.title.rendered);
articleIntent.putExtra(ArticleActivity.ARTICLE_AUTHOR_KEY, authorName);
articleIntent.putExtra(ArticleActivity.ARTICLE_LINK_KEY, targetArticle.link);
articleIntent.putExtra(ArticleActivity.AUTHOR_IMG_URL_KEY, authorImgUrl);
articleIntent.putExtra(ArticleActivity.AUTHOR_BIO_KEY, authorBio);
startActivity(articleIntent);
}
}