diff options
Diffstat (limited to 'org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeaderViewRecyclerAdapter.java')
-rw-r--r-- | org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeaderViewRecyclerAdapter.java | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeaderViewRecyclerAdapter.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeaderViewRecyclerAdapter.java new file mode 100644 index 00000000..4de15531 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeaderViewRecyclerAdapter.java @@ -0,0 +1,214 @@ +package org.fox.ttrss.util; + +/* + * Copyright (C) 2014 darnmason + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * <p> + * RecyclerView adapter designed to wrap an existing adapter allowing the addition of + * header views and footer views. + * </p> + * <p> + * I implemented it to aid with the transition from ListView to RecyclerView where the ListView's + * addHeaderView and addFooterView methods were used. Using this class you may initialize your + * header views in the Fragment/Activity and add them to the adapter in the same way you used to + * add them to a ListView. + * </p> + * <p> + * I also required to be able to swap out multiple adapters with different content, therefore + * setAdapter may be called multiple times. + * </p> + * Created by darnmason on 07/11/2014. + */ +public class HeaderViewRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + + private static final int HEADERS_START = Integer.MIN_VALUE; + private static final int FOOTERS_START = Integer.MIN_VALUE + 10; + private static final int ITEMS_START = Integer.MIN_VALUE + 20; + private static final int ADAPTER_MAX_TYPES = 100; + + private RecyclerView.Adapter mWrappedAdapter; + private List<View> mHeaderViews, mFooterViews; + private Map<Class, Integer> mItemTypesOffset; + + /** + * Construct a new header view recycler adapter + * @param adapter The underlying adapter to wrap + */ + public HeaderViewRecyclerAdapter(RecyclerView.Adapter adapter) { + mHeaderViews = new ArrayList<View>(); + mFooterViews = new ArrayList<View>(); + mItemTypesOffset = new HashMap<Class, Integer>(); + setWrappedAdapter(adapter); + } + + /** + * Replaces the underlying adapter, notifying RecyclerView of changes + * @param adapter The new adapter to wrap + */ + public void setAdapter(RecyclerView.Adapter adapter) { + if(mWrappedAdapter != null && mWrappedAdapter.getItemCount() > 0) { + notifyItemRangeRemoved(getHeaderCount(), mWrappedAdapter.getItemCount()); + } + setWrappedAdapter(adapter); + notifyItemRangeInserted(getHeaderCount(), mWrappedAdapter.getItemCount()); + } + + @Override + public int getItemViewType(int position) { + int hCount = getHeaderCount(); + if (position < hCount) return HEADERS_START + position; + else { + int itemCount = mWrappedAdapter.getItemCount(); + if (position < hCount + itemCount) { + return getAdapterTypeOffset() + mWrappedAdapter.getItemViewType(position - hCount); + } + else return FOOTERS_START + position - hCount - itemCount; + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + if (viewType < HEADERS_START + getHeaderCount()) + return new StaticViewHolder(mHeaderViews.get(viewType - HEADERS_START)); + else if (viewType < FOOTERS_START + getFooterCount()) + return new StaticViewHolder(mFooterViews.get(viewType - FOOTERS_START)); + else { + return mWrappedAdapter.onCreateViewHolder(viewGroup, viewType - getAdapterTypeOffset()); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + int hCount = getHeaderCount(); + if (position >= hCount && position < hCount + mWrappedAdapter.getItemCount()) + mWrappedAdapter.onBindViewHolder(viewHolder, position - hCount); + } + + /** + * Add a static view to appear at the start of the RecyclerView. Headers are displayed in the + * order they were added. + * @param view The header view to add + */ + public void addHeaderView(View view) { + mHeaderViews.add(view); + } + + /** + * Add a static view to appear at the end of the RecyclerView. Footers are displayed in the + * order they were added. + * @param view The footer view to add + */ + public void addFooterView(View view) { + mFooterViews.add(view); + } + + public void removeFooterView(View view) { + mFooterViews.remove(view); + } + + @Override + public int getItemCount() { + return getHeaderCount() + getFooterCount() + getWrappedItemCount(); + } + + /** + * @return The item count in the underlying adapter + */ + public int getWrappedItemCount() { + return mWrappedAdapter.getItemCount(); + } + + /** + * @return The number of header views added + */ + public int getHeaderCount() { + return mHeaderViews.size(); + } + + /** + * @return The number of footer views added + */ + public int getFooterCount() { + return mFooterViews.size(); + } + + private void setWrappedAdapter(RecyclerView.Adapter adapter) { + if (mWrappedAdapter != null) mWrappedAdapter.unregisterAdapterDataObserver(mDataObserver); + mWrappedAdapter = adapter; + Class adapterClass = mWrappedAdapter.getClass(); + if(!mItemTypesOffset.containsKey(adapterClass)) putAdapterTypeOffset(adapterClass); + mWrappedAdapter.registerAdapterDataObserver(mDataObserver); + } + + private void putAdapterTypeOffset(Class adapterClass) { + mItemTypesOffset.put(adapterClass, ITEMS_START + mItemTypesOffset.size() * ADAPTER_MAX_TYPES); + } + + private int getAdapterTypeOffset() { + return mItemTypesOffset.get(mWrappedAdapter.getClass()); + } + + private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + notifyDataSetChanged(); + } + + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + super.onItemRangeChanged(positionStart, itemCount); + notifyItemRangeChanged(positionStart + getHeaderCount(), itemCount); + } + + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + super.onItemRangeInserted(positionStart, itemCount); + notifyItemRangeInserted(positionStart + getHeaderCount(), itemCount); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + super.onItemRangeRemoved(positionStart, itemCount); + notifyItemRangeRemoved(positionStart + getHeaderCount(), itemCount); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + super.onItemRangeMoved(fromPosition, toPosition, itemCount); + int hCount = getHeaderCount(); + // TODO: No notifyItemRangeMoved method? + notifyItemRangeChanged(fromPosition + hCount, toPosition + hCount + itemCount); + } + }; + + private static class StaticViewHolder extends RecyclerView.ViewHolder { + + public StaticViewHolder(View itemView) { + super(itemView); + } + } +} |