1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.example.android.supportv7.widget;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.ValueAnimator;
21import android.annotation.TargetApi;
22import android.app.Activity;
23import android.content.Context;
24import android.os.Build;
25import android.os.Bundle;
26import android.support.v4.util.ArrayMap;
27import android.support.v7.widget.DefaultItemAnimator;
28import android.support.v7.widget.RecyclerView;
29import android.util.DisplayMetrics;
30import android.util.TypedValue;
31import android.view.Menu;
32import android.view.MenuItem;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.CheckBox;
36import android.widget.CompoundButton;
37import android.widget.TextView;
38
39import com.example.android.supportv7.R;
40
41import java.util.ArrayList;
42import java.util.List;
43
44public class AnimatedRecyclerView extends Activity {
45
46    private static final int SCROLL_DISTANCE = 80; // dp
47
48    private RecyclerView mRecyclerView;
49
50    private int mNumItemsAdded = 0;
51    ArrayList<String> mItems = new ArrayList<String>();
52    MyAdapter mAdapter;
53
54    boolean mAnimationsEnabled = true;
55    boolean mPredictiveAnimationsEnabled = true;
56    RecyclerView.ItemAnimator mCachedAnimator = null;
57    boolean mEnableInPlaceChange = true;
58
59    @Override
60    protected void onCreate(Bundle savedInstanceState) {
61        super.onCreate(savedInstanceState);
62        setContentView(R.layout.animated_recycler_view);
63
64        ViewGroup container = findViewById(R.id.container);
65        mRecyclerView = new RecyclerView(this);
66        mCachedAnimator = createAnimator();
67        mCachedAnimator.setChangeDuration(2000);
68        mRecyclerView.setItemAnimator(mCachedAnimator);
69        mRecyclerView.setLayoutManager(new MyLayoutManager(this));
70        mRecyclerView.setHasFixedSize(true);
71        mRecyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
72                ViewGroup.LayoutParams.MATCH_PARENT));
73        for (int i = 0; i < 6; ++i) {
74            mItems.add("Item #" + i);
75        }
76        mAdapter = new MyAdapter(mItems);
77        mRecyclerView.setAdapter(mAdapter);
78        container.addView(mRecyclerView);
79
80        CheckBox enableAnimations = findViewById(R.id.enableAnimations);
81        enableAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
82            @Override
83            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
84                if (isChecked && mRecyclerView.getItemAnimator() == null) {
85                    mRecyclerView.setItemAnimator(mCachedAnimator);
86                } else if (!isChecked && mRecyclerView.getItemAnimator() != null) {
87                    mRecyclerView.setItemAnimator(null);
88                }
89                mAnimationsEnabled = isChecked;
90            }
91        });
92
93        CheckBox enablePredictiveAnimations =
94                findViewById(R.id.enablePredictiveAnimations);
95        enablePredictiveAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
96            @Override
97            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
98                mPredictiveAnimationsEnabled = isChecked;
99            }
100        });
101
102        CheckBox enableInPlaceChange = findViewById(R.id.enableInPlaceChange);
103        enableInPlaceChange.setChecked(mEnableInPlaceChange);
104        enableInPlaceChange.setOnCheckedChangeListener(
105                new CompoundButton.OnCheckedChangeListener() {
106                    @Override
107                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
108                        mEnableInPlaceChange = isChecked;
109                    }
110                });
111    }
112
113    private RecyclerView.ItemAnimator createAnimator() {
114        return new DefaultItemAnimator() {
115            List<ItemChangeAnimator> mPendingChangeAnimations = new ArrayList<>();
116            ArrayMap<RecyclerView.ViewHolder, ItemChangeAnimator> mRunningAnimations
117                    = new ArrayMap<>();
118            ArrayMap<MyViewHolder, Long> mPendingSettleList = new ArrayMap<>();
119
120            @Override
121            public void runPendingAnimations() {
122                super.runPendingAnimations();
123                for (ItemChangeAnimator anim : mPendingChangeAnimations) {
124                    anim.start();
125                    mRunningAnimations.put(anim.mViewHolder, anim);
126                }
127                mPendingChangeAnimations.clear();
128                for (int i = mPendingSettleList.size() - 1; i >=0; i--) {
129                    final MyViewHolder vh = mPendingSettleList.keyAt(i);
130                    final long duration = mPendingSettleList.valueAt(i);
131                    vh.textView.animate().translationX(0f).alpha(1f)
132                            .setDuration(duration).setListener(
133                                    new AnimatorListenerAdapter() {
134                                        @Override
135                                        public void onAnimationStart(Animator animator) {
136                                            dispatchAnimationStarted(vh);
137                                        }
138
139                                        @Override
140                                        public void onAnimationEnd(Animator animator) {
141                                            vh.textView.setTranslationX(0f);
142                                            vh.textView.setAlpha(1f);
143                                            dispatchAnimationFinished(vh);
144                                        }
145
146                                        @Override
147                                        public void onAnimationCancel(Animator animator) {
148
149                                        }
150                                    }).start();
151                }
152                mPendingSettleList.clear();
153            }
154
155            @Override
156            public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
157                    RecyclerView.ViewHolder viewHolder,
158                    @AdapterChanges int changeFlags, List<Object> payloads) {
159                MyItemInfo info = (MyItemInfo) super
160                        .recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
161                info.text = ((MyViewHolder) viewHolder).textView.getText();
162                return info;
163            }
164
165            @Override
166            public ItemHolderInfo recordPostLayoutInformation(RecyclerView.State state,
167                    RecyclerView.ViewHolder viewHolder) {
168                MyItemInfo info = (MyItemInfo) super.recordPostLayoutInformation(state, viewHolder);
169                info.text = ((MyViewHolder) viewHolder).textView.getText();
170                return info;
171            }
172
173
174            @Override
175            public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
176                return mEnableInPlaceChange;
177            }
178
179            @Override
180            public void endAnimation(RecyclerView.ViewHolder item) {
181                super.endAnimation(item);
182                for (int i = mPendingChangeAnimations.size() - 1; i >= 0; i--) {
183                    ItemChangeAnimator anim = mPendingChangeAnimations.get(i);
184                    if (anim.mViewHolder == item) {
185                        mPendingChangeAnimations.remove(i);
186                        anim.setFraction(1f);
187                        dispatchChangeFinished(item, true);
188                    }
189                }
190                for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
191                    ItemChangeAnimator animator = mRunningAnimations.get(item);
192                    if (animator != null) {
193                        animator.end();
194                        mRunningAnimations.removeAt(i);
195                    }
196                }
197                for (int  i = mPendingSettleList.size() - 1; i >= 0; i--) {
198                    final MyViewHolder vh = mPendingSettleList.keyAt(i);
199                    if (vh == item) {
200                        mPendingSettleList.removeAt(i);
201                        dispatchChangeFinished(item, true);
202                    }
203                }
204            }
205
206            @Override
207            public boolean animateChange(RecyclerView.ViewHolder oldHolder,
208                    RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
209                    ItemHolderInfo postInfo) {
210                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1
211                        || oldHolder != newHolder) {
212                    return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
213                }
214                return animateChangeApiHoneycombMr1(oldHolder, newHolder, preInfo, postInfo);
215            }
216
217            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
218            private boolean animateChangeApiHoneycombMr1(RecyclerView.ViewHolder oldHolder,
219                    RecyclerView.ViewHolder newHolder,
220                    ItemHolderInfo preInfo, ItemHolderInfo postInfo) {
221                endAnimation(oldHolder);
222                MyItemInfo pre = (MyItemInfo) preInfo;
223                MyItemInfo post = (MyItemInfo) postInfo;
224                MyViewHolder vh = (MyViewHolder) oldHolder;
225
226                CharSequence finalText = post.text;
227
228                if (pre.text.equals(post.text)) {
229                    // same content. Just translate back to 0
230                    final long duration = (long) (getChangeDuration()
231                            * (vh.textView.getTranslationX() / vh.textView.getWidth()));
232                    mPendingSettleList.put(vh, duration);
233                    // we set it here because previous endAnimation would set it to other value.
234                    vh.textView.setText(finalText);
235                } else {
236                    // different content, get out and come back.
237                    vh.textView.setText(pre.text);
238                    final ItemChangeAnimator anim = new ItemChangeAnimator(vh, finalText,
239                            getChangeDuration()) {
240                        @Override
241                        public void onAnimationEnd(Animator animation) {
242                            setFraction(1f);
243                            dispatchChangeFinished(mViewHolder, true);
244                        }
245
246                        @Override
247                        public void onAnimationStart(Animator animation) {
248                            dispatchChangeStarting(mViewHolder, true);
249                        }
250                    };
251                    mPendingChangeAnimations.add(anim);
252                }
253                return true;
254            }
255
256            @Override
257            public ItemHolderInfo obtainHolderInfo() {
258                return new MyItemInfo();
259            }
260        };
261    }
262
263
264    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
265    abstract private static class ItemChangeAnimator implements
266            ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
267        CharSequence mFinalText;
268        ValueAnimator mValueAnimator;
269        MyViewHolder mViewHolder;
270        final float mMaxX;
271        final float mStartRatio;
272        public ItemChangeAnimator(MyViewHolder viewHolder, CharSequence finalText, long duration) {
273            mViewHolder = viewHolder;
274            mMaxX = mViewHolder.itemView.getWidth();
275            mStartRatio = mViewHolder.textView.getTranslationX() / mMaxX;
276            mFinalText = finalText;
277            mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
278            mValueAnimator.addUpdateListener(this);
279            mValueAnimator.addListener(this);
280            mValueAnimator.setDuration(duration);
281            mValueAnimator.setTarget(mViewHolder.itemView);
282        }
283
284        void setFraction(float fraction) {
285            fraction = mStartRatio + (1f - mStartRatio) * fraction;
286            if (fraction < .5f) {
287                mViewHolder.textView.setTranslationX(fraction * mMaxX);
288                mViewHolder.textView.setAlpha(1f - fraction);
289            } else {
290                mViewHolder.textView.setTranslationX((1f - fraction) * mMaxX);
291                mViewHolder.textView.setAlpha(fraction);
292                maybeSetFinalText();
293            }
294        }
295
296        @Override
297        public void onAnimationUpdate(ValueAnimator valueAnimator) {
298            setFraction(valueAnimator.getAnimatedFraction());
299        }
300
301        public void start() {
302            mValueAnimator.start();
303        }
304
305        @Override
306        public void onAnimationEnd(Animator animation) {
307            maybeSetFinalText();
308            mViewHolder.textView.setAlpha(1f);
309        }
310
311        public void maybeSetFinalText() {
312            if (mFinalText != null) {
313                mViewHolder.textView.setText(mFinalText);
314                mFinalText = null;
315            }
316        }
317
318        public void end() {
319            mValueAnimator.cancel();
320        }
321
322        @Override
323        public void onAnimationStart(Animator animation) {
324        }
325
326        @Override
327        public void onAnimationCancel(Animator animation) {
328        }
329
330        @Override
331        public void onAnimationRepeat(Animator animation) {
332        }
333    }
334
335    private static class MyItemInfo extends DefaultItemAnimator.ItemHolderInfo {
336        CharSequence text;
337    }
338
339    @Override
340    public boolean onCreateOptionsMenu(Menu menu) {
341        super.onCreateOptionsMenu(menu);
342        menu.add("Layout").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
343        return true;
344    }
345
346    @Override
347    public boolean onOptionsItemSelected(MenuItem item) {
348        mRecyclerView.requestLayout();
349        return super.onOptionsItemSelected(item);
350    }
351
352    @SuppressWarnings("unused")
353    public void checkboxClicked(View view) {
354        ViewGroup parent = (ViewGroup) view.getParent();
355        boolean selected = ((CheckBox) view).isChecked();
356        MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
357        mAdapter.selectItem(holder, selected);
358    }
359
360    @SuppressWarnings("unused")
361    public void itemClicked(View view) {
362        ViewGroup parent = (ViewGroup) view;
363        MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
364        final int position = holder.getAdapterPosition();
365        if (position == RecyclerView.NO_POSITION) {
366            return;
367        }
368        mAdapter.toggleExpanded(holder);
369        mAdapter.notifyItemChanged(position);
370    }
371
372    public void deleteSelectedItems(View view) {
373        int numItems = mItems.size();
374        if (numItems > 0) {
375            for (int i = numItems - 1; i >= 0; --i) {
376                final String itemText = mItems.get(i);
377                boolean selected = mAdapter.mSelected.get(itemText);
378                if (selected) {
379                    removeAtPosition(i);
380                }
381            }
382        }
383    }
384
385    private String generateNewText() {
386        return "Added Item #" + mNumItemsAdded++;
387    }
388
389    public void d1a2d3(View view) {
390        removeAtPosition(1);
391        addAtPosition(2, "Added Item #" + mNumItemsAdded++);
392        removeAtPosition(3);
393    }
394
395    private void removeAtPosition(int position) {
396        if(position < mItems.size()) {
397            mItems.remove(position);
398            mAdapter.notifyItemRemoved(position);
399        }
400    }
401
402    private void addAtPosition(int position, String text) {
403        if (position > mItems.size()) {
404            position = mItems.size();
405        }
406        mItems.add(position, text);
407        mAdapter.mSelected.put(text, Boolean.FALSE);
408        mAdapter.mExpanded.put(text, Boolean.FALSE);
409        mAdapter.notifyItemInserted(position);
410    }
411
412    public void addDeleteItem(View view) {
413        addItem(view);
414        deleteSelectedItems(view);
415    }
416
417    public void deleteAddItem(View view) {
418        deleteSelectedItems(view);
419        addItem(view);
420    }
421
422    public void addItem(View view) {
423        addAtPosition(3, "Added Item #" + mNumItemsAdded++);
424    }
425
426    /**
427     * A basic ListView-style LayoutManager.
428     */
429    class MyLayoutManager extends RecyclerView.LayoutManager {
430        private static final String TAG = "MyLayoutManager";
431        private int mFirstPosition;
432        private final int mScrollDistance;
433
434        public MyLayoutManager(Context c) {
435            final DisplayMetrics dm = c.getResources().getDisplayMetrics();
436            mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
437        }
438
439        @Override
440        public boolean supportsPredictiveItemAnimations() {
441            return mPredictiveAnimationsEnabled;
442        }
443
444        @Override
445        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
446            int parentBottom = getHeight() - getPaddingBottom();
447
448            final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null;
449            int oldTop = getPaddingTop();
450            if (oldTopView != null) {
451                oldTop = Math.min(oldTopView.getTop(), oldTop);
452            }
453
454            // Note that we add everything to the scrap, but we do not clean it up;
455            // that is handled by the RecyclerView after this method returns
456            detachAndScrapAttachedViews(recycler);
457
458            int top = oldTop;
459            int bottom = top;
460            final int left = getPaddingLeft();
461            final int right = getWidth() - getPaddingRight();
462
463            int count = state.getItemCount();
464            for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
465                View v = recycler.getViewForPosition(mFirstPosition + i);
466
467                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) v.getLayoutParams();
468                addView(v);
469                measureChild(v, 0, 0);
470                bottom = top + v.getMeasuredHeight();
471                v.layout(left, top, right, bottom);
472                if (mPredictiveAnimationsEnabled && params.isItemRemoved()) {
473                    parentBottom += v.getHeight();
474                }
475            }
476
477            if (mAnimationsEnabled && mPredictiveAnimationsEnabled && !state.isPreLayout()) {
478                // Now that we've run a full layout, figure out which views were not used
479                // (cached in previousViews). For each of these views, position it where
480                // it would go, according to its position relative to the visible
481                // positions in the list. This information will be used by RecyclerView to
482                // record post-layout positions of these items for the purposes of animating them
483                // out of view
484
485                View lastVisibleView = getChildAt(getChildCount() - 1);
486                if (lastVisibleView != null) {
487                    RecyclerView.LayoutParams lastParams =
488                            (RecyclerView.LayoutParams) lastVisibleView.getLayoutParams();
489                    int lastPosition = lastParams.getViewLayoutPosition();
490                    final List<RecyclerView.ViewHolder> previousViews = recycler.getScrapList();
491                    count = previousViews.size();
492                    for (int i = 0; i < count; ++i) {
493                        View view = previousViews.get(i).itemView;
494                        RecyclerView.LayoutParams params =
495                                (RecyclerView.LayoutParams) view.getLayoutParams();
496                        if (params.isItemRemoved()) {
497                            continue;
498                        }
499                        int position = params.getViewLayoutPosition();
500                        int newTop;
501                        if (position < mFirstPosition) {
502                            newTop = view.getHeight() * (position - mFirstPosition);
503                        } else {
504                            newTop = lastVisibleView.getTop() + view.getHeight() *
505                                    (position - lastPosition);
506                        }
507                        view.offsetTopAndBottom(newTop - view.getTop());
508                    }
509                }
510            }
511        }
512
513        @Override
514        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
515            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
516                    ViewGroup.LayoutParams.WRAP_CONTENT);
517        }
518
519        @Override
520        public boolean canScrollVertically() {
521            return true;
522        }
523
524        @Override
525        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
526                RecyclerView.State state) {
527            if (getChildCount() == 0) {
528                return 0;
529            }
530
531            int scrolled = 0;
532            final int left = getPaddingLeft();
533            final int right = getWidth() - getPaddingRight();
534            if (dy < 0) {
535                while (scrolled > dy) {
536                    final View topView = getChildAt(0);
537                    final int hangingTop = Math.max(-topView.getTop(), 0);
538                    final int scrollBy = Math.min(scrolled - dy, hangingTop);
539                    scrolled -= scrollBy;
540                    offsetChildrenVertical(scrollBy);
541                    if (mFirstPosition > 0 && scrolled > dy) {
542                        mFirstPosition--;
543                        View v = recycler.getViewForPosition(mFirstPosition);
544                        addView(v, 0);
545                        measureChild(v, 0, 0);
546                        final int bottom = topView.getTop(); // TODO decorated top?
547                        final int top = bottom - v.getMeasuredHeight();
548                        v.layout(left, top, right, bottom);
549                    } else {
550                        break;
551                    }
552                }
553            } else if (dy > 0) {
554                final int parentHeight = getHeight();
555                while (scrolled < dy) {
556                    final View bottomView = getChildAt(getChildCount() - 1);
557                    final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0);
558                    final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
559                    scrolled -= scrollBy;
560                    offsetChildrenVertical(scrollBy);
561                    if (scrolled < dy && state.getItemCount() > mFirstPosition + getChildCount()) {
562                        View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
563                        final int top = getChildAt(getChildCount() - 1).getBottom();
564                        addView(v);
565                        measureChild(v, 0, 0);
566                        final int bottom = top + v.getMeasuredHeight();
567                        v.layout(left, top, right, bottom);
568                    } else {
569                        break;
570                    }
571                }
572            }
573            recycleViewsOutOfBounds(recycler);
574            return scrolled;
575        }
576
577        @Override
578        public View onFocusSearchFailed(View focused, int direction,
579                RecyclerView.Recycler recycler, RecyclerView.State state) {
580            final int oldCount = getChildCount();
581
582            if (oldCount == 0) {
583                return null;
584            }
585
586            final int left = getPaddingLeft();
587            final int right = getWidth() - getPaddingRight();
588
589            View toFocus = null;
590            int newViewsHeight = 0;
591            if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
592                while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
593                    mFirstPosition--;
594                    View v = recycler.getViewForPosition(mFirstPosition);
595                    final int bottom = getChildAt(0).getTop(); // TODO decorated top?
596                    addView(v, 0);
597                    measureChild(v, 0, 0);
598                    final int top = bottom - v.getMeasuredHeight();
599                    v.layout(left, top, right, bottom);
600                    if (v.isFocusable()) {
601                        toFocus = v;
602                        break;
603                    }
604                }
605            }
606            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
607                while (mFirstPosition + getChildCount() < state.getItemCount() &&
608                        newViewsHeight < mScrollDistance) {
609                    View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
610                    final int top = getChildAt(getChildCount() - 1).getBottom();
611                    addView(v);
612                    measureChild(v, 0, 0);
613                    final int bottom = top + v.getMeasuredHeight();
614                    v.layout(left, top, right, bottom);
615                    if (v.isFocusable()) {
616                        toFocus = v;
617                        break;
618                    }
619                }
620            }
621
622            return toFocus;
623        }
624
625        public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
626            final int childCount = getChildCount();
627            final int parentWidth = getWidth();
628            final int parentHeight = getHeight();
629            boolean foundFirst = false;
630            int first = 0;
631            int last = 0;
632            for (int i = 0; i < childCount; i++) {
633                final View v = getChildAt(i);
634                if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
635                        v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
636                    if (!foundFirst) {
637                        first = i;
638                        foundFirst = true;
639                    }
640                    last = i;
641                }
642            }
643            for (int i = childCount - 1; i > last; i--) {
644                removeAndRecycleViewAt(i, recycler);
645            }
646            for (int i = first - 1; i >= 0; i--) {
647                removeAndRecycleViewAt(i, recycler);
648            }
649            if (getChildCount() == 0) {
650                mFirstPosition = 0;
651            } else {
652                mFirstPosition += first;
653            }
654        }
655
656        @Override
657        public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
658            if (positionStart < mFirstPosition) {
659                mFirstPosition += itemCount;
660            }
661        }
662
663        @Override
664        public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
665            if (positionStart < mFirstPosition) {
666                mFirstPosition -= itemCount;
667            }
668        }
669    }
670
671    class MyAdapter extends RecyclerView.Adapter {
672        private int mBackground;
673        List<String> mData;
674        ArrayMap<String, Boolean> mSelected = new ArrayMap<String, Boolean>();
675        ArrayMap<String, Boolean> mExpanded = new ArrayMap<String, Boolean>();
676
677        public MyAdapter(List<String> data) {
678            TypedValue val = new TypedValue();
679            AnimatedRecyclerView.this.getTheme().resolveAttribute(
680                    R.attr.selectableItemBackground, val, true);
681            mBackground = val.resourceId;
682            mData = data;
683            for (String itemText : mData) {
684                mSelected.put(itemText, Boolean.FALSE);
685                mExpanded.put(itemText, Boolean.FALSE);
686            }
687        }
688
689        @Override
690        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
691            MyViewHolder h = new MyViewHolder(getLayoutInflater().inflate(R.layout.selectable_item,
692                    null));
693            h.textView.setMinimumHeight(128);
694            h.textView.setFocusable(true);
695            h.textView.setBackgroundResource(mBackground);
696            return h;
697        }
698
699        @Override
700        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
701            String itemText = mData.get(position);
702            MyViewHolder myViewHolder = (MyViewHolder) holder;
703            myViewHolder.boundText = itemText;
704            myViewHolder.textView.setText(itemText);
705            boolean selected = false;
706            if (mSelected.get(itemText) != null) {
707                selected = mSelected.get(itemText);
708            }
709            myViewHolder.checkBox.setChecked(selected);
710            Boolean expanded = mExpanded.get(itemText);
711            if (Boolean.TRUE.equals(expanded)) {
712                myViewHolder.textView.setText("More text for the expanded version");
713            } else {
714                myViewHolder.textView.setText(itemText);
715            }
716        }
717
718        @Override
719        public int getItemCount() {
720            return mData.size();
721        }
722
723        public void selectItem(MyViewHolder holder, boolean selected) {
724            mSelected.put(holder.boundText, selected);
725        }
726
727        public void toggleExpanded(MyViewHolder holder) {
728            mExpanded.put(holder.boundText, !mExpanded.get(holder.boundText));
729        }
730    }
731
732    static class MyViewHolder extends RecyclerView.ViewHolder {
733        public TextView textView;
734        public CheckBox checkBox;
735        public String boundText;
736
737        public MyViewHolder(View v) {
738            super(v);
739            textView = (TextView) v.findViewById(R.id.text);
740            checkBox = (CheckBox) v.findViewById(R.id.selected);
741        }
742
743        @Override
744        public String toString() {
745            return super.toString() + " \"" + textView.getText() + "\"";
746        }
747    }
748}
749