1 /* 2 * Copyright (C) 2015 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 android.support.v7.widget; 17 18 import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_CHANGED; 19 import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_MOVED; 20 import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_REMOVED; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.assertNotSame; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertSame; 28 import static org.junit.Assert.assertThat; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assert.fail; 31 32 import android.support.annotation.NonNull; 33 import android.support.annotation.Nullable; 34 import android.support.test.runner.AndroidJUnit4; 35 import android.test.suitebuilder.annotation.MediumTest; 36 import android.view.View; 37 38 import org.hamcrest.CoreMatchers; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.concurrent.atomic.AtomicInteger; 47 48 /** 49 * Includes tests for the new RecyclerView animations API (v2). 50 */ 51 @MediumTest 52 @RunWith(AndroidJUnit4.class) 53 public class ItemAnimatorV2ApiTest extends BaseRecyclerViewAnimationsTest { 54 @Override 55 protected RecyclerView.ItemAnimator createItemAnimator() { 56 return mAnimator; 57 } 58 59 @Test 60 public void changeMovedOutside() throws Throwable { 61 setupBasic(10); 62 final RecyclerView.ViewHolder target = mRecyclerView.findViewHolderForAdapterPosition(9); 63 mLayoutManager.expectLayouts(2); 64 mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9; 65 mTestAdapter.changeAndNotify(9, 1); 66 mLayoutManager.waitForLayout(2); 67 // changed item should not be laid out and should just receive disappear 68 LoggingInfo pre = mAnimator.preLayoutInfoMap.get(target); 69 assertNotNull("test sanity", pre); 70 assertNull("test sanity", mAnimator.postLayoutInfoMap.get(target)); 71 assertTrue(mAnimator.animateChangeList.isEmpty()); 72 assertEquals(1, mAnimator.animateDisappearanceList.size()); 73 assertEquals(new AnimateDisappearance(target, pre, null), 74 mAnimator.animateDisappearanceList.get(0)); 75 // This is kind of problematic because layout manager will never layout the updated 76 // version of this view since it went out of bounds and it won't show up in scrap. 77 // I don't think we can do much better since other option is to bind a fresh view 78 } 79 80 @Test 81 public void changeMovedOutsideWithPredictiveAndTwoViewHolders() throws Throwable { 82 final RecyclerView.ViewHolder[] targets = new RecyclerView.ViewHolder[2]; 83 84 setupBasic(10, 0, 10, new TestAdapter(10) { 85 @Override 86 public void onBindViewHolder(TestViewHolder holder, 87 int position) { 88 super.onBindViewHolder(holder, position); 89 if (position == 0) { 90 if (targets[0] == null) { 91 targets[0] = holder; 92 } else { 93 assertThat(targets[1], CoreMatchers.nullValue()); 94 targets[1] = holder; 95 } 96 } 97 } 98 }); 99 final RecyclerView.ViewHolder singleItemTarget = 100 mRecyclerView.findViewHolderForAdapterPosition(1); 101 mAnimator.canReUseCallback = new CanReUseCallback() { 102 @Override 103 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 104 return viewHolder == singleItemTarget; 105 } 106 }; 107 mLayoutManager.expectLayouts(2); 108 mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() { 109 @Override 110 void onLayoutChildren(RecyclerView.Recycler recycler, 111 AnimationLayoutManager lm, RecyclerView.State state) { 112 super.onLayoutChildren(recycler, lm, state); 113 if (!state.isPreLayout()) { 114 mLayoutManager.addDisappearingView(recycler.getViewForPosition(0)); 115 mLayoutManager.addDisappearingView(recycler.getScrapList().get(0).itemView); 116 } 117 } 118 }; 119 mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 8; 120 mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 2; 121 mTestAdapter.changeAndNotify(0, 2); 122 mLayoutManager.waitForLayout(2); 123 checkForMainThreadException(); 124 final RecyclerView.ViewHolder oldTarget = targets[0]; 125 final RecyclerView.ViewHolder newTarget = targets[1]; 126 assertNotNull("test sanity", targets[0]); 127 assertNotNull("test sanity", targets[1]); 128 // changed item should not be laid out and should just receive disappear 129 LoggingInfo pre = mAnimator.preLayoutInfoMap.get(oldTarget); 130 assertNotNull("test sanity", pre); 131 assertNull("test sanity", mAnimator.postLayoutInfoMap.get(oldTarget)); 132 133 assertNull("test sanity", mAnimator.preLayoutInfoMap.get(newTarget)); 134 LoggingInfo post = mAnimator.postLayoutInfoMap.get(newTarget); 135 assertNotNull("test sanity", post); 136 assertEquals(1, mAnimator.animateChangeList.size()); 137 assertEquals(1, mAnimator.animateDisappearanceList.size()); 138 139 assertEquals(new AnimateChange(oldTarget, newTarget, pre, post), 140 mAnimator.animateChangeList.get(0)); 141 142 LoggingInfo singleItemPre = mAnimator.preLayoutInfoMap.get(singleItemTarget); 143 assertNotNull("test sanity", singleItemPre); 144 LoggingInfo singleItemPost = mAnimator.postLayoutInfoMap.get(singleItemTarget); 145 assertNotNull("test sanity", singleItemPost); 146 147 assertEquals(new AnimateDisappearance(singleItemTarget, singleItemPre, singleItemPost), 148 mAnimator.animateDisappearanceList.get(0)); 149 } 150 @Test 151 public void changeMovedOutsideWithPredictive() throws Throwable { 152 setupBasic(10); 153 final RecyclerView.ViewHolder target = mRecyclerView.findViewHolderForAdapterPosition(0); 154 mLayoutManager.expectLayouts(2); 155 mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() { 156 @Override 157 void onLayoutChildren(RecyclerView.Recycler recycler, 158 AnimationLayoutManager lm, RecyclerView.State state) { 159 super.onLayoutChildren(recycler, lm, state); 160 List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList(); 161 assertThat(scrapList.size(), CoreMatchers.is(2)); 162 mLayoutManager.addDisappearingView(scrapList.get(0).itemView); 163 mLayoutManager.addDisappearingView(scrapList.get(0).itemView); 164 } 165 }; 166 mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 8; 167 mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 2; 168 mTestAdapter.changeAndNotify(0, 2); 169 mLayoutManager.waitForLayout(2); 170 checkForMainThreadException(); 171 // changed item should not be laid out and should just receive disappear 172 LoggingInfo pre = mAnimator.preLayoutInfoMap.get(target); 173 assertNotNull("test sanity", pre); 174 LoggingInfo postInfo = mAnimator.postLayoutInfoMap.get(target); 175 assertNotNull("test sanity", postInfo); 176 assertTrue(mAnimator.animateChangeList.isEmpty()); 177 assertEquals(2, mAnimator.animateDisappearanceList.size()); 178 try { 179 assertEquals(new AnimateDisappearance(target, pre, postInfo), 180 mAnimator.animateDisappearanceList.get(0)); 181 } catch (Throwable t) { 182 assertEquals(new AnimateDisappearance(target, pre, postInfo), 183 mAnimator.animateDisappearanceList.get(1)); 184 } 185 186 } 187 188 @Test 189 public void simpleAdd() throws Throwable { 190 setupBasic(10); 191 mLayoutManager.expectLayouts(2); 192 mTestAdapter.addAndNotify(2, 1); 193 mLayoutManager.waitForLayout(2); 194 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 195 assertEquals(1, mAnimator.animateAppearanceList.size()); 196 AnimateAppearance log = mAnimator.animateAppearanceList.get(0); 197 assertSame(vh, log.viewHolder); 198 assertNull(log.preInfo); 199 assertEquals(0, log.postInfo.changeFlags); 200 // the first two should not receive anything 201 for (int i = 0; i < 2; i++) { 202 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 203 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 204 } 205 for (int i = 3; i < mTestAdapter.getItemCount(); i++) { 206 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 207 assertEquals(FLAG_MOVED, mAnimator.preLayoutInfoMap.get(other).changeFlags); 208 } 209 checkForMainThreadException(); 210 } 211 212 @Test 213 public void simpleRemove() throws Throwable { 214 setupBasic(10); 215 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 216 mLayoutManager.expectLayouts(2); 217 mTestAdapter.deleteAndNotify(2, 1); 218 mLayoutManager.waitForLayout(2); 219 checkForMainThreadException(); 220 assertEquals(1, mAnimator.animateDisappearanceList.size()); 221 AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0); 222 assertSame(vh, log.viewHolder); 223 assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh)); 224 assertEquals(FLAG_REMOVED, log.preInfo.changeFlags); 225 // the first two should not receive anything 226 for (int i = 0; i < 2; i++) { 227 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 228 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 229 } 230 for (int i = 3; i < mTestAdapter.getItemCount(); i++) { 231 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 232 assertEquals(FLAG_MOVED, mAnimator.preLayoutInfoMap.get(other).changeFlags); 233 } 234 checkForMainThreadException(); 235 } 236 237 @Test 238 public void simpleUpdate() throws Throwable { 239 setupBasic(10); 240 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 241 mLayoutManager.expectLayouts(2); 242 mTestAdapter.changeAndNotify(2, 1); 243 mLayoutManager.waitForLayout(2); 244 assertEquals(1, mAnimator.animateChangeList.size()); 245 AnimateChange log = mAnimator.animateChangeList.get(0); 246 assertSame(vh, log.viewHolder); 247 assertSame(vh, log.newHolder); 248 assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh)); 249 assertTrue(mAnimator.postLayoutInfoMap.containsKey(vh)); 250 assertEquals(FLAG_CHANGED, log.preInfo.changeFlags); 251 assertEquals(0, log.postInfo.changeFlags); 252 //others should not receive anything 253 for (int i = 0; i < mTestAdapter.getItemCount(); i++) { 254 if (i == 2) { 255 continue; 256 } 257 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 258 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 259 } 260 checkForMainThreadException(); 261 } 262 263 @Test 264 public void updateWithDuplicateViewHolder() throws Throwable { 265 setupBasic(10); 266 final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 267 mAnimator.canReUseCallback = new CanReUseCallback() { 268 @Override 269 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 270 assertSame(viewHolder, vh); 271 return false; 272 } 273 }; 274 mLayoutManager.expectLayouts(2); 275 mTestAdapter.changeAndNotify(2, 1); 276 mLayoutManager.waitForLayout(2); 277 final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2); 278 assertNotSame(vh, newVh); 279 assertEquals(1, mAnimator.animateChangeList.size()); 280 AnimateChange log = mAnimator.animateChangeList.get(0); 281 assertSame(vh, log.viewHolder); 282 assertSame(newVh, log.newHolder); 283 assertNull(vh.itemView.getParent()); 284 assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh)); 285 assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh)); 286 assertTrue(mAnimator.postLayoutInfoMap.containsKey(newVh)); 287 assertEquals(FLAG_CHANGED, log.preInfo.changeFlags); 288 assertEquals(0, log.postInfo.changeFlags); 289 //others should not receive anything 290 for (int i = 0; i < mTestAdapter.getItemCount(); i++) { 291 if (i == 2) { 292 continue; 293 } 294 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 295 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 296 } 297 checkForMainThreadException(); 298 } 299 300 @Test 301 public void updateWithOneDuplicateAndOneInPlace() throws Throwable { 302 setupBasic(10); 303 final RecyclerView.ViewHolder replaced = mRecyclerView.findViewHolderForAdapterPosition(2); 304 final RecyclerView.ViewHolder reused = mRecyclerView.findViewHolderForAdapterPosition(3); 305 mAnimator.canReUseCallback = new CanReUseCallback() { 306 @Override 307 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 308 if (viewHolder == replaced) { 309 return false; 310 } else if (viewHolder == reused) { 311 return true; 312 } 313 fail("unpexpected view"); 314 return false; 315 } 316 }; 317 mLayoutManager.expectLayouts(2); 318 mTestAdapter.changeAndNotify(2, 2); 319 mLayoutManager.waitForLayout(2); 320 final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2); 321 322 assertNotSame(replaced, newVh); 323 assertSame(reused, mRecyclerView.findViewHolderForAdapterPosition(3)); 324 325 assertEquals(2, mAnimator.animateChangeList.size()); 326 AnimateChange logReplaced = null, logReused = null; 327 for (AnimateChange change : mAnimator.animateChangeList) { 328 if (change.newHolder == change.viewHolder) { 329 logReused = change; 330 } else { 331 logReplaced = change; 332 } 333 } 334 assertNotNull(logReplaced); 335 assertNotNull(logReused); 336 assertSame(replaced, logReplaced.viewHolder); 337 assertSame(newVh, logReplaced.newHolder); 338 assertSame(reused, logReused.viewHolder); 339 assertSame(reused, logReused.newHolder); 340 341 assertTrue(mAnimator.preLayoutInfoMap.containsKey(replaced)); 342 assertTrue(mAnimator.preLayoutInfoMap.containsKey(reused)); 343 344 assertTrue(mAnimator.postLayoutInfoMap.containsKey(newVh)); 345 assertTrue(mAnimator.postLayoutInfoMap.containsKey(reused)); 346 assertFalse(mAnimator.postLayoutInfoMap.containsKey(replaced)); 347 348 assertEquals(FLAG_CHANGED, logReplaced.preInfo.changeFlags); 349 assertEquals(FLAG_CHANGED, logReused.preInfo.changeFlags); 350 351 assertEquals(0, logReplaced.postInfo.changeFlags); 352 assertEquals(0, logReused.postInfo.changeFlags); 353 //others should not receive anything 354 for (int i = 0; i < mTestAdapter.getItemCount(); i++) { 355 if (i == 2 || i == 3) { 356 continue; 357 } 358 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 359 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 360 } 361 checkForMainThreadException(); 362 } 363 364 @Test 365 public void changeToDisappear() throws Throwable { 366 setupBasic(10); 367 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(9); 368 mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9; 369 mLayoutManager.expectLayouts(2); 370 mTestAdapter.changeAndNotify(9, 1); 371 mLayoutManager.waitForLayout(2); 372 assertEquals(1, mAnimator.animateDisappearanceList.size()); 373 AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0); 374 assertSame(vh, log.viewHolder); 375 assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh)); 376 assertEquals(FLAG_CHANGED, log.preInfo.changeFlags); 377 assertEquals(0, mAnimator.animateChangeList.size()); 378 assertEquals(0, mAnimator.animateAppearanceList.size()); 379 assertEquals(9, mAnimator.animatePersistenceList.size()); 380 checkForMainThreadException(); 381 } 382 383 @Test 384 public void changeToDisappearFromHead() throws Throwable { 385 setupBasic(10); 386 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(0); 387 mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9; 388 mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1; 389 mLayoutManager.expectLayouts(2); 390 mTestAdapter.changeAndNotify(0, 1); 391 mLayoutManager.waitForLayout(2); 392 assertEquals(1, mAnimator.animateDisappearanceList.size()); 393 AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0); 394 assertSame(vh, log.viewHolder); 395 assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh)); 396 assertEquals(FLAG_CHANGED, log.preInfo.changeFlags); 397 assertEquals(0, mAnimator.animateChangeList.size()); 398 assertEquals(0, mAnimator.animateAppearanceList.size()); 399 assertEquals(9, mAnimator.animatePersistenceList.size()); 400 checkForMainThreadException(); 401 } 402 403 @Test 404 public void updatePayload() throws Throwable { 405 setupBasic(10); 406 final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 407 final Object payload = new Object(); 408 mAnimator.canReUseCallback = new CanReUseCallback() { 409 @Override 410 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 411 assertSame(vh, viewHolder); 412 assertEquals(1, payloads.size()); 413 assertSame(payload, payloads.get(0)); 414 return true; 415 } 416 }; 417 mLayoutManager.expectLayouts(2); 418 mTestAdapter.changeAndNotifyWithPayload(2, 1, payload); 419 mLayoutManager.waitForLayout(2); 420 assertEquals(1, mAnimator.animateChangeList.size()); 421 AnimateChange log = mAnimator.animateChangeList.get(0); 422 assertSame(vh, log.viewHolder); 423 assertSame(vh, log.newHolder); 424 assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh)); 425 assertTrue(mAnimator.postLayoutInfoMap.containsKey(vh)); 426 assertEquals(FLAG_CHANGED, log.preInfo.changeFlags); 427 assertEquals(0, log.postInfo.changeFlags); 428 assertNotNull(log.preInfo.payloads); 429 assertTrue(log.preInfo.payloads.contains(payload)); 430 //others should not receive anything 431 for (int i = 0; i < mTestAdapter.getItemCount(); i++) { 432 if (i == 2) { 433 continue; 434 } 435 RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i); 436 assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags); 437 } 438 checkForMainThreadException(); 439 } 440 441 @Test 442 public void notifyDataSetChanged() throws Throwable { 443 TestAdapter adapter = new TestAdapter(10); 444 adapter.setHasStableIds(true); 445 setupBasic(10, 0, 10, adapter); 446 mLayoutManager.expectLayouts(1); 447 mTestAdapter.dispatchDataSetChanged(); 448 mLayoutManager.waitForLayout(2); 449 assertEquals(10, mAnimator.animateChangeList.size()); 450 for (AnimateChange change : mAnimator.animateChangeList) { 451 assertNotNull(change.preInfo); 452 assertNotNull(change.postInfo); 453 assertSame(change.preInfo.viewHolder, change.postInfo.viewHolder); 454 } 455 assertEquals(0, mAnimator.animatePersistenceList.size()); 456 assertEquals(0, mAnimator.animateAppearanceList.size()); 457 assertEquals(0, mAnimator.animateDisappearanceList.size()); 458 } 459 460 @Test 461 public void notifyDataSetChangedWithoutStableIds() throws Throwable { 462 TestAdapter adapter = new TestAdapter(10); 463 adapter.setHasStableIds(false); 464 setupBasic(10, 0, 10, adapter); 465 mLayoutManager.expectLayouts(1); 466 mTestAdapter.dispatchDataSetChanged(); 467 mLayoutManager.waitForLayout(2); 468 assertEquals(0, mAnimator.animateChangeList.size()); 469 assertEquals(0, mAnimator.animatePersistenceList.size()); 470 assertEquals(0, mAnimator.animateAppearanceList.size()); 471 assertEquals(0, mAnimator.animateDisappearanceList.size()); 472 } 473 474 @Test 475 public void notifyDataSetChangedWithAppearing() throws Throwable { 476 notifyDataSetChangedWithAppearing(false); 477 } 478 479 @Test 480 public void notifyDataSetChangedWithAppearingNotifyBoth() throws Throwable { 481 notifyDataSetChangedWithAppearing(true); 482 } 483 484 public void notifyDataSetChangedWithAppearing(final boolean notifyBoth) throws Throwable { 485 final TestAdapter adapter = new TestAdapter(10); 486 adapter.setHasStableIds(true); 487 setupBasic(10, 0, 10, adapter); 488 mLayoutManager.expectLayouts(1); 489 runTestOnUiThread(new Runnable() { 490 @Override 491 public void run() { 492 try { 493 if (notifyBoth) { 494 adapter.addAndNotify(2, 2); 495 } else { 496 adapter.mItems.add(2, new Item(2, "custom 1")); 497 adapter.mItems.add(3, new Item(3, "custom 2")); 498 } 499 500 adapter.notifyDataSetChanged(); 501 } catch (Throwable throwable) { 502 throwable.printStackTrace(); 503 } 504 } 505 }); 506 mLayoutManager.waitForLayout(2); 507 assertEquals(10, mAnimator.animateChangeList.size()); 508 assertEquals(0, mAnimator.animatePersistenceList.size()); 509 assertEquals(2, mAnimator.animateAppearanceList.size()); 510 assertEquals(0, mAnimator.animateDisappearanceList.size()); 511 } 512 513 @Test 514 public void notifyDataSetChangedWithDispappearing() throws Throwable { 515 notifyDataSetChangedWithDispappearing(false); 516 } 517 518 @Test 519 public void notifyDataSetChangedWithDispappearingNotifyBoth() throws Throwable { 520 notifyDataSetChangedWithDispappearing(true); 521 } 522 523 public void notifyDataSetChangedWithDispappearing(final boolean notifyBoth) throws Throwable { 524 final TestAdapter adapter = new TestAdapter(10); 525 adapter.setHasStableIds(true); 526 setupBasic(10, 0, 10, adapter); 527 mLayoutManager.expectLayouts(1); 528 runTestOnUiThread(new Runnable() { 529 @Override 530 public void run() { 531 try { 532 if (notifyBoth) { 533 adapter.deleteAndNotify(2, 2); 534 } else { 535 adapter.mItems.remove(2); 536 adapter.mItems.remove(2); 537 } 538 adapter.notifyDataSetChanged(); 539 } catch (Throwable throwable) { 540 throwable.printStackTrace(); 541 } 542 } 543 }); 544 mLayoutManager.waitForLayout(2); 545 assertEquals(8, mAnimator.animateChangeList.size()); 546 assertEquals(0, mAnimator.animatePersistenceList.size()); 547 assertEquals(0, mAnimator.animateAppearanceList.size()); 548 assertEquals(2, mAnimator.animateDisappearanceList.size()); 549 } 550 551 @Test 552 public void notifyUpdateWithChangedAdapterType() throws Throwable { 553 final AtomicInteger itemType = new AtomicInteger(1); 554 final TestAdapter adapter = new TestAdapter(10) { 555 @Override 556 public int getItemViewType(int position) { 557 return position == 2 ? itemType.get() : 20; 558 } 559 }; 560 adapter.setHasStableIds(true); 561 setupBasic(10, 0, 10, adapter); 562 final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2); 563 564 mAnimator.canReUseCallback = new CanReUseCallback() { 565 @Override 566 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 567 return viewHolder != vh; 568 } 569 }; 570 571 mLayoutManager.expectLayouts(1); 572 itemType.set(3); 573 adapter.dispatchDataSetChanged(); 574 mLayoutManager.waitForLayout(2); 575 final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2); 576 // TODO we should be able to map old type to the new one but doing that change has some 577 // recycling side effects. 578 assertEquals(9, mAnimator.animateChangeList.size()); 579 assertEquals(0, mAnimator.animatePersistenceList.size()); 580 assertEquals(1, mAnimator.animateAppearanceList.size()); 581 assertEquals(0, mAnimator.animateDisappearanceList.size()); 582 assertNotSame(vh, newVh); 583 for (AnimateChange change : mAnimator.animateChangeList) { 584 if (change.viewHolder == vh) { 585 assertSame(change.newHolder, newVh); 586 assertSame(change.viewHolder, vh); 587 } else { 588 assertSame(change.newHolder, change.viewHolder); 589 } 590 } 591 } 592 593 LoggingV2Animator mAnimator = new LoggingV2Animator(); 594 595 class LoggingV2Animator extends RecyclerView.ItemAnimator { 596 597 CanReUseCallback canReUseCallback = new CanReUseCallback() { 598 @Override 599 public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) { 600 return true; 601 } 602 }; 603 Map<RecyclerView.ViewHolder, LoggingInfo> preLayoutInfoMap = new HashMap<>(); 604 Map<RecyclerView.ViewHolder, LoggingInfo> postLayoutInfoMap = new HashMap<>(); 605 606 List<AnimateAppearance> animateAppearanceList = new ArrayList<>(); 607 List<AnimateDisappearance> animateDisappearanceList = new ArrayList<>(); 608 List<AnimatePersistence> animatePersistenceList = new ArrayList<>(); 609 List<AnimateChange> animateChangeList = new ArrayList<>(); 610 611 @Override 612 public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder, 613 List<Object> payloads) { 614 return canReUseCallback.canReUse(viewHolder, payloads); 615 } 616 617 @NonNull 618 @Override 619 public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, 620 @NonNull RecyclerView.ViewHolder viewHolder, 621 @AdapterChanges int changeFlags, @NonNull List<Object> payloads) { 622 LoggingInfo loggingInfo = new LoggingInfo(viewHolder, changeFlags, payloads); 623 preLayoutInfoMap.put(viewHolder, loggingInfo); 624 return loggingInfo; 625 } 626 627 @NonNull 628 @Override 629 public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state, 630 @NonNull RecyclerView.ViewHolder viewHolder) { 631 LoggingInfo loggingInfo = new LoggingInfo(viewHolder, 0, null); 632 postLayoutInfoMap.put(viewHolder, loggingInfo); 633 return loggingInfo; 634 } 635 636 @Override 637 public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, 638 @NonNull ItemHolderInfo preLayoutInfo, 639 @Nullable ItemHolderInfo postLayoutInfo) { 640 animateDisappearanceList.add(new AnimateDisappearance(viewHolder, 641 (LoggingInfo) preLayoutInfo, (LoggingInfo) postLayoutInfo)); 642 assertSame(preLayoutInfoMap.get(viewHolder), preLayoutInfo); 643 assertSame(postLayoutInfoMap.get(viewHolder), postLayoutInfo); 644 dispatchAnimationFinished(viewHolder); 645 646 return false; 647 } 648 649 @Override 650 public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, 651 ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 652 animateAppearanceList.add( 653 new AnimateAppearance(viewHolder, (LoggingInfo) preInfo, (LoggingInfo) postInfo)); 654 assertSame(preLayoutInfoMap.get(viewHolder), preInfo); 655 assertSame(postLayoutInfoMap.get(viewHolder), postInfo); 656 dispatchAnimationFinished(viewHolder); 657 return false; 658 } 659 660 @Override 661 public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, 662 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 663 animatePersistenceList.add(new AnimatePersistence(viewHolder, (LoggingInfo) preInfo, 664 (LoggingInfo) postInfo)); 665 dispatchAnimationFinished(viewHolder); 666 assertSame(preLayoutInfoMap.get(viewHolder), preInfo); 667 assertSame(postLayoutInfoMap.get(viewHolder), postInfo); 668 return false; 669 } 670 671 @Override 672 public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, 673 @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, 674 @NonNull ItemHolderInfo postInfo) { 675 animateChangeList.add(new AnimateChange(oldHolder, newHolder, (LoggingInfo) preInfo, 676 (LoggingInfo) postInfo)); 677 if (oldHolder != null) { 678 dispatchAnimationFinished(oldHolder); 679 assertSame(preLayoutInfoMap.get(oldHolder), preInfo); 680 } 681 if (newHolder != null && oldHolder != newHolder) { 682 dispatchAnimationFinished(newHolder); 683 assertSame(postLayoutInfoMap.get(newHolder), postInfo); 684 } 685 686 return false; 687 } 688 689 @Override 690 public void runPendingAnimations() { 691 692 } 693 694 @Override 695 public void endAnimation(RecyclerView.ViewHolder item) { 696 } 697 698 @Override 699 public void endAnimations() { 700 701 } 702 703 @Override 704 public boolean isRunning() { 705 return false; 706 } 707 } 708 709 interface CanReUseCallback { 710 711 boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads); 712 } 713 } 714