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