Home | History | Annotate | Download | only in widget
      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 android.animation.Animator;
     19 import android.animation.AnimatorListenerAdapter;
     20 import android.animation.ValueAnimator;
     21 import android.annotation.TargetApi;
     22 import android.app.Activity;
     23 import android.content.Context;
     24 import android.os.Build;
     25 import android.os.Bundle;
     26 import android.support.v4.util.ArrayMap;
     27 import android.support.v7.widget.DefaultItemAnimator;
     28 import android.support.v7.widget.RecyclerView;
     29 import android.util.DisplayMetrics;
     30 import android.util.TypedValue;
     31 import android.view.Menu;
     32 import android.view.MenuItem;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.widget.CheckBox;
     36 import android.widget.CompoundButton;
     37 import android.widget.TextView;
     38 
     39 import com.example.android.supportv7.R;
     40 
     41 import java.util.ArrayList;
     42 import java.util.List;
     43 
     44 public 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