Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 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 androidx.recyclerview.widget;
     17 
     18 import static androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_CHANGED;
     19 import static androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_MOVED;
     20 import static androidx.recyclerview.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.test.filters.MediumTest;
     33 import android.support.test.runner.AndroidJUnit4;
     34 
     35 import androidx.annotation.NonNull;
     36 import androidx.annotation.Nullable;
     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(@NonNull 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     private 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         mActivityRule.runOnUiThread(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     private 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         mActivityRule.runOnUiThread(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