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