RecyclerView 刷新时重新加载相同的数据

RecyclerView reload same data when refresh

我有一个问题,当我滑动刷新数据时,第一次滑动没问题,但之后每次滑动都重新加载并一遍又一遍地添加相同的数据,到最后我有一个包含相同项目的列表以及...我正在使用装载机。 我之前尝试清除,但我不明白我的代码有什么问题,如果有人可以向我解释的话。谢谢你。

这是我的代码:

public abstract class NewsFragment extends Fragment implements LoaderManager.LoaderCallbacks<ArrayList<Articles>> {

    protected ItemAdapter mArticleAdapter;
    protected RecyclerView mRecyclerView;
    protected NewsFragment.OnNewSelectedInterface mListener;
    protected RecyclerView.LayoutManager mManager;
    protected SwipeRefreshLayout mSwipeRefreshLayout;
    protected LoaderManager mLoaderManager;
    private boolean mStateSaved;

    private static final int NEWS_LOAD_ID = 1;
    public static final String KEY_LIST = "key_list";

    public interface OnNewSelectedInterface {
        void onListNewSelected(int index, ArrayList<Articles> articles);
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.list_present_news, container, false);

        mListener = (NewsFragment.OnNewSelectedInterface) getActivity();
        mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeContainer);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
        mManager = new LinearLayoutManager(getActivity());
        mArticleAdapter = new ItemAdapter(getActivity(), new ArrayList<Articles>(), mListener);
        mLoaderManager = getLoaderManager();
        mStateSaved = mArticleAdapter.isStateSaved();

        mRecyclerView.setAdapter(mArticleAdapter);
        mRecyclerView.setLayoutManager(mManager);

        getData();
        refreshData();

        if(!isNetworkAvailable())alertUserAboutError();

        setDivider();

        return view;
    }

    private void setDivider() {
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView
                .getContext(), DividerItemDecoration.VERTICAL);
        mRecyclerView.addItemDecoration(dividerItemDecoration);
    }

    private void getData() {
        getLoaderManager().initLoader(NEWS_LOAD_ID, null, this).forceLoad();
    }

    private void alertUserAboutError() {
        AlertDialogFragment alertDialogFragment = new AlertDialogFragment();
        alertDialogFragment.show(getActivity().getFragmentManager(), "error_dialog");
    }

    protected abstract String[] getUrl();

    private boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager)
                getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
        boolean isAvailable = false;
        if (networkInfo != null && networkInfo.isConnected()) {
            isAvailable = true;
        }
        return isAvailable;
    }

    private void refreshData() {
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mArticleAdapter.clear();
                mSwipeRefreshLayout.setRefreshing(false);

            }
        });

        mSwipeRefreshLayout.setColorSchemeResources(
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
    }

    @Override
    public Loader<ArrayList<Articles>> onCreateLoader(int id, Bundle args) {
        return new NewsLoader(getActivity(), getUrl());
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<Articles>> loader, ArrayList<Articles> data) {
        if (data != null && !data.isEmpty()) {
            mArticleAdapter.addAll(data);
        }
    }

    @Override
    public void onLoaderReset(Loader<ArrayList<Articles>> loader) {
        mArticleAdapter.clear();
    }
}

我的装载机 class:

public class NewsLoader extends AsyncTaskLoader<ArrayList<Articles>>{

    private ArrayList<Articles> mArticlesArrayList;
    private String[] mUrl;

    public NewsLoader(Context context, String[] url) {
        super(context);
        mUrl = url;
    }

