Android无限滚动视图有时在分页期间从第二页开始(有时跳过第一页)

Android无限滚动视图有时在分页期间从第二页开始(有时跳过第一页),android,pagination,infinite-scroll,android-recyclerview,Android,Pagination,Infinite Scroll,Android Recyclerview,我正在开发一款Android应用程序,它在回收器视图中使用无限滚动条。该应用程序是我所在学院的新闻阅读器应用程序,允许学生阅读学院新闻报纸上的文章。我通过访问我们网站的RESTAPI来收集这些文章 我一直在使用从这里得到的无限滚动视图:,但继续遇到同样的问题。大约1/5次,当我刷新滚动视图时,它将从新闻结果的第二页开始,而不是第一页。我对此做了大量的研究,但没有找到任何东西,我一直在使用无止境卷轴监听器的源代码,但没有任何效果 下面我附上了我的项目的相关源代码和一系列显示该问题的图片 代码: 无

我正在开发一款Android应用程序,它在回收器视图中使用无限滚动条。该应用程序是我所在学院的新闻阅读器应用程序,允许学生阅读学院新闻报纸上的文章。我通过访问我们网站的RESTAPI来收集这些文章

我一直在使用从这里得到的无限滚动视图:,但继续遇到同样的问题。大约1/5次,当我刷新滚动视图时,它将从新闻结果的第二页开始,而不是第一页。我对此做了大量的研究,但没有找到任何东西,我一直在使用无止境卷轴监听器的源代码,但没有任何效果

下面我附上了我的项目的相关源代码和一系列显示该问题的图片

代码:

无限滚动侦听器。这是从上面的github链接获取的

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);
    }
}