web-dev-qa-db-ja.com

下部のProgressBarでEndless Scroll RecyclerViewにアイテムを追加する

SOに関するVilenの優れた回答に従いました: ProgressBarで無限のスクロールrecyclerviewを実装する方法について、RecyclerViewグリッド にフッターとして不確定なプログレスバーを配置します。

私はそれを自分で実装し、機能しますが、例を拡張したいと思います。新しいステータス更新を追加するときにFacebookが行う方法と同様に、recyclerviewの上部に追加のアイテムを追加します。

リストに追加のアイテムを正常に追加できませんでした-これは、MainActivityのVilenのコードに追加した私のコードです。

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    int id = item.getItemId();

    if (id == R.id.add) {
        myDataset.add(0, "Newly added");
        mRecyclerView.smoothScrollToPosition(0);
        mAdapter.notifyItemInserted(0);
}
return super.onOptionsItemSelected(item);
}

[追加]ボタンをクリックしたとき:

Adding a new item

下にスクロールすると、1つではなく2つのスピナーが表示されます。

Scroll down

スピナーが終了し、次の5つのアイテムが読み込まれても、スピナーはまだそこにあります。

after spinner

私は何を間違えていますか?

32
Simon

問題は、新しいアイテムを追加すると、内部EndlessRecyclerOnScrollListenerがそれを認識せず、カウンターが壊れることです。実際のところ、EndlessRecyclerOnScrollListenerを使用した回答には、いくつかの制限と考えられる問題があります。一度に1つのアイテムをロードすると、機能しません。そのため、ここに拡張バージョンがあります。

  1. 不要になったEndlessRecyclerOnScrollListenerを取り除く
  2. アダプタをスクロールリスナーを含むこれに変更します

    public class MyAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        private final int VIEW_ITEM = 1;
        private final int VIEW_PROG = 0;
    
        private List<T> mDataset;
    
        // The minimum amount of items to have below your current scroll position before loading more.
        private int visibleThreshold = 2;
        private int lastVisibleItem, totalItemCount;
        private boolean loading;
        private OnLoadMoreListener onLoadMoreListener;
    
        public MyAdapter(List<T> myDataSet, RecyclerView recyclerView) {
            mDataset = myDataSet;
    
            if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
    
                final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                recyclerView.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 (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                            // End has been reached
                            // Do something
                            if (onLoadMoreListener != null) {
                                onLoadMoreListener.onLoadMore();
                            }
                            loading = true;
                        }
                    }
                });
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            return mDataset.get(position) != null ? VIEW_ITEM : VIEW_PROG;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder vh;
            if (viewType == VIEW_ITEM) {
                View v = LayoutInflater.from(parent.getContext())
                        .inflate(Android.R.layout.simple_list_item_1, parent, false);
    
                vh = new TextViewHolder(v);
            } else {
                View v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.progress_item, parent, false);
    
                vh = new ProgressViewHolder(v);
            }
            return vh;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder instanceof TextViewHolder) {
                ((TextViewHolder) holder).mTextView.setText(mDataset.get(position).toString());
            } else {
                ((ProgressViewHolder) holder).progressBar.setIndeterminate(true);
            }
        }
    
        public void setLoaded() {
            loading = false;
        }
    
        @Override
        public int getItemCount() {
            return mDataset.size();
        }
    
        public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
            this.onLoadMoreListener = onLoadMoreListener;
        }
    
        public interface OnLoadMoreListener {
            void onLoadMore();
        }
    
        public static class TextViewHolder extends RecyclerView.ViewHolder {
            public TextView mTextView;
    
            public TextViewHolder(View v) {
                super(v);
                mTextView = (TextView) v.findViewById(Android.R.id.text1);
            }
        }
    
        public static class ProgressViewHolder extends RecyclerView.ViewHolder {
            public ProgressBar progressBar;
    
            public ProgressViewHolder(View v) {
                super(v);
                progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
            }
        }
    }
    
  3. アクティビティクラスのコードを変更する

    mAdapter = new MyAdapter<String>(myDataset, mRecyclerView);
    mRecyclerView.setAdapter(mAdapter);
    
    mAdapter.setOnLoadMoreListener(new MyAdapter.OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            //add progress item
            myDataset.add(null);
            mAdapter.notifyItemInserted(myDataset.size() - 1);
    
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //remove progress item
                    myDataset.remove(myDataset.size() - 1);
                    mAdapter.notifyItemRemoved(myDataset.size());
                    //add items one by one
                    for (int i = 0; i < 15; i++) {
                        myDataset.add("Item" + (myDataset.size() + 1));
                        mAdapter.notifyItemInserted(myDataset.size());
                    }
                    mAdapter.setLoaded();
                    //or you can add all at once but do not forget to call mAdapter.notifyDataSetChanged();
                }
            }, 2000);
            System.out.println("load");
        }
    });
    

残りは変更されていませんが、これで問題が解決するかどうかをお知らせください。

82
Vilen

私はそれを理解したと思う。

NotifyItemRangeChangedを呼び出すのを忘れました。

@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.add) {
        myDataset.add(0, "Newly added");
        mAdapter.notifyItemInserted(0);
        mAdapter.notifyItemRangeChanged(1, myDataset.size());
        mRecyclerView.smoothScrollToPosition(0);
}
return super.onOptionsItemSelected(item);
}

コードを追加するとコードは機能しますが、スピナーの回転が完了すると、アイテム番号が正しくインクリメントされないことがわかります。

increment

これは、一番上の「新しく追加された」アイテムが実際のアイテムとしてカウントされるためです(「アイテム0」と呼ぶことができます)。つまり、アイテム22の前に実際のアイテムが21個あります。

2
Simon