    @Override
    public ArrayList<Articles> loadInBackground() {

        OkHttpClient mClient = new OkHttpClient();
        for (String aMUrl : mUrl) {
            Request mRequest = new Request.Builder().url(aMUrl).build();
            try {
                Response response = mClient.newCall(mRequest).execute();
                try {
                    if (response.isSuccessful()) {
                        String json = response.body().string();
                        getMultipleUrls(json);
                    }
                } catch (IOException | JSONException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return mArticlesArrayList;
    }

    private void getMultipleUrls(String jsonData) throws JSONException {

        if (mArticlesArrayList == null) {
            mArticlesArrayList = getArticleForecast(jsonData);
        } else {
            mArticlesArrayList.addAll(getArticleForecast(jsonData));
        }
    }

    private ArrayList<Articles> getArticleForecast(String jsonData) throws JSONException {
        JSONObject forecast = new JSONObject(jsonData);
        JSONArray articles = forecast.getJSONArray("articles");

        ArrayList<Articles> listArticles = new ArrayList<>(articles.length());

        for (int i = 0; i < articles.length(); i++) {
            JSONObject jsonArticle = articles.getJSONObject(i);
            Articles article = new Articles();

            String urlImage = jsonArticle.getString("urlToImage");

            article.setTitle(jsonArticle.getString("title"));
            article.setDescription(jsonArticle.getString("description"));
            article.setImageView(urlImage);
            article.setArticleUrl(jsonArticle.getString("url"));

            listArticles.add(i, article);
        }

        return listArticles;
    }
}

我的适配器class:

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ArticleViewHolder> {

    private static final String TAGO = ItemAdapter.class.getSimpleName();
    private final NewsFragment.OnNewSelectedInterface mListener;
    private ArrayList<Articles> mArticlesList;
    private Context mContext;
    private int lastPosition = -1;
    private boolean mStateSaved = false;


    public boolean isStateSaved() {
        return mStateSaved;
    }

    public void setStateSaved(boolean stateSaved) {
        mStateSaved = stateSaved;
    }

    public ItemAdapter(Context context, ArrayList<Articles> articles, NewsFragment.OnNewSelectedInterface listener){
        mContext = context;
        mArticlesList = articles;
        mListener = listener;
    }

    @Override
    public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);

        ArticleViewHolder articleViewHolder = new ArticleViewHolder(view);
        articleViewHolder.setIsRecyclable(false);
        return articleViewHolder;
    }

    @Override
    public void onBindViewHolder(ArticleViewHolder holder, int position) {

        holder.bindArticle(mArticlesList.get(holder.getAdapterPosition()));
        setAnimation(holder.itemView, holder.getAdapterPosition());
    }

    private void setAnimation(View viewToAnimate, int position) {
        if (position > lastPosition) {
            Animation animation = AnimationUtils.loadAnimation(viewToAnimate.getContext(), android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            lastPosition = position;
        }

    }

    @Override
    public int getItemCount() {
        return mArticlesList.size();
    }

    public void clear() {
        mArticlesList.clear();
        notifyDataSetChanged();
    }

    public void addAll(ArrayList<Articles> articles) {
        mArticlesList.addAll(articles);
        notifyDataSetChanged();
    }


    protected class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        private ImageView mImageView;
        private TextView mTitleTextView, mDescriptionTextView;
        private FloatingActionButton mSaveButton;

        private ArticleViewHolder(View itemView) {
            super(itemView);

            mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
            mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
            mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
            mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);

            itemView.setOnClickListener(this);
        }

        private void bindArticle(final Articles article) {

            Glide.with(mContext).load(article.getImageView()).into(mImageView);
            mTitleTextView.setText(article.getTitle());
            mDescriptionTextView.setText(article.getDescription());
            if(mDescriptionTextView.getText().equals("")){
                mDescriptionTextView.setVisibility(View.GONE);
            }

            mSaveButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    insertArticle(article);
                    article.setStateSaved(true);
                }
            });

            Log.v(TAGO, "Item id : " + getItemId()
                    + "Item count : " + getItemCount()
                    + "Item position : " + getAdapterPosition()
                    + String.valueOf(article.isStateSaved()));
        }

        private void insertArticle(Articles articles) {

            String title = articles.getTitle();
            String description = articles.getDescription();
            String url = articles.getArticleUrl();

            ContentValues contentValues = new ContentValues();
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_TITLE_ARTICLE, title);
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_DESCRIPTION_ARTICLE, description);
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_URL_ARTICLE, url);

            Uri uri = mContext.getContentResolver().insert(ArticleContract.ArticleEntry.CONTENT_URI, contentValues);

            if(uri == null) {
                Log.v(TAGO, "Error");
            } else Toast.makeText(mContext, "Article Saved", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onClick(View view) {
            mListener.onListNewSelected(getLayoutPosition(), mArticlesList);
        }

    }
}
@Override
public void onBindViewHolder(ArticleViewHolder holder, int position) {
    holder.bindArticle(mArticlesList.get(position));
    setAnimation(holder.itemView, position);
}


