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