/* * Copyright (C) 2016 Haruki Hasegawa * * 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. */ package com.h6ah4i.android.widget.advrecyclerview.composedadapter; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import com.h6ah4i.android.widget.advrecyclerview.adapter.AdapterPathSegment; import com.h6ah4i.android.widget.advrecyclerview.adapter.BridgeAdapterDataObserver; import com.h6ah4i.android.widget.advrecyclerview.adapter.UnwrapPositionResult; import com.h6ah4i.android.widget.advrecyclerview.adapter.WrapperAdapter; import com.h6ah4i.android.widget.advrecyclerview.adapter.ItemIdComposer; import com.h6ah4i.android.widget.advrecyclerview.adapter.ItemViewTypeComposer; import com.h6ah4i.android.widget.advrecyclerview.utils.WrappedAdapterUtils; import java.util.List; /** * A wrapper adapter which can compose and manage several children adapters. */ public class ComposedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements WrapperAdapter<RecyclerView.ViewHolder>, BridgeAdapterDataObserver.Subscriber { /** * Corresponding segmented position value of {@link RecyclerView#NO_POSITION}. */ public static long NO_SEGMENTED_POSITION = AdaptersSet.NO_SEGMENTED_POSITION; private AdaptersSet mAdaptersSet; private SegmentedPositionTranslator mSegmentedPositionTranslator; private SegmentedViewTypeTranslator mViewTypeTranslator; /** * Constructor. */ public ComposedAdapter() { mAdaptersSet = new AdaptersSet(this); mSegmentedPositionTranslator = new SegmentedPositionTranslator(mAdaptersSet); mViewTypeTranslator = new SegmentedViewTypeTranslator(); setHasStableIds(true); } /** * {@inheritDoc} */ @Override public void getWrappedAdapters(@NonNull List<RecyclerView.Adapter> adapters) { if (mAdaptersSet != null) { adapters.addAll(mAdaptersSet.getUniqueAdaptersList()); } } /** * {@inheritDoc} */ @Override public void release() { onRelease(); } /** * Implementation of the {@link #release()} method. */ @CallSuper protected void onRelease() { if (mAdaptersSet != null) { mAdaptersSet.release(); mAdaptersSet = null; } if (mSegmentedPositionTranslator != null) { mSegmentedPositionTranslator.release(); mSegmentedPositionTranslator = null; } mViewTypeTranslator = null; } /** * Returns the total number of children adapters. * * @return The total number of children adapters */ public int getChildAdapterCount() { return mAdaptersSet.getSegmentCount(); } /** * Add a child adapter to the tail. * <p>This method can be invoked before attaching (= call {@link RecyclerView#setAdapter(RecyclerView.Adapter)}) to RecyclerView. * Also it may throws an {@link IllegalStateException} if the ComposedAdapter has been configured to support stable IDs, </p> * * @param adapter The adapter which to be managed by the ComposedAdapter * @return An instance of {@link ComposedChildAdapterTag} * @see {@link #addAdapter(RecyclerView.Adapter, int)} */ public ComposedChildAdapterTag addAdapter(@NonNull RecyclerView.Adapter adapter) { return addAdapter(adapter, getChildAdapterCount()); } /** * Adda child adapter to the specified position. * * @param adapter The adapter which to be managed by the ComposedAdapter * @param position The position inserting a child adapter * @return An instance of {@link ComposedChildAdapterTag} * @see {@link #addAdapter(RecyclerView.Adapter)} */ public ComposedChildAdapterTag addAdapter(@NonNull RecyclerView.Adapter adapter, int position) { if (hasObservers() && hasStableIds()) { if (!adapter.hasStableIds()) { throw new IllegalStateException("Wrapped child adapter must has stable IDs"); } } final ComposedChildAdapterTag tag = mAdaptersSet.addAdapter(adapter, position); final int segment = mAdaptersSet.getAdapterSegment(tag); mSegmentedPositionTranslator.invalidateSegment(segment); // NOTE: Need to assume as data set change here because view types and item IDs are completely changed! notifyDataSetChanged(); return tag; } /** * Remove a child adapter. * * @param tag The tag object linked to a child adapter to be removed * @return True if the child adapter is removed. Otherwise false. */ public boolean removeAdapter(@NonNull ComposedChildAdapterTag tag) { final int segment = mAdaptersSet.getAdapterSegment(tag); if (segment < 0) { return false; } mAdaptersSet.removeAdapter(tag); mSegmentedPositionTranslator.invalidateSegment(segment); // NOTE: Need to assume as data set change here because view types and item IDs are completely changed! notifyDataSetChanged(); return true; } /** * Returns the assigned segment number. * * @param tag The tag object linked to a child adapter * @return Number of the assigned segment */ public int getSegment(@NonNull ComposedChildAdapterTag tag) { return mAdaptersSet.getAdapterSegment(tag); } /** * Gets a "segmented position". * <p>The segmented position is a packed long value which contains "segment" and * "offset inside of the segment" information.</p> * * @param flatPosition The normal flat position to be converted * @return Segmented Position * @see {@link #extractSegmentPart(long)} * @see {@link #extractSegmentOffsetPart(long)} */ public long getSegmentedPosition(int flatPosition) { return mSegmentedPositionTranslator.getSegmentedPosition(flatPosition); } /** * Returns the segment value extracted from a segmented position. * * @param segmentedPosition The segment position value to be converted * @return segment part */ public static int extractSegmentPart(long segmentedPosition) { return AdaptersSet.extractSegment(segmentedPosition); } /** * Returns the offset value extracted from a segmented position. * * @param segmentedPosition The segment position value to be converted * @return offset part */ public static int extractSegmentOffsetPart(long segmentedPosition) { return AdaptersSet.extractSegmentOffset(segmentedPosition); } /** * {@liheritDoc} */ @Override public void setHasStableIds(boolean hasStableIds) { // checks all children adapters support stable IDs if (hasStableIds && !hasStableIds()) { final int numSegments = mAdaptersSet.getSegmentCount(); for (int i = 0; i < numSegments; i++) { RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(i); if (!adapter.hasStableIds()) { throw new IllegalStateException("All child adapters must support stable IDs"); } } } super.setHasStableIds(hasStableIds); } /** * {@inheritDoc} */ @Override public long getItemId(int position) { final long segmentedPosition = getSegmentedPosition(position); final int segment = AdaptersSet.extractSegment(segmentedPosition); final int offset = AdaptersSet.extractSegmentOffset(segmentedPosition); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); final int rawViewType = adapter.getItemViewType(offset); final long rawId = adapter.getItemId(offset); final int wrappedViewType = mViewTypeTranslator.wrapItemViewType(segment, rawViewType); final int wrappedSegment = ItemViewTypeComposer.extractSegmentPart(wrappedViewType); return ItemIdComposer.composeSegment(wrappedSegment, rawId); } /** * {@lineritDoc} */ @Override public int getItemViewType(int position) { final long segmentedPosition = getSegmentedPosition(position); final int segment = AdaptersSet.extractSegment(segmentedPosition); final int offset = AdaptersSet.extractSegmentOffset(segmentedPosition); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); final int rawViewType = adapter.getItemViewType(offset); return mViewTypeTranslator.wrapItemViewType(segment, rawViewType); } /** * {@lineritDoc} */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final long packedViewType = mViewTypeTranslator.unwrapViewType(viewType); final int segment = SegmentedViewTypeTranslator.extractWrapperSegment(packedViewType); final int origViewType = SegmentedViewTypeTranslator.extractWrappedViewType(packedViewType); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); return adapter.onCreateViewHolder(parent, origViewType); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { final long segmentedPosition = getSegmentedPosition(position); final int segment = AdaptersSet.extractSegment(segmentedPosition); final int offset = AdaptersSet.extractSegmentOffset(segmentedPosition); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); adapter.onBindViewHolder(holder, offset); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) { final long segmentedPosition = getSegmentedPosition(position); final int segment = AdaptersSet.extractSegment(segmentedPosition); final int offset = AdaptersSet.extractSegmentOffset(segmentedPosition); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); adapter.onBindViewHolder(holder, offset, payloads); } /** * {@lineritDoc} */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { final List<RecyclerView.Adapter> adapters = mAdaptersSet.getUniqueAdaptersList(); for (int i = 0; i < adapters.size(); i++) { adapters.get(i).onAttachedToRecyclerView(recyclerView); } } /** * {@lineritDoc} */ @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { final List<RecyclerView.Adapter> adapters = mAdaptersSet.getUniqueAdaptersList(); for (int i = 0; i < adapters.size(); i++) { adapters.get(i).onDetachedFromRecyclerView(recyclerView); } } /** * {@lineritDoc} */ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { onViewAttachedToWindow(holder, holder.getItemViewType()); } /** * {@lineritDoc} */ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder, int viewType) { final long packedViewType = mViewTypeTranslator.unwrapViewType(viewType); final int segment = SegmentedViewTypeTranslator.extractWrapperSegment(packedViewType); final int wrappedViewType = SegmentedViewTypeTranslator.extractWrappedViewType(packedViewType); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); WrappedAdapterUtils.invokeOnViewAttachedToWindow(adapter, holder, wrappedViewType); } /** * {@lineritDoc} */ @Override public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { onViewDetachedFromWindow(holder, holder.getItemViewType()); } /** * {@lineritDoc} */ @Override public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder, int viewType) { final long packedViewType = mViewTypeTranslator.unwrapViewType(viewType); final int segment = SegmentedViewTypeTranslator.extractWrapperSegment(packedViewType); final int wrappedViewType = SegmentedViewTypeTranslator.extractWrappedViewType(packedViewType); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); WrappedAdapterUtils.invokeOnViewDetachedFromWindow(adapter, holder, wrappedViewType); } /** * {@lineritDoc} */ @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { onViewRecycled(holder, holder.getItemViewType()); } /** * {@lineritDoc} */ @Override public void onViewRecycled(RecyclerView.ViewHolder holder, int viewType) { final long packedViewType = mViewTypeTranslator.unwrapViewType(viewType); final int segment = SegmentedViewTypeTranslator.extractWrapperSegment(packedViewType); final int wrappedViewType = SegmentedViewTypeTranslator.extractWrappedViewType(packedViewType); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); WrappedAdapterUtils.invokeOnViewRecycled(adapter, holder, wrappedViewType); } /** * {@lineritDoc} */ @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { return onFailedToRecycleView(holder, holder.getItemViewType()); } /** * {@lineritDoc} */ @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder, int viewType) { final long packedViewType = mViewTypeTranslator.unwrapViewType(viewType); final int segment = SegmentedViewTypeTranslator.extractWrapperSegment(packedViewType); final int wrappedViewType = SegmentedViewTypeTranslator.extractWrappedViewType(packedViewType); final RecyclerView.Adapter adapter = mAdaptersSet.getAdapter(segment); return WrappedAdapterUtils.invokeOnFailedToRecycleView(adapter, holder, wrappedViewType); } /** * {@lineritDoc} */ @Override public int getItemCount() { return mSegmentedPositionTranslator.getTotalItemCount(); } /** * {@lineritDoc} */ @Override public void unwrapPosition(@NonNull UnwrapPositionResult dest, int position) { final long segmentedPosition = mSegmentedPositionTranslator.getSegmentedPosition(position); if (segmentedPosition != AdaptersSet.NO_SEGMENTED_POSITION) { final int segment = AdaptersSet.extractSegment(segmentedPosition); final int offset = AdaptersSet.extractSegmentOffset(segmentedPosition); dest.adapter = mAdaptersSet.getAdapter(segment); dest.position = offset; dest.tag = mAdaptersSet.getTag(segment); } } /** * {@lineritDoc} */ @Override public int wrapPosition(@NonNull AdapterPathSegment pathSegment, int position) { if (pathSegment.tag != null) { final ComposedChildAdapterTag tag = (ComposedChildAdapterTag) pathSegment.tag; final int segment = mAdaptersSet.getAdapterSegment(tag); return mSegmentedPositionTranslator.getFlatPosition(segment, position); } else { return RecyclerView.NO_POSITION; } } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterChanged(RecyclerView.Adapter source, Object tag) { onHandleWrappedAdapterChanged(source, (List<ComposedChildAdapterTag>) tag); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterItemRangeChanged(RecyclerView.Adapter source, Object tag, int positionStart, int itemCount) { onHandleWrappedAdapterItemRangeChanged(source, (List<ComposedChildAdapterTag>) tag, positionStart, itemCount); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterItemRangeChanged(RecyclerView.Adapter source, Object tag, int positionStart, int itemCount, Object payload) { onHandleWrappedAdapterItemRangeChanged(source, (List<ComposedChildAdapterTag>) tag, positionStart, itemCount, payload); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterItemRangeInserted(RecyclerView.Adapter source, Object tag, int positionStart, int itemCount) { onHandleWrappedAdapterItemRangeInserted(source, (List<ComposedChildAdapterTag>) tag, positionStart, itemCount); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterItemRangeRemoved(RecyclerView.Adapter source, Object tag, int positionStart, int itemCount) { onHandleWrappedAdapterItemRangeRemoved(source, (List<ComposedChildAdapterTag>) tag, positionStart, itemCount); } /** * {@lineritDoc} */ @Override @SuppressWarnings("unchecked") public void onBridgedAdapterRangeMoved(RecyclerView.Adapter source, Object tag, int fromPosition, int toPosition, int itemCount) { onHandleWrappedAdapterRangeMoved(source, (List<ComposedChildAdapterTag>) tag, fromPosition, toPosition, itemCount); } protected void onHandleWrappedAdapterChanged(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags) { mSegmentedPositionTranslator.invalidateAll(); notifyDataSetChanged(); } protected void onHandleWrappedAdapterItemRangeChanged(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags, int localPositionStart, int itemCount) { final int nTags = sourceTags.size(); for (int i = 0; i < nTags; i++) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(i)); final int positionStart = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localPositionStart); notifyItemRangeChanged(positionStart, itemCount); } } protected void onHandleWrappedAdapterItemRangeChanged(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags, int localPositionStart, int itemCount, Object payload) { final int nTags = sourceTags.size(); for (int i = 0; i < nTags; i++) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(i)); final int positionStart = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localPositionStart); notifyItemRangeChanged(positionStart, itemCount, payload); } } protected void onHandleWrappedAdapterItemRangeInserted(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags, int localPositionStart, int itemCount) { if (itemCount <= 0) { return; } final int nTags = sourceTags.size(); if (nTags == 1) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(0)); mSegmentedPositionTranslator.invalidateSegment(adapterSegment); final int positionStart = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localPositionStart); notifyItemRangeInserted(positionStart, itemCount); } else { for (int i = 0; i < nTags; i++) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(i)); mSegmentedPositionTranslator.invalidateSegment(adapterSegment); } notifyDataSetChanged(); } } protected void onHandleWrappedAdapterItemRangeRemoved(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags, int localPositionStart, int itemCount) { if (itemCount <= 0) { return; } final int nTags = sourceTags.size(); if (nTags == 1) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(0)); mSegmentedPositionTranslator.invalidateSegment(adapterSegment); final int positionStart = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localPositionStart); notifyItemRangeRemoved(positionStart, itemCount); } else { for (int i = 0; i < nTags; i++) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(i)); mSegmentedPositionTranslator.invalidateSegment(adapterSegment); } notifyDataSetChanged(); } } protected void onHandleWrappedAdapterRangeMoved(RecyclerView.Adapter sourceAdapter, List<ComposedChildAdapterTag> sourceTags, int localFromPosition, int localToPosition, int itemCount) { if (itemCount != 1) { throw new IllegalStateException("itemCount should be always 1 (actual: " + itemCount + ")"); } final int nTags = sourceTags.size(); if (nTags == 1) { final int adapterSegment = mAdaptersSet.getAdapterSegment(sourceTags.get(0)); final int fromPosition = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localFromPosition); final int toPosition = mSegmentedPositionTranslator.getFlatPosition(adapterSegment, localToPosition); notifyItemMoved(fromPosition, toPosition); } else { notifyDataSetChanged(); } } }