public void addAll(ArrayList<Articles> articles) {
    mArticlesList.clear();
    mArticlesList.addAll(articles);
    notifyDataSetChanged();
}

如果这不起作用,那么我认为您的 api 给您提供了冗余数据。 为什么使用 articleViewHolder.setIsRecyclable(false);

另一个可能导致问题的地方是

private void getMultipleUrls(String jsonData) throws JSONException {

    if (mArticlesArrayList == null) {
         mArticlesArrayList = getArticleForecast(jsonData);
    } else {
        mArticlesArrayList.addAll(getArticleForecast(jsonData));
    }
}

您正在从一个循环中调用它,将数据添加到您的数组列表中。您的 ArrayList

中可以以某种方式插入多个数据

您使用的 ViewHolder#setIsRecyclable 不正确;此方法旨在用于防止 ViewHolder 在对其进行更改时 被回收。根据文档:

Calls to setIsRecyclable() should always be paired (one call to setIsRecyclabe(false) should always be matched with a later call to setIsRecyclable(true)).

这意味着 none 的 ViewHolder 对象将被回收,有效地使 RecyclerView 的使用变得毫无价值,并防止它在您尝试绑定新对象时重用视图反对你的 RecyclerView.

所以,简而言之,删除那行代码。


我还注意到您的适配器代码还有其他一些小问题,这些问题将来可能会引起很多麻烦;所以我冒昧地强调了一些我会做出的改变。

为了我自己的理智,我将您的 Articles class 称为 Article

把你的 Context 到处乱扔通常不是个好主意。传递给您的 ViewHolderView 已经引用了 Context,因此您可以改用它。

至于 insertArticle() 代码,Activity 无论如何都应该处理这个问题。因此,您可以通过将侦听器传递给您的 Adapter(以及随后的每个 ViewHolder)而不是 Context,将 Article 传递回 Activity。 =46=]

您还应该考虑使用 DiffUtil class 而不是仅仅调用 notifyDataSetChanged();它更有效率。只需确保您的 Article class 正在实施 equals()hashCode() 否则它将不起作用。

我没有包括动画代码(可以很容易地添加回去)或保存的状态代码(主要是因为我不知道你想做什么)。

public class ArticleAdapter extends RecyclerView.Adapter<Article> {

    private List<Article> mData;

    private ArticleViewHolder.OnSelectedListener mOnSelectedListener;
    private ArticleViewHolder.OnSaveListener mOnSaveListener;

    public ArticleAdapter(ArticleViewHolder.OnSelectedListener onSelectedListener, ArticleViewHolder.OnSaveListener onSaveListener) {
        mOnSelectedListener = onSelectedListener;
        mOnSaveListener = onSaveListener;
        mData = new ArrayList<>();
    }

    public void replaceData(final List<Article> data) {
        final List<Article> oldData = new ArrayList<>(mData);
        mData.clear();

        if (data != null) {
            mData.addAll(data);
        }

        DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldData.size();
            }

            @Override
            public int getNewListSize() {
                return mData.size();
            }

