/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.example.android.supportv7.widget;

import com.example.android.supportv7.R;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class AnimatedRecyclerView extends Activity {

    private static final int SCROLL_DISTANCE = 80; // dp

    private RecyclerView mRecyclerView;

    private int mNumItemsAdded = 0;
    ArrayList<String> mItems = new ArrayList<String>();
    MyAdapter mAdapter;

    boolean mAnimationsEnabled = true;
    boolean mPredictiveAnimationsEnabled = true;
    RecyclerView.ItemAnimator mCachedAnimator = null;
    boolean mEnableInPlaceChange = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.animated_recycler_view);

        ViewGroup container = (ViewGroup) findViewById(R.id.container);
        mRecyclerView = new RecyclerView(this);
        mCachedAnimator = createAnimator();
        mCachedAnimator.setChangeDuration(2000);
        mRecyclerView.setItemAnimator(mCachedAnimator);
        mRecyclerView.setLayoutManager(new MyLayoutManager(this));
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        for (int i = 0; i < 6; ++i) {
            mItems.add("Item #" + i);
        }
        mAdapter = new MyAdapter(mItems);
        mRecyclerView.setAdapter(mAdapter);
        container.addView(mRecyclerView);

        CheckBox enableAnimations = (CheckBox) findViewById(R.id.enableAnimations);
        enableAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked && mRecyclerView.getItemAnimator() == null) {
                    mRecyclerView.setItemAnimator(mCachedAnimator);
                } else if (!isChecked && mRecyclerView.getItemAnimator() != null) {
                    mRecyclerView.setItemAnimator(null);
                }
                mAnimationsEnabled = isChecked;
            }
        });

        CheckBox enablePredictiveAnimations =
                (CheckBox) findViewById(R.id.enablePredictiveAnimations);
        enablePredictiveAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mPredictiveAnimationsEnabled = isChecked;
            }
        });

        CheckBox enableInPlaceChange = (CheckBox) findViewById(R.id.enableInPlaceChange);
        enableInPlaceChange.setChecked(mEnableInPlaceChange);
        enableInPlaceChange.setOnCheckedChangeListener(
                new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        mEnableInPlaceChange = isChecked;
                    }
                });
    }

    private RecyclerView.ItemAnimator createAnimator() {
        return new DefaultItemAnimator() {
            List<ItemChangeAnimator> mPendingChangeAnimations = new ArrayList<>();
            ArrayMap<RecyclerView.ViewHolder, ItemChangeAnimator> mRunningAnimations
                    = new ArrayMap<>();
            ArrayMap<MyViewHolder, Long> mPendingSettleList = new ArrayMap<>();

            @Override
            public void runPendingAnimations() {
                super.runPendingAnimations();
                for (ItemChangeAnimator anim : mPendingChangeAnimations) {
                    anim.start();
                    mRunningAnimations.put(anim.mViewHolder, anim);
                }
                mPendingChangeAnimations.clear();
                for (int i = mPendingSettleList.size() - 1; i >=0; i--) {
                    final MyViewHolder vh = mPendingSettleList.keyAt(i);
                    final long duration = mPendingSettleList.valueAt(i);
                    ViewCompat.animate(vh.textView).translationX(0f).alpha(1f)
                            .setDuration(duration).setListener(
                            new ViewPropertyAnimatorListener() {
                                @Override
                                public void onAnimationStart(View view) {
                                    dispatchAnimationStarted(vh);
                                }

                                @Override
                                public void onAnimationEnd(View view) {
                                    ViewCompat.setTranslationX(vh.textView, 0f);
                                    ViewCompat.setAlpha(vh.textView, 1f);
                                    dispatchAnimationFinished(vh);
                                }

                                @Override
                                public void onAnimationCancel(View view) {

                                }
                            }).start();
                }
                mPendingSettleList.clear();
            }

            @Override
            public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
                    RecyclerView.ViewHolder viewHolder,
                    @AdapterChanges int changeFlags, List<Object> payloads) {
                MyItemInfo info = (MyItemInfo) super
                        .recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
                info.text = ((MyViewHolder) viewHolder).textView.getText();
                return info;
            }

            @Override
            public ItemHolderInfo recordPostLayoutInformation(RecyclerView.State state,
                    RecyclerView.ViewHolder viewHolder) {
                MyItemInfo info = (MyItemInfo) super.recordPostLayoutInformation(state, viewHolder);
                info.text = ((MyViewHolder) viewHolder).textView.getText();
                return info;
            }


            @Override
            public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
                return mEnableInPlaceChange;
            }

            @Override
            public void endAnimation(RecyclerView.ViewHolder item) {
                super.endAnimation(item);
                for (int i = mPendingChangeAnimations.size() - 1; i >= 0; i--) {
                    ItemChangeAnimator anim = mPendingChangeAnimations.get(i);
                    if (anim.mViewHolder == item) {
                        mPendingChangeAnimations.remove(i);
                        anim.setFraction(1f);
                        dispatchChangeFinished(item, true);
                    }
                }
                for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
                    ItemChangeAnimator animator = mRunningAnimations.get(item);
                    if (animator != null) {
                        animator.end();
                        mRunningAnimations.removeAt(i);
                    }
                }
                for (int  i = mPendingSettleList.size() - 1; i >= 0; i--) {
                    final MyViewHolder vh = mPendingSettleList.keyAt(i);
                    if (vh == item) {
                        mPendingSettleList.removeAt(i);
                        dispatchChangeFinished(item, true);
                    }
                }
            }

            @Override
            public boolean animateChange(RecyclerView.ViewHolder oldHolder,
                    RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
                    ItemHolderInfo postInfo) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1
                        || oldHolder != newHolder) {
                    return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
                }
                return animateChangeApiHoneycombMr1(oldHolder, newHolder, preInfo, postInfo);
            }

            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
            private boolean animateChangeApiHoneycombMr1(RecyclerView.ViewHolder oldHolder,
                    RecyclerView.ViewHolder newHolder,
                    ItemHolderInfo preInfo, ItemHolderInfo postInfo) {
                endAnimation(oldHolder);
                MyItemInfo pre = (MyItemInfo) preInfo;
                MyItemInfo post = (MyItemInfo) postInfo;
                MyViewHolder vh = (MyViewHolder) oldHolder;

                CharSequence finalText = post.text;

                if (pre.text.equals(post.text)) {
                    // same content. Just translate back to 0
                    final long duration = (long) (getChangeDuration()
                            * (ViewCompat.getTranslationX(vh.textView) / vh.textView.getWidth()));
                    mPendingSettleList.put(vh, duration);
                    // we set it here because previous endAnimation would set it to other value.
                    vh.textView.setText(finalText);
                } else {
                    // different content, get out and come back.
                    vh.textView.setText(pre.text);
                    final ItemChangeAnimator anim = new ItemChangeAnimator(vh, finalText,
                            getChangeDuration()) {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            setFraction(1f);
                            dispatchChangeFinished(mViewHolder, true);
                        }

                        @Override
                        public void onAnimationStart(Animator animation) {
                            dispatchChangeStarting(mViewHolder, true);
                        }
                    };
                    mPendingChangeAnimations.add(anim);
                }
                return true;
            }

            @Override
            public ItemHolderInfo obtainHolderInfo() {
                return new MyItemInfo();
            }
        };
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
    abstract private static class ItemChangeAnimator implements
            ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
        CharSequence mFinalText;
        ValueAnimator mValueAnimator;
        MyViewHolder mViewHolder;
        final float mMaxX;
        final float mStartRatio;
        public ItemChangeAnimator(MyViewHolder viewHolder, CharSequence finalText, long duration) {
            mViewHolder = viewHolder;
            mMaxX = mViewHolder.itemView.getWidth();
            mStartRatio = ViewCompat.getTranslationX(mViewHolder.textView) / mMaxX;
            mFinalText = finalText;
            mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);
            mValueAnimator.setDuration(duration);
            mValueAnimator.setTarget(mViewHolder.itemView);
        }

        void setFraction(float fraction) {
            fraction = mStartRatio + (1f - mStartRatio) * fraction;
            if (fraction < .5f) {
                ViewCompat.setTranslationX(mViewHolder.textView, fraction * mMaxX);
                ViewCompat.setAlpha(mViewHolder.textView, 1f - fraction);
            } else {
                ViewCompat.setTranslationX(mViewHolder.textView, (1f - fraction) * mMaxX);
                ViewCompat.setAlpha(mViewHolder.textView, fraction);
                maybeSetFinalText();
            }
        }

        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            setFraction(valueAnimator.getAnimatedFraction());
        }

        public void start() {
            mValueAnimator.start();
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            maybeSetFinalText();
            ViewCompat.setAlpha(mViewHolder.textView, 1f);
        }

        public void maybeSetFinalText() {
            if (mFinalText != null) {
                mViewHolder.textView.setText(mFinalText);
                mFinalText = null;
            }
        }

        public void end() {
            mValueAnimator.cancel();
        }

        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationCancel(Animator animation) {
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }
    }

    private static class MyItemInfo extends DefaultItemAnimator.ItemHolderInfo {
        CharSequence text;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        mRecyclerView.requestLayout();
        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("unused")
    public void checkboxClicked(View view) {
        ViewGroup parent = (ViewGroup) view.getParent();
        boolean selected = ((CheckBox) view).isChecked();
        MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
        mAdapter.selectItem(holder, selected);
    }

    @SuppressWarnings("unused")
    public void itemClicked(View view) {
        ViewGroup parent = (ViewGroup) view;
        MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
        final int position = holder.getAdapterPosition();
        if (position == RecyclerView.NO_POSITION) {
            return;
        }
        mAdapter.toggleExpanded(holder);
        mAdapter.notifyItemChanged(position);
    }

    public void deleteSelectedItems(View view) {
        int numItems = mItems.size();
        if (numItems > 0) {
            for (int i = numItems - 1; i >= 0; --i) {
                final String itemText = mItems.get(i);
                boolean selected = mAdapter.mSelected.get(itemText);
                if (selected) {
                    removeAtPosition(i);
                }
            }
        }
    }

    private String generateNewText() {
        return "Added Item #" + mNumItemsAdded++;
    }

    public void d1a2d3(View view) {
        removeAtPosition(1);
        addAtPosition(2, "Added Item #" + mNumItemsAdded++);
        removeAtPosition(3);
    }

    private void removeAtPosition(int position) {
        if(position < mItems.size()) {
            mItems.remove(position);
            mAdapter.notifyItemRemoved(position);
        }
    }

    private void addAtPosition(int position, String text) {
        if (position > mItems.size()) {
            position = mItems.size();
        }
        mItems.add(position, text);
        mAdapter.mSelected.put(text, Boolean.FALSE);
        mAdapter.mExpanded.put(text, Boolean.FALSE);
        mAdapter.notifyItemInserted(position);
    }

    public void addDeleteItem(View view) {
        addItem(view);
        deleteSelectedItems(view);
    }

    public void deleteAddItem(View view) {
        deleteSelectedItems(view);
        addItem(view);
    }

    public void addItem(View view) {
        addAtPosition(3, "Added Item #" + mNumItemsAdded++);
    }

    /**
     * A basic ListView-style LayoutManager.
     */
    class MyLayoutManager extends RecyclerView.LayoutManager {
        private static final String TAG = "MyLayoutManager";
        private int mFirstPosition;
        private final int mScrollDistance;

        public MyLayoutManager(Context c) {
            final DisplayMetrics dm = c.getResources().getDisplayMetrics();
            mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
        }

        @Override
        public boolean supportsPredictiveItemAnimations() {
            return mPredictiveAnimationsEnabled;
        }

        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            int parentBottom = getHeight() - getPaddingBottom();

            final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null;
            int oldTop = getPaddingTop();
            if (oldTopView != null) {
                oldTop = Math.min(oldTopView.getTop(), oldTop);
            }

            // Note that we add everything to the scrap, but we do not clean it up;
            // that is handled by the RecyclerView after this method returns
            detachAndScrapAttachedViews(recycler);

            int top = oldTop;
            int bottom = top;
            final int left = getPaddingLeft();
            final int right = getWidth() - getPaddingRight();

            int count = state.getItemCount();
            for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
                View v = recycler.getViewForPosition(mFirstPosition + i);

                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) v.getLayoutParams();
                addView(v);
                measureChild(v, 0, 0);
                bottom = top + v.getMeasuredHeight();
                v.layout(left, top, right, bottom);
                if (mPredictiveAnimationsEnabled && params.isItemRemoved()) {
                    parentBottom += v.getHeight();
                }
            }

            if (mAnimationsEnabled && mPredictiveAnimationsEnabled && !state.isPreLayout()) {
                // Now that we've run a full layout, figure out which views were not used
                // (cached in previousViews). For each of these views, position it where
                // it would go, according to its position relative to the visible
                // positions in the list. This information will be used by RecyclerView to
                // record post-layout positions of these items for the purposes of animating them
                // out of view

                View lastVisibleView = getChildAt(getChildCount() - 1);
                if (lastVisibleView != null) {
                    RecyclerView.LayoutParams lastParams =
                            (RecyclerView.LayoutParams) lastVisibleView.getLayoutParams();
                    int lastPosition = lastParams.getViewLayoutPosition();
                    final List<RecyclerView.ViewHolder> previousViews = recycler.getScrapList();
                    count = previousViews.size();
                    for (int i = 0; i < count; ++i) {
                        View view = previousViews.get(i).itemView;
                        RecyclerView.LayoutParams params =
                                (RecyclerView.LayoutParams) view.getLayoutParams();
                        if (params.isItemRemoved()) {
                            continue;
                        }
                        int position = params.getViewLayoutPosition();
                        int newTop;
                        if (position < mFirstPosition) {
                            newTop = view.getHeight() * (position - mFirstPosition);
                        } else {
                            newTop = lastVisibleView.getTop() + view.getHeight() *
                                    (position - lastPosition);
                        }
                        view.offsetTopAndBottom(newTop - view.getTop());
                    }
                }
            }
        }

        @Override
        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        @Override
        public boolean canScrollVertically() {
            return true;
        }

        @Override
        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                RecyclerView.State state) {
            if (getChildCount() == 0) {
                return 0;
            }

            int scrolled = 0;
            final int left = getPaddingLeft();
            final int right = getWidth() - getPaddingRight();
            if (dy < 0) {
                while (scrolled > dy) {
                    final View topView = getChildAt(0);
                    final int hangingTop = Math.max(-topView.getTop(), 0);
                    final int scrollBy = Math.min(scrolled - dy, hangingTop);
                    scrolled -= scrollBy;
                    offsetChildrenVertical(scrollBy);
                    if (mFirstPosition > 0 && scrolled > dy) {
                        mFirstPosition--;
                        View v = recycler.getViewForPosition(mFirstPosition);
                        addView(v, 0);
                        measureChild(v, 0, 0);
                        final int bottom = topView.getTop(); // TODO decorated top?
                        final int top = bottom - v.getMeasuredHeight();
                        v.layout(left, top, right, bottom);
                    } else {
                        break;
                    }
                }
            } else if (dy > 0) {
                final int parentHeight = getHeight();
                while (scrolled < dy) {
                    final View bottomView = getChildAt(getChildCount() - 1);
                    final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0);
                    final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
                    scrolled -= scrollBy;
                    offsetChildrenVertical(scrollBy);
                    if (scrolled < dy && state.getItemCount() > mFirstPosition + getChildCount()) {
                        View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
                        final int top = getChildAt(getChildCount() - 1).getBottom();
                        addView(v);
                        measureChild(v, 0, 0);
                        final int bottom = top + v.getMeasuredHeight();
                        v.layout(left, top, right, bottom);
                    } else {
                        break;
                    }
                }
            }
            recycleViewsOutOfBounds(recycler);
            return scrolled;
        }

        @Override
        public View onFocusSearchFailed(View focused, int direction,
                RecyclerView.Recycler recycler, RecyclerView.State state) {
            final int oldCount = getChildCount();

            if (oldCount == 0) {
                return null;
            }

            final int left = getPaddingLeft();
            final int right = getWidth() - getPaddingRight();

            View toFocus = null;
            int newViewsHeight = 0;
            if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
                while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
                    mFirstPosition--;
                    View v = recycler.getViewForPosition(mFirstPosition);
                    final int bottom = getChildAt(0).getTop(); // TODO decorated top?
                    addView(v, 0);
                    measureChild(v, 0, 0);
                    final int top = bottom - v.getMeasuredHeight();
                    v.layout(left, top, right, bottom);
                    if (v.isFocusable()) {
                        toFocus = v;
                        break;
                    }
                }
            }
            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
                while (mFirstPosition + getChildCount() < state.getItemCount() &&
                        newViewsHeight < mScrollDistance) {
                    View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
                    final int top = getChildAt(getChildCount() - 1).getBottom();
                    addView(v);
                    measureChild(v, 0, 0);
                    final int bottom = top + v.getMeasuredHeight();
                    v.layout(left, top, right, bottom);
                    if (v.isFocusable()) {
                        toFocus = v;
                        break;
                    }
                }
            }

            return toFocus;
        }

        public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
            final int childCount = getChildCount();
            final int parentWidth = getWidth();
            final int parentHeight = getHeight();
            boolean foundFirst = false;
            int first = 0;
            int last = 0;
            for (int i = 0; i < childCount; i++) {
                final View v = getChildAt(i);
                if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
                        v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
                    if (!foundFirst) {
                        first = i;
                        foundFirst = true;
                    }
                    last = i;
                }
            }
            for (int i = childCount - 1; i > last; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
            for (int i = first - 1; i >= 0; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
            if (getChildCount() == 0) {
                mFirstPosition = 0;
            } else {
                mFirstPosition += first;
            }
        }

        @Override
        public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
            if (positionStart < mFirstPosition) {
                mFirstPosition += itemCount;
            }
        }

        @Override
        public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
            if (positionStart < mFirstPosition) {
                mFirstPosition -= itemCount;
            }
        }
    }

    class MyAdapter extends RecyclerView.Adapter {
        private int mBackground;
        List<String> mData;
        ArrayMap<String, Boolean> mSelected = new ArrayMap<String, Boolean>();
        ArrayMap<String, Boolean> mExpanded = new ArrayMap<String, Boolean>();

        public MyAdapter(List<String> data) {
            TypedValue val = new TypedValue();
            AnimatedRecyclerView.this.getTheme().resolveAttribute(
                    R.attr.selectableItemBackground, val, true);
            mBackground = val.resourceId;
            mData = data;
            for (String itemText : mData) {
                mSelected.put(itemText, Boolean.FALSE);
                mExpanded.put(itemText, Boolean.FALSE);
            }
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            MyViewHolder h = new MyViewHolder(getLayoutInflater().inflate(R.layout.selectable_item,
                    null));
            h.textView.setMinimumHeight(128);
            h.textView.setFocusable(true);
            h.textView.setBackgroundResource(mBackground);
            return h;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            String itemText = mData.get(position);
            MyViewHolder myViewHolder = (MyViewHolder) holder;
            myViewHolder.boundText = itemText;
            myViewHolder.textView.setText(itemText);
            boolean selected = false;
            if (mSelected.get(itemText) != null) {
                selected = mSelected.get(itemText);
            }
            myViewHolder.checkBox.setChecked(selected);
            Boolean expanded = mExpanded.get(itemText);
            if (Boolean.TRUE.equals(expanded)) {
                myViewHolder.textView.setText("More text for the expanded version");
            } else {
                myViewHolder.textView.setText(itemText);
            }
        }

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

        public void selectItem(MyViewHolder holder, boolean selected) {
            mSelected.put(holder.boundText, selected);
        }

        public void toggleExpanded(MyViewHolder holder) {
            mExpanded.put(holder.boundText, !mExpanded.get(holder.boundText));
        }
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;
        public CheckBox checkBox;
        public String boundText;

        public MyViewHolder(View v) {
            super(v);
            textView = (TextView) v.findViewById(R.id.text);
            checkBox = (CheckBox) v.findViewById(R.id.selected);
        }

        @Override
        public String toString() {
            return super.toString() + " \"" + textView.getText() + "\"";
        }
    }
}
