web-dev-qa-db-ja.com

RecyclerViewでグループの要素を分割する

(以下の写真の受信トレイアプリのように)タイトルのあるグループでRecyclerViewの要素を分割する必要があるので、どのアプローチが私のケースに適しているかを把握してください:1)異種レイアウトを使用できますが、グループに新しい要素を挿入するのはそれほど便利ではありません(同じグループの要素が既に追加されているか、新しい仕切りを追加する必要があるかどうかを確認する必要があるため)。この場合、このようなデータ構造を持つすべての操作を個別のクラスにラップします。

2)理論的には、ラベルを使用して各グループを独自のRecyclerViewでラップできますか?

Inbox app

40
Leo

たとえば、次のことができます。

  1. 使う TreeMap<Date,List<Event>>日付で要素を分割します。これは、ビジネスオブジェクトを保持するためのコレクションになります。もちろん、すでに同様の構造を持っている場合は、それを保持できます。 UIに正しい要素の順序を設定するためのアイテムのリストを簡単に作成するためのものが必要です。

  2. Listアイテム(たとえば、ListItem)専用の抽象型を定義して、ビジネスオブジェクトをラップします。その実装は次のようになります。

    public abstract class ListItem {
    
        public static final int TYPE_HEADER = 0;
        public static final int TYPE_EVENT = 1;
    
        abstract public int getType();
    } 
    
  3. リスト要素タイプごとにクラスを定義します(ここでは2つのタイプのみを追加しましたが、必要に応じて多くを使用できます)。

    public class HeaderItem extends ListItem {
    
        private Date date;
    
        // here getters and setters 
        // for title and so on, built
        // using date
    
        @Override
        public int getType() {
            return TYPE_HEADER;
        }
    
    }
    
    public class EventItem extends ListItem {
    
        private Event event;
    
        // here getters and setters 
        // for title and so on, built 
        // using event
    
        @Override
        public int getType() {
            return TYPE_EVENT;
        }
    
    }
    
  4. 次のようにリストを作成します(ここで、mEventsMapはポイント1のマップビルドです)。

    List<ListItem> mItems;
    // ...
    mItems = new ArrayList<>();
    for (Date date : mEventsMap.keySet()) {
        HeaderItem header = new HeaderItem();
        header.setDate(date); 
        mItems.add(header);
        for (Event event : mEventsMap.get(date)) {
            EventItem item = new EventItem();
            item.setEvent(event);
            mItems.add(item);
        }
    }
    
  5. ポイント4で定義されたRecyclerViewで作業するListのアダプターを定義します。ここで重要なのは、次のようにgetItemViewTypeメソッドをオーバーライドすることです。

    @Override
    public int getItemViewType(int position) {
        return mItems.get(position).getType();
    }
    

    次に、ヘッダーとイベントアイテム用に2つのレイアウトとViewHolderが必要です。これに応じて、アダプタメソッドがこれを処理する必要があります。

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ListItem.TYPE_HEADER) {
            View itemView = mLayoutInflater.inflate(R.layout.view_list_item_header, parent, false);
            return new HeaderViewHolder(itemView);
        } else {
            View itemView = mLayoutInflater.inflate(R.layout.view_list_item_event, parent, false);
            return new EventViewHolder(itemView);
        }
    }
    
    
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
        int type = getItemViewType(position);
        if (type == ListItem.TYPE_HEADER) {
            HeaderItem header = (HeaderItem) mItems.get(position);
            HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
            // your logic here
        } else {            
            EventItem event = (EventItem) mItems.get(position);
            EventViewHolder holder = (EventViewHolder) viewHolder;
            // your logic here
        }
    }
    

ここ これは、上記で説明したアプローチの実装を提供するGitHub上のリポジトリです。

93
thetonrifles

私のプロジェクトでこの問題を解決するために、私が書いたライブラリを使用してみてください。 Gradle依存関係(jcenterリポジトリが必要):

dependencies {
    //your other dependencies
    compile 'su.j2e:rv-joiner:1.0.3'//latest version by now
}

次に、あなたの状況では、次のようなことをすることができます:

//init your RecyclerView as usual
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));

//construct a joiner
RvJoiner rvJoiner = new RvJoiner();
rvJoiner.add(new JoinableLayout(R.layout.today));
YourAdapter todayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(todayAdapter));
rvJoiner.add(new JoinableLayout(R.layout.yesterday));
YourAdapter yesterdayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(yesterdayAdapter));

//set join adapter to your RecyclerView
rv.setAdapter(rvJoiner.getAdapter());

アイテムを追加する必要がある場合は、次のように適切なアダプターに追加します。

if (timeIsToday) {
    todayAdapter.addItem(item);//or other func you've written
} else if (timeIsYesterday) {
    yesterdayAdapter.addItem(item);
}

新しいグループをリサイクラビューに動的に追加する必要がある場合は、次のメソッドを使用できます。

rvJoiner.add(new JoinableLayout(R.layout.tomorrow));
YourAdapter tomorrowAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(tomorrowAdapter));

ライブラリの詳細については、 このリンク を確認してください。確かにあなたの目標を達成するための最良の方法だとは言えませんが、時々助けになります。

UPD:

外部ライブラリを使用せずにこれを行う方法を見つけました。 RecyclerView.ItemDecoration classを使用します。たとえば、グループ内の3つのアイテムでアイテムをグループ化するには、次のようにします。

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {

        private int textSize = 50;
        private int groupSpacing = 100;
        private int itemsInGroup = 3;

        private Paint paint = new Paint();
        {
            Paint.setTextSize(textSize);
        }

        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                View view = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(view);
                if (position % itemsInGroup == 0) {
                    c.drawText("Group " + (position / itemsInGroup + 1), view.getLeft(),
                            view.getTop() - groupSpacing / 2 + textSize / 3, Paint);
                }
            }
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (parent.getChildAdapterPosition(view) % itemsInGroup == 0) {
                outRect.set(0, groupSpacing, 0, 0);
            }
        }
    });

それが役に立てば幸い。

10
j2esu