            @Override
            public int areItemsTheSame(int oldItemPosition, int newItemPosition) {
                return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
            }
        }).dispatchUpdatesTo(this);
    }

    @Override
    public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);
        return new SelectLocationViewHolder(view, mOnSelectedListener, mOnSaveListener);
    }

    @Override
    public void onBindViewHolder(ArticleViewHolder holder, int position) {
        holder.bind(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

}

public class ArticleViewHolder extends RecyclerView.ViewHolder {

    public interface OnSelectedListener {
        void onSelected(Article article);
    }

    public interface OnSaveListener {
        void onSave(Article article);
    }

    private View mView;
    private Article mArticle;

    private OnSelectedListener mOnSelectedListener;
    private OnSaveListener mOnSaveListener;

    private ImageView mImageView;
    private TextView mTitleTextView, mDescriptionTextView;
    private FloatingActionButton mSaveButton;

    public ArticleViewHolder(View itemView, final OnSelectedListener onSelectedListener, final OnSaveListener onSaveListener) {
        super(itemView);

        mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
        mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
        mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
        mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);

        mView = itemView;
        mView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onSelectedListener.onSelected(mArticle);
            }
        });

        mSaveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onSaveListener.onSave(mArticle);
            }
        });
    }

    public void bind(Article article) {
        mArticle = article;
        mTitleTextView.setText(article.getTitle());

        mDescriptionTextView.setText(article.getDescription());
        if(TextUtils.isEmpty(article.getDescription())) {
            mDescriptionTextView.setVisibility(View.GONE);
        }

        Glide.with(mView.getContext()).load(article.getImage()).into(mImageView);
    }

}

编辑

实际问题是您的加载程序每次都使用相同的 ArrayList,并不断向其中添加新结果。

public class NewsLoader extends AsyncTaskLoader<List<Article>> {

    private final String[] mUrls;
    private final OkHttpClient mClient;

    public NewsLoader(Context context, OkHttpClient client, String... urls) {
        super(context);
        mClient = client;
        mUrls = urls;
    }

    @Override
    public List<Article> loadInBackground() {
        List<Article> articles = new ArrayList<>();

        for (String url : mUrls) {
            Request request = new Request.Builder().url(url).build();
            try {
                Response response = mClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    parseData(response.body().string(), articles);
                }
            } catch (IOException | JSONException e) {
                e.printStackTrace();
            }
        }

         return articles;
    }

    private void parseData(List<Article> articles, String data) throws JSONException {
        JSONObject forecast = new JSONObject(data);
        JSONArray a = forecast.getJSONArray("articles");

        for (int i = 0; i < a.length(); i++) {
            JSONObject o = a.getJSONObject(i);
            Article article = new Article(
                    o.getString("title"),
                    o.getString("description"),
                    o.getString("url"),
                    o.getString("urlToImage"));
            articles.add(article);
        }
    }

}

此外,您可能已经注意到,我对您的 Article 构造函数做了一个小改动。你应该考虑制作 Article class immutable,因为这将防止你在处理多线程时出错。它应该看起来像这样:

public class Article {

    private final String mTitle;
    private final String mDescription;
    private final String mUrl;
    private final String mImageUrl;


    public Article(String title, String description, String url, String imageUrl) {
        mTitle = title;
        mDescription = description;
        mUrl = url;
        mImageUrl = imageUrl;
    }

    public String title() {
        return mTitle;
    }

    public String description() {
        return mDescription;
    }

    public String url() {
        return mUrl;
    }

    public String imageUrl() {
        return mImageUrl;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Article other = (Article) o;

        return mTitle != null && mTitle.equals(other.mTitle) &&
                mDescription != null && mDescription.equals(other.mDescription) &&
                mUrl != null && mUrl.equals(other.mUrl) &&
                mImageUrl != null && mImageUrl.equals(other.mImageUrl);
    }

    @Override
    public int hashCode() {
        int result = mTitle != null ? mTitle.hashCode() : 0;
        result = 31 * result + (mDescription != null ? mDescription.hashCode() : 0);
        result = 31 * result + (mUrl != null ? mUrl.hashCode() : 0);
        result = 31 * result + (mImageUrl != null ? mImageUrl.hashCode() : 0);
        return result;
    }

}