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 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 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; 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 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 299 public void onAnimationUpdate(ValueAnimator valueAnimator) { 300 setFraction(valueAnimator.getAnimatedFraction()); 301 } 302 303 public void start() { 304 mValueAnimator.start(); 305 } 306 307 @Override 308 public void onAnimationEnd(Animator animation) { 309 maybeSetFinalText(); 310 ViewCompat.setAlpha(mViewHolder.textView, 1f); 311 } 312 313 public void maybeSetFinalText() { 314 if (mFinalText != null) { 315 mViewHolder.textView.setText(mFinalText); 316 mFinalText = null; 317 } 318 } 319 320 public void end() { 321 mValueAnimator.cancel(); 322 } 323 324 @Override 325 public void onAnimationStart(Animator animation) { 326 } 327 328 @Override 329 public void onAnimationCancel(Animator animation) { 330 } 331 332 @Override 333 public void onAnimationRepeat(Animator animation) { 334 } 335 } 336 337 private static class MyItemInfo extends DefaultItemAnimator.ItemHolderInfo { 338 CharSequence text; 339 } 340 341 @Override 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 349 public boolean onOptionsItemSelected(MenuItem item) { 350 mRecyclerView.requestLayout(); 351 return super.onOptionsItemSelected(item); 352 } 353 354 @SuppressWarnings("unused") 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") 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 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 387 private String generateNewText() { 388 return "Added Item #" + mNumItemsAdded++; 389 } 390 391 public void d1a2d3(View view) { 392 removeAtPosition(1); 393 addAtPosition(2, "Added Item #" + mNumItemsAdded++); 394 removeAtPosition(3); 395 } 396 397 private void removeAtPosition(int position) { 398 if(position < mItems.size()) { 399 mItems.remove(position); 400 mAdapter.notifyItemRemoved(position); 401 } 402 } 403 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 414 public void addDeleteItem(View view) { 415 addItem(view); 416 deleteSelectedItems(view); 417 } 418 419 public void deleteAddItem(View view) { 420 deleteSelectedItems(view); 421 addItem(view); 422 } 423 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 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 442 public boolean supportsPredictiveItemAnimations() { 443 return mPredictiveAnimationsEnabled; 444 } 445 446 @Override 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 516 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 517 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 518 ViewGroup.LayoutParams.WRAP_CONTENT); 519 } 520 521 @Override 522 public boolean canScrollVertically() { 523 return true; 524 } 525 526 @Override 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 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 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 659 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 660 if (positionStart < mFirstPosition) { 661 mFirstPosition += itemCount; 662 } 663 } 664 665 @Override 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 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 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 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 721 public int getItemCount() { 722 return mData.size(); 723 } 724 725 public void selectItem(MyViewHolder holder, boolean selected) { 726 mSelected.put(holder.boundText, selected); 727 } 728 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 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 746 public String toString() { 747 return super.toString() + " \"" + textView.getText() + "\""; 748 } 749 } 750 } 751