public class ScrambleAdapter<T> extends AbsScrambleAdapter<
        ScrambleAdapter<T>,
        ScrambleAdapter<?>,
        RecyclerView.ViewHolder,
        ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder>,
        T
        > {
    private Context mContext;
    private List<T> mItems;

    private ListenerRelay<ScrambleAdapter<?>, RecyclerView.ViewHolder> mListenerRelay;

    @SafeVarargs
    public ScrambleAdapter(
            @NonNull Context context,
            @NonNull List<T> items,
            @Nullable ListenerRelay<ScrambleAdapter<?>,
                    RecyclerView.ViewHolder> listenerRelay,
            @NonNull IViewHolderFactory<ScrambleAdapter<?>,
                    RecyclerView.ViewHolder,
                    ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder>,
                    ?
                    >... iViewHolderFactories
    ) {
        this(context, items, listenerRelay, Arrays.asList(iViewHolderFactories));
    }

    public ScrambleAdapter(
            @NonNull Context context,
            @NonNull List<T> items,
            @Nullable ListenerRelay<ScrambleAdapter<?>,
                    RecyclerView.ViewHolder> listenerRelay,
            @NonNull List<? extends IViewHolderFactory<ScrambleAdapter<?>,
                    RecyclerView.ViewHolder,
                    ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder>,
                    ?
                    >> iViewHolderFactories
    ) {
        super(iViewHolderFactories);
        this.mContext = context;
        this.mItems = items;
        this.mListenerRelay = listenerRelay;
    }

    public Context getContext() {
        return mContext;
    }

    @Override
    public ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> createForwardingListener(IViewHolderFactory<ScrambleAdapter<?>, RecyclerView.ViewHolder, ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder>, ?> viewHolderFactory) {
        ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> forwardingListener = viewHolderFactory.createForwardingListener();
        if (forwardingListener != null) {
            forwardingListener.setListenerRelay(mListenerRelay);
        }
        return forwardingListener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder createNullViewHolder() {
        View view = new View(mContext);
        return new RecyclerView.ViewHolder(view) {
        };
    }

    @Override
    public T getItemAt(int position) {
        return mItems.get(position);
    }

    public List<T> getItems() {
        return mItems;
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    public static abstract class AbsViewHolderFactory<EVH extends RecyclerView.ViewHolder>
            implements IViewHolderFactory<
            ScrambleAdapter<?>,
            RecyclerView.ViewHolder,
            ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder>,
            EVH
            > {

        @Override
        public boolean isAssignable(@NonNull ScrambleAdapter<?> adapter, Object object) {
            return isAssignable(object);
        }

        public abstract boolean isAssignable(Object object);

        @Override
        public void onBindViewHolder(@NonNull ScrambleAdapter<?> adapter, @NonNull EVH holder, int position, @Nullable Object object, List<Object> payloads) {
            this.onBindViewHolder(adapter, holder, position, object);
        }

        @Override
        public void onBindViewHolder(@NonNull ScrambleAdapter<?> adapter, @NonNull EVH holder, int position, @Nullable Object object) {
            // override me
        }

        @Override
        public ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> createForwardingListener() {
            return new ForwardingListener<>();
        }

        public ClassicForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> createClassicForwardingListener() {
            return new ClassicForwardingListener<>();
        }

        @Override
        public boolean onFailedToRecycleView(@NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder holder) {
            // no-op
            return false;
        }

        @Override
        public void onViewAttachedToWindow(@NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder holder) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(@NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder holder) {
            // no-op
        }

        @Override
        public void onViewRecycled(@NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder holder) {
            // no-op
        }
    }
} public class ListenerRelay<
        A extends RecyclerView.Adapter<? extends VH>,
        VH extends RecyclerView.ViewHolder
        > {

    /**
     * @see android.view.View.OnClickListener
     */
    public void onClick(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull View view) {
    }

    /**
     * @see android.view.View.OnLongClickListener
     */
    public boolean onLongClick(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull View view) {
        return false;
    }

    /**
     * @see RadioGroup.OnCheckedChangeListener
     */
    public void onCheckedChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, RadioGroup group, int checkedId) {
    }

    /**
     * @see android.widget.CompoundButton.OnCheckedChangeListener
     */
    public void onCheckedChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull CompoundButton buttonView, boolean isChecked) {
    }

    /**
     * @see android.widget.SeekBar.OnSeekBarChangeListener
     */
    public void onProgressChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull SeekBar seekBar, int progress, boolean fromUser) {
    }

    /**
     * @see android.widget.SeekBar.OnSeekBarChangeListener
     */
    public void onStartTrackingTouch(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull SeekBar seekBar) {
    }

    /**
     * @see android.widget.SeekBar.OnSeekBarChangeListener
     */
    public void onStopTrackingTouch(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull SeekBar seekBar) {
    }

    /**
     * @see android.widget.AdapterView.OnItemSelectedListener
     */
    public void onNothingSelected(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull AdapterView<?> parent) {

    }

    /**
     * @see android.widget.AdapterView.OnItemSelectedListener
     */
    public void onItemSelected(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull AdapterView<?> parent, @NonNull View view, int position, long id) {
    }

    /**
     * @see android.widget.TextView.OnEditorActionListener
     */
    public boolean onEditorAction(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull TextView v, int actionId, KeyEvent event) {
        return false;
    }

    /**
     * @see android.text.TextWatcher
     */
    public void beforeTextChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull TextView v, @NonNull CharSequence s, int start, int count, int after) {
    }

    /**
     * @see android.text.TextWatcher
     */
    public void onTextChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull TextView v, @NonNull CharSequence s, int start, int before, int count) {
    }

    /**
     * @see android.text.TextWatcher
     */
    public void afterTextChanged(@NonNull RecyclerView recyclerView, @NonNull A adapter, @NonNull VH vh, @NonNull TextView v, @NonNull Editable s) {
    }
} public class SimpleStringAdapter extends ScrambleAdapter<String> {
    public SimpleStringAdapter(Context context, List<String> items, ListenerRelay<ScrambleAdapter<?>, RecyclerView.ViewHolder> listenerRelay) {
        super(context, items, listenerRelay, new SimpleStringViewHolderFactory());
    }
} public class MyInfoViewHolderFactory extends ScrambleAdapter.AbsViewHolderFactory<MyInfoViewHolderFactory.ViewHolder> {
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ScrambleAdapter<?> adapter, @NonNull ViewGroup parent, @NonNull ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> forwardingListener) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_my_info, parent, false);
        ViewHolder vh = new ViewHolder(view);

        vh.intValueSeek.setOnSeekBarChangeListener(forwardingListener);
        vh.ordinalLabelSpinner.setOnItemSelectedListener(forwardingListener);
        vh.checkedSwitch.setOnCheckedChangeListener(forwardingListener);

        vh.ordinalLabelSpinner.setAdapter(CodeLabelAdapter.newInstance(view.getContext(), OrdinalLabel.values(), true));

        return vh;
    }

    @Override
    public void onBindViewHolder(@NonNull ScrambleAdapter<?> adapter, @NonNull ViewHolder holder, int position, Object object) {
        MyInfo item = (MyInfo) object;

        holder.intValueSeek.setProgress(item.getIntValue());
        SpinnerUtils.selectSpinnerValue(holder.ordinalLabelSpinner, item.getOrdinalLabel(), true);
        holder.checkedSwitch.setChecked(item.isChecked());
    }

    @Override
    public boolean isAssignable(Object object) {
        return object instanceof MyInfo;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        SeekBar intValueSeek;
        Spinner ordinalLabelSpinner;
        Switch checkedSwitch;

        public ViewHolder(View itemView) {
            super(itemView);
            intValueSeek = (SeekBar) itemView.findViewById(R.id.seek_int_value);
            ordinalLabelSpinner = (Spinner) itemView.findViewById(R.id.spinner_ordinal_label);
            checkedSwitch = (Switch) itemView.findViewById(R.id.switch_checked);
        }
    }
} public class TextInfoViewHolderFactory extends ScrambleAdapter.AbsViewHolderFactory<TextInfoViewHolderFactory.ViewHolder> {
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ScrambleAdapter<?> adapter, @NonNull ViewGroup parent, @NonNull ForwardingListener<ScrambleAdapter<?>, RecyclerView.ViewHolder> forwardingListener) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_info, parent, false);
        ViewHolder vh = new ViewHolder(view);

        vh.editText.setOnEditorActionListener(forwardingListener);
        forwardingListener.addTextChangedListener(vh.editText);
        vh.goButton.setOnClickListener(forwardingListener);

        return vh;
    }

    @Override
    public void onBindViewHolder(@NonNull ScrambleAdapter adapter, @NonNull TextInfoViewHolderFactory.ViewHolder holder, int position, Object object) {
        TextInfo item = (TextInfo) object;

        holder.editText.setText(item.getText());
    }

    @Override
    public boolean isAssignable(Object object) {
        return object instanceof TextInfo;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public final EditText editText;
        public final View goButton;

        public ViewHolder(View itemView) {
            super(itemView);
            editText = (EditText) itemView.findViewById(R.id.edit_text);
            goButton = itemView.findViewById(R.id.button_go);
        }
    }
} public class MyInfo {
    private int intValue;
    private OrdinalLabel ordinalLabel;
    private boolean checked;

    public int getIntValue() {
        return intValue;
    }

    public void setIntValue(int intValue) {
        this.intValue = intValue;
    }

    public OrdinalLabel getOrdinalLabel() {
        return ordinalLabel;
    }

    public void setOrdinalLabel(OrdinalLabel ordinalLabel) {
        this.ordinalLabel = ordinalLabel;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }
} public enum OrdinalLabel implements ICodeLabel {
    FIRST("first", R.string.ordinal_label_first),
    SECOND("second", R.string.ordinal_label_second),
    THIRD("third", R.string.ordinal_label_third),
    FOURTH("fourth", R.string.ordinal_label_fourth),
    //
    ;

    final String code;
    @StringRes
    final int resId;

    OrdinalLabel(String code, @StringRes int resId) {
        this.code = code;
        this.resId = resId;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getLabel(Resources res) {
        return res.getString(resId);
    }
} public class TextInfo {
    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
} public class SnackbarLogic {
    public SnackbarLogic() {
    }

    @NonNull
    public ISnackbarWrapper make(@NonNull View view, @NonNull CharSequence text, int duration) {
        return new SnackbarWrapperImpl(Snackbar.make(view, text, duration));
    }

    @NonNull
    public ISnackbarWrapper make(@NonNull View view, @StringRes int resId, int duration) {
        return new SnackbarWrapperImpl(Snackbar.make(view, resId, duration));
    }

    /**
     * For InstrumentationTest
     */
    public interface ISnackbarWrapper {
        void show();
    }

    private static class SnackbarWrapperImpl implements ISnackbarWrapper {
        private Snackbar orig;

        public SnackbarWrapperImpl(Snackbar orig) {
            this.orig = orig;
        }

        @Override
        public void show() {
            orig.show();
        }
    }
} public class MergeRecyclerAdapter<T extends RecyclerView.Adapter> extends RecyclerView
        .Adapter {

    private Context mContext;
    protected ArrayList<LocalAdapter> mAdapters = new ArrayList<>();
    private int mViewTypeIndex = 0;

    public MergeRecyclerAdapter() {
    }

    public MergeRecyclerAdapter(Context context) {
        this.mContext = context;
    }

    /**
     * A Mergeable adapter must implement both ListAdapter and SpinnerAdapter to be useful in lists and spinners.
     */

    public class LocalAdapter extends RecyclerView.AdapterDataObserver {
        public final T mAdapter;
        public int mLocalPosition = 0;
        public Map<Integer, Integer> mViewTypesMap = new HashMap<>();

        public LocalAdapter(T adapter) {
            mAdapter = adapter;
        }

        @Override
        public void onChanged() {
            notifyDataSetChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            super.onItemRangeChanged(positionStart, itemCount);
            int offset = getAdapterOffsetForAdapter(this);
            notifyItemRangeChanged(offset + positionStart, itemCount);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            super.onItemRangeInserted(positionStart, itemCount);
            int offset = getAdapterOffsetForAdapter(this);
            notifyItemRangeInserted(offset + positionStart, itemCount);
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            super.onItemRangeRemoved(positionStart, itemCount);
            int offset = getAdapterOffsetForAdapter(this);
            notifyItemRangeRemoved(offset + positionStart, itemCount);
        }
    }

    /**
     * Append the given adapter to the list of merged adapters.
     */
    public void addAdapter(T adapter) {
        addAdapter(mAdapters.size(), adapter);
    }

    /**
     * Append the given adapter to the list of merged adapters.
     */
    public void addAdapter(T adapter, boolean useNotifyItemRangeInserted) {
        addAdapter(mAdapters.size(), adapter, useNotifyItemRangeInserted);
    }

    /**
     * Add the given adapter to the list of merged adapters at the given index.
     */
    public void addAdapter(int index, T adapter) {
        addAdapter(index, adapter, true);
    }

    /**
     * Add the given adapter to the list of merged adapters at the given index.
     */
    public void addAdapter(int index, T adapter, boolean useNotifyItemRangeInserted) {
        LocalAdapter la = new LocalAdapter(adapter);
        mAdapters.add(index, la);
        adapter.registerAdapterDataObserver(la);
        if (useNotifyItemRangeInserted) {
            int position = getAdapterOffsetForAdapter(la);
            notifyItemRangeInserted(position, adapter.getItemCount());
        } else {
            notifyDataSetChanged();
        }
    }

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     * remove adapter
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    /**
     * Remove the given adapter from the list of merged adapters.
     */
    public void removeAdapter(T adapter) {
        removeAdapter(adapter, true);
    }

    /**
     * Remove the given adapter from the list of merged adapters.
     */
    public void removeAdapter(T adapter, boolean useNotifyItemRangeRemoved) {
        int index = indexOfAdapter(adapter);
        if (index != -1) {
            removeAdapter(index, useNotifyItemRangeRemoved);
        }
    }

    /**
     * Remove the adapter at the given index from the list of merged adapters.
     */
    public void removeAdapter(int index) {
        removeAdapter(index, true);
    }

    /**
     * Remove the adapter at the given index from the list of merged adapters.
     */
    public void removeAdapter(int index, boolean useNotifyItemRangeRemoved) {
        if (index < 0 || index >= mAdapters.size()) return;
        LocalAdapter adapter = mAdapters.get(index);
        int position = getAdapterOffsetForAdapter(adapter);
        int itemCount = adapter.mAdapter.getItemCount();
        mAdapters.remove(index);
        adapter.mAdapter.unregisterAdapterDataObserver(adapter);
        if (useNotifyItemRangeRemoved) {
            notifyItemRangeRemoved(position, itemCount);
        } else {
            notifyDataSetChanged();
        }
    }

    /**
     * Returns the index of the first occurrence of the specified adapter in this list, or -1 if this list does not contain the adapter.
     */
    public int indexOfAdapter(T adapter) {
        int index = 0;
        for (LocalAdapter la : mAdapters) {
            if (la.mAdapter.equals(adapter)) {
                return index;
            }
            index++;
        }
        return -1;
    }


    public int getSubAdapterCount() {
        return mAdapters.size();
    }

    public T getSubAdapter(int index) {
        return mAdapters.get(index).mAdapter;
    }

    /**
     * Adds a new View to the roster of things to appear in
     * the aggregate list.
     *
     * @param view Single view to add
     */
    public void addView(View view) {
        ArrayList<View> list = new ArrayList<View>(1);

        list.add(view);

        addViews(list);
    }

    /**
     * Adds a list of views to the roster of things to appear
     * in the aggregate list.
     *
     * @param views List of views to add
     */
    public void addViews(List<View> views) {
        addAdapter((T) new ViewsAdapter(mContext, views));
    }

    @Override
    public int getItemCount() {
        int count = 0;
        for (LocalAdapter adapter : mAdapters) {
            count += adapter.mAdapter.getItemCount();
        }
        return count;
        // TODO: cache counts until next onChanged
    }

    /**
     * For a given merged position, find the corresponding Adapter and local position within that Adapter by iterating through Adapters and
     * summing their counts until the merged position is found.
     *
     * @param position a merged (global) position
     * @return the matching Adapter and local position, or null if not found
     */
    public LocalAdapter getAdapterOffsetForItem(final int position) {
        final int adapterCount = mAdapters.size();
        int i = 0;
        int count = 0;

        while (i < adapterCount) {
            LocalAdapter a = mAdapters.get(i);
            int newCount = count + a.mAdapter.getItemCount();
            if (position < newCount) {
                a.mLocalPosition = position - count;
                return a;
            }
            count = newCount;
            i++;
        }
        return null;
    }

    private int getAdapterOffsetForAdapter(LocalAdapter t) {
        final int adapterCount = mAdapters.size();
        int i = 0;
        int count = 0;

        while (i < adapterCount) {
            LocalAdapter a = mAdapters.get(i);
            int newCount = count + a.mAdapter.getItemCount();
            if (a == t) {
                return count;
            }
            count = newCount;
            i++;
        }
        return -1;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }


    @Override
    public int getItemViewType(int position) {
        LocalAdapter result = getAdapterOffsetForItem(position);
        int localViewType = result.mAdapter.getItemViewType(result.mLocalPosition);
        if (result.mViewTypesMap.containsValue(localViewType)) {
            for (Map.Entry<Integer, Integer> entry : result.mViewTypesMap.entrySet()) {
                if (entry.getValue() == localViewType) {
                    return entry.getKey();
                }
            }
        }
        mViewTypeIndex += 1;
        result.mViewTypesMap.put(mViewTypeIndex, localViewType);
        return mViewTypeIndex;
    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        for (LocalAdapter adapter : mAdapters) {
            if (adapter.mViewTypesMap.containsKey(viewType))
                return adapter.mAdapter.onCreateViewHolder(viewGroup, adapter.mViewTypesMap.get(viewType));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        LocalAdapter result = getAdapterOffsetForItem(position);
        result.mAdapter.onBindViewHolder(viewHolder, result.mLocalPosition);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, List payloads) {
        LocalAdapter result = getAdapterOffsetForItem(position);
        result.mAdapter.onBindViewHolder(viewHolder, result.mLocalPosition, payloads);
    }

    /**
     * ViewsAdapter, ported from CommonsWare SackOfViews adapter.
     */
    public static class ViewsAdapter extends RecyclerView.Adapter {
        private List<View> views = null;
        private Context context;

        /**
         * Constructor creating an empty list of views, but with
         * a specified count. Subclasses must override newView().
         */
        public ViewsAdapter(Context context, int count) {
            super();
            this.context = context;

            views = new ArrayList<>(count);

            for (int i = 0; i < count; i++) {
                views.add(null);
            }
        }

        /**
         * Constructor wrapping a supplied list of views.
         * Subclasses must override newView() if any of the elements
         * in the list are null.
         */
        public ViewsAdapter(Context context, List<View> views) {
            super();
            this.context = context;

            this.views = views;
        }

        /**
         * How many items are in the data set represented by this
         * Adapter.
         */
        @Override
        public int getItemCount() {
            return (views.size());
        }

        /**
         * Get the type of View that will be created by getView()
         * for the specified item.
         *
         * @param position Position of the item whose data we want
         */
        @Override
        public int getItemViewType(int position) {
            return (position);
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
            //view type is equal to the position in this adapter.
            ViewsViewHolder holder = new ViewsViewHolder(views.get(viewType));
            return holder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        }

        /**
         * Get the row id associated with the specified position
         * in the list.
         *
         * @param position Position of the item whose data we want
         */
        @Override
        public long getItemId(int position) {
            return (position);
        }

        public boolean hasView(View v) {
            return (views.contains(v));
        }

        /**
         * Create a new View to go into the list at the specified
         * position.
         *
         * @param position Position of the item whose data we want
         * @param parent   ViewGroup containing the returned View
         */
        protected View newView(int position, ViewGroup parent) {
            throw new RuntimeException("You must override newView()!");
        }
    }

    public static class ViewsViewHolder extends RecyclerView.ViewHolder {
        public ViewsViewHolder(View itemView) {
            super(itemView);
        }
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        for (LocalAdapter adapter : mAdapters) {
            adapter.mAdapter.onAttachedToRecyclerView(recyclerView);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        for (LocalAdapter adapter : mAdapters) {
            adapter.mAdapter.onDetachedFromRecyclerView(recyclerView);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        super.onViewRecycled(holder);
        LocalAdapter la = getAdapterOffsetForItem(holder.getAdapterPosition());
        if (la != null) {
            la.mAdapter.onViewRecycled(holder);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
        LocalAdapter la = getAdapterOffsetForItem(holder.getAdapterPosition());
        if (la != null) {
            return la.mAdapter.onFailedToRecycleView(holder);
        }
        return super.onFailedToRecycleView(holder);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        LocalAdapter la = getAdapterOffsetForItem(holder.getAdapterPosition());
        if (la != null) {
            la.mAdapter.onViewAttachedToWindow(holder);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        LocalAdapter la = getAdapterOffsetForItem(holder.getAdapterPosition());
        if (la != null) {
            la.mAdapter.onViewDetachedFromWindow(holder);
        }
    }
}
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.TextView;
import net.cattaka.android.adaptertoolbox.adapter.ScrambleAdapter;
import net.cattaka.android.adaptertoolbox.adapter.listener.ListenerRelay;
import net.cattaka.android.adaptertoolbox.example.adapter.SimpleStringAdapter;
import net.cattaka.android.adaptertoolbox.example.adapter.factory.MyInfoViewHolderFactory;
import net.cattaka.android.adaptertoolbox.example.adapter.factory.TextInfoViewHolderFactory;
import net.cattaka.android.adaptertoolbox.example.data.MyInfo;
import net.cattaka.android.adaptertoolbox.example.data.OrdinalLabel;
import net.cattaka.android.adaptertoolbox.example.data.TextInfo;
import net.cattaka.android.adaptertoolbox.example.logic.SnackbarLogic;
import net.cattaka.android.adaptertoolbox.thirdparty.MergeRecyclerAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
package net.cattaka.android.adaptertoolbox.example;




/**
 * Created by cattaka on 16/05/15.
 */
public class ManipulableListExampleActivity extends AppCompatActivity {
    ListenerRelay<ScrambleAdapter<?>, RecyclerView.ViewHolder> mListenerRelay = new ListenerRelay<ScrambleAdapter<?>, RecyclerView.ViewHolder>() {
        @Override
        public void onCheckedChanged(@NonNull RecyclerView recyclerView, @NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull CompoundButton buttonView, boolean isChecked) {
            MergeRecyclerAdapter.LocalAdapter la = mMergeRecyclerAdapter.getAdapterOffsetForItem(viewHolder.getAdapterPosition());
            if (la.mAdapter == mItemAdapter) {
                if (viewHolder instanceof MyInfoViewHolderFactory.ViewHolder) {
                    MyInfo item = (MyInfo) mItemAdapter.getItemAt(la.mLocalPosition);
                    item.setChecked(isChecked);
                }
            }
        }

        @Override
        public void onProgressChanged(@NonNull RecyclerView recyclerView, @NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull SeekBar seekBar, int progress, boolean fromUser) {
            MergeRecyclerAdapter.LocalAdapter la = mMergeRecyclerAdapter.getAdapterOffsetForItem(viewHolder.getAdapterPosition());
            if (la.mAdapter == mItemAdapter) {
                if (viewHolder instanceof MyInfoViewHolderFactory.ViewHolder) {
                    MyInfo item = (MyInfo) mItemAdapter.getItemAt(la.mLocalPosition);
                    item.setIntValue(progress);
                }
            }
        }

        @Override
        public void onItemSelected(@NonNull RecyclerView recyclerView, @NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull AdapterView<?> parent, @NonNull View view, int position, long id) {
            MergeRecyclerAdapter.LocalAdapter la = mMergeRecyclerAdapter.getAdapterOffsetForItem(viewHolder.getAdapterPosition());
            if (la.mAdapter == mItemAdapter) {
                if (viewHolder instanceof MyInfoViewHolderFactory.ViewHolder) {
                    MyInfo item = (MyInfo) mItemAdapter.getItemAt(la.mLocalPosition);
                    item.setOrdinalLabel((OrdinalLabel) parent.getItemAtPosition(position));
                }
            }
        }

        @Override
        public void afterTextChanged(@NonNull RecyclerView recyclerView, @NonNull ScrambleAdapter<?> adapter, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull TextView v, @NonNull Editable s) {
            MergeRecyclerAdapter.LocalAdapter la = mMergeRecyclerAdapter.getAdapterOffsetForItem(viewHolder.getAdapterPosition());
            if (la.mAdapter == mItemAdapter) {
                if (viewHolder instanceof TextInfoViewHolderFactory.ViewHolder) {
                    TextInfo item = (TextInfo) mItemAdapter.getItemAt(la.mLocalPosition);