/******************************************************************************* * This file is part of RedReader. * * RedReader is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RedReader is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RedReader. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.quantumbadger.redreader.adapters; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.concurrent.atomic.AtomicLong; @SuppressWarnings("ForLoopReplaceableByForEach") public class GroupedRecyclerViewAdapter extends RecyclerView.Adapter { private static final AtomicLong ITEM_UNIQUE_ID_GENERATOR = new AtomicLong(100000); public static abstract class Item { private final long mUniqueId = ITEM_UNIQUE_ID_GENERATOR.incrementAndGet(); private boolean mCurrentlyHidden = false; public abstract Class getViewType(); public abstract RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup viewGroup); public abstract void onBindViewHolder(final RecyclerView.ViewHolder viewHolder); public abstract boolean isHidden(); } private final ArrayList<Item>[] mItems; private final HashMap<Class, Integer> mItemViewTypeMap = new HashMap<>(); private final HashMap<Integer, Item> mViewTypeItemMap = new HashMap<>(); public GroupedRecyclerViewAdapter(final int groups) { //noinspection unchecked mItems = (ArrayList<Item>[])new ArrayList[groups]; for(int i = 0; i < groups; i++) { mItems[i] = new ArrayList<>(); } setHasStableIds(true); } private int getItemPositionInternal(final int groupId, final Item item) { final ArrayList<Item> group = mItems[groupId]; for(int i = 0; i < group.size(); i++) { if(group.get(i) == item) { return getItemPositionInternal(groupId, i); } } throw new RuntimeException("Item not found"); } // "positionInGroup" should include both hidden and visible items private int getItemPositionInternal(final int group, final int positionInGroup) { int result = 0; for(int i = 0; i < group; i++) { result += getGroupUnhiddenCount(i); } for(int i = 0; i < positionInGroup; i++) { if(!mItems[group].get(i).mCurrentlyHidden) { result++; } } return result; } private Item getItemInternal(final int desiredPosition) { if(desiredPosition < 0) { throw new RuntimeException("Item desiredPosition " + desiredPosition + " is too low"); } int currentPosition = 0; for(int groupId = 0; groupId < mItems.length; groupId++) { final ArrayList<Item> group = mItems[groupId]; for(int positionInGroup = 0; positionInGroup < group.size(); positionInGroup++) { final Item item = group.get(positionInGroup); if(!item.mCurrentlyHidden) { if(currentPosition == desiredPosition) { return item; } currentPosition++; } } } throw new RuntimeException("Item desiredPosition " + desiredPosition + " is too high"); } @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup viewGroup, final int viewType) { final RecyclerView.ViewHolder viewHolder = mViewTypeItemMap.get(viewType).onCreateViewHolder(viewGroup); final RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); viewHolder.itemView.setLayoutParams(layoutParams); return viewHolder; } @Override public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) { getItemInternal(position).onBindViewHolder(viewHolder); } @Override public int getItemViewType(final int position) { final Item item = getItemInternal(position); final Class viewTypeClass = item.getViewType(); Integer typeId = mItemViewTypeMap.get(viewTypeClass); if(typeId == null) { typeId = mItemViewTypeMap.size(); mItemViewTypeMap.put(viewTypeClass, typeId); mViewTypeItemMap.put(typeId, item); } return typeId; } private int getGroupUnhiddenCount(final int groupId) { final ArrayList<Item> group = mItems[groupId]; int result = 0; for(int i = 0; i < group.size(); i++) { if(!group.get(i).mCurrentlyHidden) { result++; } } return result; } @Override public long getItemId(final int position) { return getItemInternal(position).mUniqueId; } @Override public int getItemCount() { int count = 0; //noinspection ForLoopReplaceableByForEach for(int i = 0; i < mItems.length; i++) { count += getGroupUnhiddenCount(i); } return count; } public Item getItemAtPosition(final int position) { return getItemInternal(position); } public void appendToGroup(final int group, final Item item) { final int position = getItemPositionInternal(group + 1, 0); mItems[group].add(item); if(!item.mCurrentlyHidden) { notifyItemInserted(position); } } public void appendToGroup(final int group, final Collection<Item> items) { final int position = getItemPositionInternal(group + 1, 0); mItems[group].addAll(items); for(final Item item : items) { item.mCurrentlyHidden = false; } notifyItemRangeInserted(position, items.size()); } public void removeAllFromGroup(final int groupId) { final ArrayList<Item> group = mItems[groupId]; for(int i = group.size() - 1; i >= 0; i--) { final Item item = group.get(i); final int position = getItemPositionInternal(groupId, i); group.remove(i); if(!item.mCurrentlyHidden) { notifyItemRemoved(position); } } } public void removeFromGroup(final int groupId, final Item item) { final ArrayList<Item> group = mItems[groupId]; for(int i = 0; i < group.size(); i++) { if(group.get(i) == item) { final int position = getItemPositionInternal(groupId, i); group.remove(i); if(!item.mCurrentlyHidden) { notifyItemRemoved(position); } return; } } throw new RuntimeException("Item not found"); } public void updateHiddenStatus() { int position = 0; for(int groupId = 0; groupId < mItems.length; groupId++) { final ArrayList<Item> group = mItems[groupId]; for(int positionInGroup = 0; positionInGroup < group.size(); positionInGroup++) { final Item item = group.get(positionInGroup); final boolean wasHidden = item.mCurrentlyHidden; final boolean isHidden = item.isHidden(); item.mCurrentlyHidden = isHidden; if(isHidden && !wasHidden) { notifyItemRemoved(position); } else if(!isHidden && wasHidden) { notifyItemInserted(position); } if(!isHidden) { position++; } } } } public void notifyItemChanged(final int groupId, final Item item) { final int position = getItemPositionInternal(groupId, item); notifyItemChanged(position); } }