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.app.Activity; 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.util.DisplayMetrics; 25 import android.util.TypedValue; 26 import android.view.Menu; 27 import android.view.MenuItem; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.CheckBox; 31 import android.widget.CompoundButton; 32 import android.widget.TextView; 33 34 import androidx.collection.ArrayMap; 35 import androidx.recyclerview.widget.DefaultItemAnimator; 36 import androidx.recyclerview.widget.RecyclerView; 37 38 import com.example.android.supportv7.R; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 43 public 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 onCreate(Bundle savedInstanceState)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 createAnimator()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; ItemChangeAnimator(MyViewHolder viewHolder, CharSequence finalText, long duration)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 setFraction(float fraction)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 onAnimationUpdate(ValueAnimator valueAnimator)292 public void onAnimationUpdate(ValueAnimator valueAnimator) { 293 setFraction(valueAnimator.getAnimatedFraction()); 294 } 295 start()296 public void start() { 297 mValueAnimator.start(); 298 } 299 300 @Override onAnimationEnd(Animator animation)301 public void onAnimationEnd(Animator animation) { 302 maybeSetFinalText(); 303 mViewHolder.textView.setAlpha(1f); 304 } 305 maybeSetFinalText()306 public void maybeSetFinalText() { 307 if (mFinalText != null) { 308 mViewHolder.textView.setText(mFinalText); 309 mFinalText = null; 310 } 311 } 312 end()313 public void end() { 314 mValueAnimator.cancel(); 315 } 316 317 @Override onAnimationStart(Animator animation)318 public void onAnimationStart(Animator animation) { 319 } 320 321 @Override onAnimationCancel(Animator animation)322 public void onAnimationCancel(Animator animation) { 323 } 324 325 @Override onAnimationRepeat(Animator animation)326 public void onAnimationRepeat(Animator animation) { 327 } 328 } 329 330 private static class MyItemInfo extends DefaultItemAnimator.ItemHolderInfo { 331 CharSequence text; 332 } 333 334 @Override onCreateOptionsMenu(Menu menu)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 onOptionsItemSelected(MenuItem item)342 public boolean onOptionsItemSelected(MenuItem item) { 343 mRecyclerView.requestLayout(); 344 return super.onOptionsItemSelected(item); 345 } 346 347 @SuppressWarnings("unused") checkboxClicked(View view)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") itemClicked(View view)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 deleteSelectedItems(View view)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 generateNewText()380 private String generateNewText() { 381 return "Added Item #" + mNumItemsAdded++; 382 } 383 d1a2d3(View view)384 public void d1a2d3(View view) { 385 removeAtPosition(1); 386 addAtPosition(2, "Added Item #" + mNumItemsAdded++); 387 removeAtPosition(3); 388 } 389 removeAtPosition(int position)390 private void removeAtPosition(int position) { 391 if(position < mItems.size()) { 392 mItems.remove(position); 393 mAdapter.notifyItemRemoved(position); 394 } 395 } 396 addAtPosition(int position, String text)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 addDeleteItem(View view)407 public void addDeleteItem(View view) { 408 addItem(view); 409 deleteSelectedItems(view); 410 } 411 deleteAddItem(View view)412 public void deleteAddItem(View view) { 413 deleteSelectedItems(view); 414 addItem(view); 415 } 416 addItem(View view)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 MyLayoutManager(Context c)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 supportsPredictiveItemAnimations()435 public boolean supportsPredictiveItemAnimations() { 436 return mPredictiveAnimationsEnabled; 437 } 438 439 @Override onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)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 generateDefaultLayoutParams()509 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 510 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 511 ViewGroup.LayoutParams.WRAP_CONTENT); 512 } 513 514 @Override canScrollVertically()515 public boolean canScrollVertically() { 516 return true; 517 } 518 519 @Override scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)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 onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state)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 recycleViewsOutOfBounds(RecyclerView.Recycler recycler)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 onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)652 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 653 if (positionStart < mFirstPosition) { 654 mFirstPosition += itemCount; 655 } 656 } 657 658 @Override onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)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 MyAdapter(List<String> data)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 onCreateViewHolder(ViewGroup parent, int viewType)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 onBindViewHolder(RecyclerView.ViewHolder holder, int position)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 getItemCount()714 public int getItemCount() { 715 return mData.size(); 716 } 717 selectItem(MyViewHolder holder, boolean selected)718 public void selectItem(MyViewHolder holder, boolean selected) { 719 mSelected.put(holder.boundText, selected); 720 } 721 toggleExpanded(MyViewHolder holder)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 MyViewHolder(View v)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 toString()739 public String toString() { 740 return super.toString() + " \"" + textView.getText() + "\""; 741 } 742 } 743 } 744