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