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 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotNull;
     21 import static org.junit.Assert.assertNotSame;
     22 import static org.junit.Assert.assertTrue;
     23 
     24 import android.content.Context;
     25 import android.graphics.Canvas;
     26 import android.util.AttributeSet;
     27 import android.util.Log;
     28 import android.view.View;
     29 
     30 import java.util.ArrayList;
     31 import java.util.HashSet;
     32 import java.util.List;
     33 import java.util.Set;
     34 import java.util.concurrent.CountDownLatch;
     35 import java.util.concurrent.TimeUnit;
     36 
     37 /**
     38  * Base class for animation related tests.
     39  */
     40 public class BaseRecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
     41 
     42     protected static final boolean DEBUG = false;
     43 
     44     protected static final String TAG = "RecyclerViewAnimationsTest";
     45 
     46     AnimationLayoutManager mLayoutManager;
     47 
     48     TestAdapter mTestAdapter;
     49 
     50     public BaseRecyclerViewAnimationsTest() {
     51         super(DEBUG);
     52     }
     53 
     54     RecyclerView setupBasic(int itemCount) throws Throwable {
     55         return setupBasic(itemCount, 0, itemCount);
     56     }
     57 
     58     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
     59             throws Throwable {
     60         return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
     61     }
     62 
     63     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
     64             TestAdapter testAdapter)
     65             throws Throwable {
     66         final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
     67         recyclerView.setHasFixedSize(true);
     68         if (testAdapter == null) {
     69             mTestAdapter = new TestAdapter(itemCount);
     70         } else {
     71             mTestAdapter = testAdapter;
     72         }
     73         recyclerView.setAdapter(mTestAdapter);
     74         recyclerView.setItemAnimator(createItemAnimator());
     75         mLayoutManager = new AnimationLayoutManager();
     76         recyclerView.setLayoutManager(mLayoutManager);
     77         mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
     78         mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
     79 
     80         mLayoutManager.expectLayouts(1);
     81         recyclerView.expectDraw(1);
     82         setRecyclerView(recyclerView);
     83         mLayoutManager.waitForLayout(2);
     84         recyclerView.waitForDraw(1);
     85         mLayoutManager.mOnLayoutCallbacks.reset();
     86         getInstrumentation().waitForIdleSync();
     87         checkForMainThreadException();
     88         assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
     89         assertEquals("all expected children should be laid out", firstLayoutItemCount,
     90                 mLayoutManager.getChildCount());
     91         return recyclerView;
     92     }
     93 
     94     protected RecyclerView.ItemAnimator createItemAnimator() {
     95         return new DefaultItemAnimator();
     96     }
     97 
     98     public TestRecyclerView getTestRecyclerView() {
     99         return (TestRecyclerView) mRecyclerView;
    100     }
    101 
    102     class AnimationLayoutManager extends TestLayoutManager {
    103 
    104         protected int mTotalLayoutCount = 0;
    105         private String log;
    106 
    107         OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
    108         };
    109 
    110 
    111 
    112         @Override
    113         public boolean supportsPredictiveItemAnimations() {
    114             return true;
    115         }
    116 
    117         public String getLog() {
    118             return log;
    119         }
    120 
    121         private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
    122             StringBuilder builder = new StringBuilder();
    123             builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
    124             builder.append("\nViewHolders:\n");
    125             for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
    126                 builder.append(vh).append("\n");
    127             }
    128             builder.append("scrap:\n");
    129             for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
    130                 builder.append(vh).append("\n");
    131             }
    132 
    133             if (state.isPreLayout() && !done) {
    134                 log = "\n" + builder.toString();
    135             } else {
    136                 log += "\n" + builder.toString();
    137             }
    138             return log;
    139         }
    140 
    141         @Override
    142         public void expectLayouts(int count) {
    143             super.expectLayouts(count);
    144             mOnLayoutCallbacks.mLayoutCount = 0;
    145         }
    146 
    147         public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
    148             mOnLayoutCallbacks = onLayoutCallbacks;
    149         }
    150 
    151         @Override
    152         public final void onLayoutChildren(RecyclerView.Recycler recycler,
    153                 RecyclerView.State state) {
    154             try {
    155                 mTotalLayoutCount++;
    156                 prepareLog(recycler, state, false);
    157                 if (state.isPreLayout()) {
    158                     validateOldPositions(recycler, state);
    159                 } else {
    160                     validateClearedOldPositions(recycler, state);
    161                 }
    162                 mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
    163                 prepareLog(recycler, state, true);
    164             } finally {
    165                 layoutLatch.countDown();
    166             }
    167         }
    168 
    169         private void validateClearedOldPositions(RecyclerView.Recycler recycler,
    170                 RecyclerView.State state) {
    171             if (getTestRecyclerView() == null) {
    172                 return;
    173             }
    174             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
    175                 assertEquals("there should NOT be an old position in post layout",
    176                         RecyclerView.NO_POSITION, viewHolder.mOldPosition);
    177                 assertEquals("there should NOT be a pre layout position in post layout",
    178                         RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
    179             }
    180         }
    181 
    182         private void validateOldPositions(RecyclerView.Recycler recycler,
    183                 RecyclerView.State state) {
    184             if (getTestRecyclerView() == null) {
    185                 return;
    186             }
    187             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
    188                 if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
    189                     assertTrue("there should be an old position in pre-layout",
    190                             viewHolder.mOldPosition != RecyclerView.NO_POSITION);
    191                 }
    192             }
    193         }
    194 
    195         public int getTotalLayoutCount() {
    196             return mTotalLayoutCount;
    197         }
    198 
    199         @Override
    200         public boolean canScrollVertically() {
    201             return true;
    202         }
    203 
    204         @Override
    205         public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    206                 RecyclerView.State state) {
    207             mOnLayoutCallbacks.onScroll(dy, recycler, state);
    208             return super.scrollVerticallyBy(dy, recycler, state);
    209         }
    210 
    211         public void onPostDispatchLayout() {
    212             mOnLayoutCallbacks.postDispatchLayout();
    213         }
    214     }
    215 
    216     abstract class OnLayoutCallbacks {
    217 
    218         int mLayoutMin = Integer.MIN_VALUE;
    219 
    220         int mLayoutItemCount = Integer.MAX_VALUE;
    221 
    222         int expectedPreLayoutItemCount = -1;
    223 
    224         int expectedPostLayoutItemCount = -1;
    225 
    226         int mDeletedViewCount;
    227 
    228         int mLayoutCount = 0;
    229 
    230         void setExpectedItemCounts(int preLayout, int postLayout) {
    231             expectedPreLayoutItemCount = preLayout;
    232             expectedPostLayoutItemCount = postLayout;
    233         }
    234 
    235         void reset() {
    236             mLayoutMin = Integer.MIN_VALUE;
    237             mLayoutItemCount = Integer.MAX_VALUE;
    238             expectedPreLayoutItemCount = -1;
    239             expectedPostLayoutItemCount = -1;
    240             mLayoutCount = 0;
    241         }
    242 
    243         void beforePreLayout(RecyclerView.Recycler recycler,
    244                 AnimationLayoutManager lm, RecyclerView.State state) {
    245             mDeletedViewCount = 0;
    246             for (int i = 0; i < lm.getChildCount(); i++) {
    247                 View v = lm.getChildAt(i);
    248                 if (lm.getLp(v).isItemRemoved()) {
    249                     mDeletedViewCount++;
    250                 }
    251             }
    252         }
    253 
    254         void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
    255                 RecyclerView.State state) {
    256             if (DEBUG) {
    257                 Log.d(TAG, "item count " + state.getItemCount());
    258             }
    259             lm.detachAndScrapAttachedViews(recycler);
    260             final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
    261             final int count = mLayoutItemCount
    262                     == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
    263             lm.layoutRange(recycler, start, start + count);
    264             assertEquals("correct # of children should be laid out",
    265                     count, lm.getChildCount());
    266             lm.assertVisibleItemPositions();
    267         }
    268 
    269         private void assertNoPreLayoutPosition(RecyclerView.Recycler recycler) {
    270             for (RecyclerView.ViewHolder vh : recycler.mAttachedScrap) {
    271                 assertPreLayoutPosition(vh);
    272             }
    273         }
    274 
    275         private void assertNoPreLayoutPosition(RecyclerView.LayoutManager lm) {
    276             for (int i = 0; i < lm.getChildCount(); i ++) {
    277                 final RecyclerView.ViewHolder vh = mRecyclerView
    278                         .getChildViewHolder(lm.getChildAt(i));
    279                 assertPreLayoutPosition(vh);
    280             }
    281         }
    282 
    283         private void assertPreLayoutPosition(RecyclerView.ViewHolder vh) {
    284             assertEquals("in post layout, there should not be a view holder w/ a pre "
    285                     + "layout position", RecyclerView.NO_POSITION, vh.mPreLayoutPosition);
    286             assertEquals("in post layout, there should not be a view holder w/ an old "
    287                     + "layout position", RecyclerView.NO_POSITION, vh.mOldPosition);
    288         }
    289 
    290         void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
    291                 RecyclerView.State state) {
    292             if (state.isPreLayout()) {
    293                 if (expectedPreLayoutItemCount != -1) {
    294                     assertEquals("on pre layout, state should return abstracted adapter size",
    295                             expectedPreLayoutItemCount, state.getItemCount());
    296                 }
    297                 beforePreLayout(recycler, lm, state);
    298             } else {
    299                 if (expectedPostLayoutItemCount != -1) {
    300                     assertEquals("on post layout, state should return real adapter size",
    301                             expectedPostLayoutItemCount, state.getItemCount());
    302                 }
    303                 beforePostLayout(recycler, lm, state);
    304             }
    305             if (!state.isPreLayout()) {
    306                 assertNoPreLayoutPosition(recycler);
    307             }
    308             doLayout(recycler, lm, state);
    309             if (state.isPreLayout()) {
    310                 afterPreLayout(recycler, lm, state);
    311             } else {
    312                 afterPostLayout(recycler, lm, state);
    313                 assertNoPreLayoutPosition(lm);
    314             }
    315             mLayoutCount++;
    316         }
    317 
    318         void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    319                 RecyclerView.State state) {
    320         }
    321 
    322         void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    323                 RecyclerView.State state) {
    324         }
    325 
    326         void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    327                 RecyclerView.State state) {
    328         }
    329 
    330         void postDispatchLayout() {
    331         }
    332 
    333         public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    334 
    335         }
    336     }
    337 
    338     class TestRecyclerView extends RecyclerView {
    339 
    340         CountDownLatch drawLatch;
    341 
    342         public TestRecyclerView(Context context) {
    343             super(context);
    344         }
    345 
    346         public TestRecyclerView(Context context, AttributeSet attrs) {
    347             super(context, attrs);
    348         }
    349 
    350         public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    351             super(context, attrs, defStyle);
    352         }
    353 
    354         @Override
    355         void initAdapterManager() {
    356             super.initAdapterManager();
    357             mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
    358                 @Override
    359                 public void run() {
    360                     validatePostUpdateOp();
    361                 }
    362             };
    363         }
    364 
    365         @Override
    366         boolean isAccessibilityEnabled() {
    367             return true;
    368         }
    369 
    370         public void expectDraw(int count) {
    371             drawLatch = new CountDownLatch(count);
    372         }
    373 
    374         public void waitForDraw(long timeout) throws Throwable {
    375             drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
    376             assertEquals("all expected draws should happen at the expected time frame",
    377                     0, drawLatch.getCount());
    378         }
    379 
    380         List<ViewHolder> collectViewHolders() {
    381             List<ViewHolder> holders = new ArrayList<ViewHolder>();
    382             final int childCount = getChildCount();
    383             for (int i = 0; i < childCount; i++) {
    384                 ViewHolder holder = getChildViewHolderInt(getChildAt(i));
    385                 if (holder != null) {
    386                     holders.add(holder);
    387                 }
    388             }
    389             return holders;
    390         }
    391 
    392 
    393         private void validateViewHolderPositions() {
    394             final Set<Integer> existingOffsets = new HashSet<Integer>();
    395             int childCount = getChildCount();
    396             StringBuilder log = new StringBuilder();
    397             for (int i = 0; i < childCount; i++) {
    398                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
    399                 TestViewHolder tvh = (TestViewHolder) vh;
    400                 log.append(tvh.mBoundItem).append(vh)
    401                         .append(" hidden:")
    402                         .append(mChildHelper.mHiddenViews.contains(vh.itemView))
    403                         .append("\n");
    404             }
    405             for (int i = 0; i < childCount; i++) {
    406                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
    407                 if (vh.isInvalid()) {
    408                     continue;
    409                 }
    410                 if (vh.getLayoutPosition() < 0) {
    411                     LayoutManager lm = getLayoutManager();
    412                     for (int j = 0; j < lm.getChildCount(); j ++) {
    413                         assertNotSame("removed view holder should not be in LM's child list",
    414                                 vh.itemView, lm.getChildAt(j));
    415                     }
    416                 } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
    417                     if (!existingOffsets.add(vh.getLayoutPosition())) {
    418                         throw new IllegalStateException("view holder position conflict for "
    419                                 + "existing views " + vh + "\n" + log);
    420                     }
    421                 }
    422             }
    423         }
    424 
    425         void validatePostUpdateOp() {
    426             try {
    427                 validateViewHolderPositions();
    428                 if (super.mState.isPreLayout()) {
    429                     validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
    430                 }
    431                 validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
    432             } catch (Throwable t) {
    433                 postExceptionToInstrumentation(t);
    434             }
    435         }
    436 
    437 
    438 
    439         private void validateAdapterPosition(AnimationLayoutManager lm) {
    440             for (ViewHolder vh : collectViewHolders()) {
    441                 if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
    442                     assertEquals("adapter position calculations should match view holder "
    443                                     + "pre layout:" + mState.isPreLayout()
    444                                     + " positions\n" + vh + "\n" + lm.getLog(),
    445                             mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
    446                 }
    447             }
    448         }
    449 
    450         // ensures pre layout positions are continuous block. This is not necessarily a case
    451         // but valid in test RV
    452         private void validatePreLayoutSequence(AnimationLayoutManager lm) {
    453             Set<Integer> preLayoutPositions = new HashSet<Integer>();
    454             for (ViewHolder vh : collectViewHolders()) {
    455                 assertTrue("pre layout positions should be distinct " + lm.getLog(),
    456                         preLayoutPositions.add(vh.mPreLayoutPosition));
    457             }
    458             int minPos = Integer.MAX_VALUE;
    459             for (Integer pos : preLayoutPositions) {
    460                 if (pos < minPos) {
    461                     minPos = pos;
    462                 }
    463             }
    464             for (int i = 1; i < preLayoutPositions.size(); i++) {
    465                 assertNotNull("next position should exist " + lm.getLog(),
    466                         preLayoutPositions.contains(minPos + i));
    467             }
    468         }
    469 
    470         @Override
    471         protected void dispatchDraw(Canvas canvas) {
    472             super.dispatchDraw(canvas);
    473             if (drawLatch != null) {
    474                 drawLatch.countDown();
    475             }
    476         }
    477 
    478         @Override
    479         void dispatchLayout() {
    480             try {
    481                 super.dispatchLayout();
    482                 if (getLayoutManager() instanceof AnimationLayoutManager) {
    483                     ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
    484                 }
    485             } catch (Throwable t) {
    486                 postExceptionToInstrumentation(t);
    487             }
    488 
    489         }
    490 
    491 
    492     }
    493 
    494     abstract class AdapterOps {
    495 
    496         final public void run(TestAdapter adapter) throws Throwable {
    497             onRun(adapter);
    498         }
    499 
    500         abstract void onRun(TestAdapter testAdapter) throws Throwable;
    501     }
    502 
    503     static class CollectPositionResult {
    504 
    505         // true if found in scrap
    506         public RecyclerView.ViewHolder scrapResult;
    507 
    508         public RecyclerView.ViewHolder adapterResult;
    509 
    510         static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
    511             CollectPositionResult cpr = new CollectPositionResult();
    512             cpr.scrapResult = viewHolder;
    513             return cpr;
    514         }
    515 
    516         static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
    517             CollectPositionResult cpr = new CollectPositionResult();
    518             cpr.adapterResult = viewHolder;
    519             return cpr;
    520         }
    521 
    522         @Override
    523         public String toString() {
    524             return "CollectPositionResult{" +
    525                     "scrapResult=" + scrapResult +
    526                     ", adapterResult=" + adapterResult +
    527                     '}';
    528         }
    529     }
    530 
    531     static class PositionConstraint {
    532 
    533         public static enum Type {
    534             scrap,
    535             adapter,
    536             adapterScrap /*first pass adapter, second pass scrap*/
    537         }
    538 
    539         Type mType;
    540 
    541         int mOldPos; // if VH
    542 
    543         int mPreLayoutPos;
    544 
    545         int mPostLayoutPos;
    546 
    547         int mValidateCount = 0;
    548 
    549         public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
    550             PositionConstraint constraint = new PositionConstraint();
    551             constraint.mType = Type.scrap;
    552             constraint.mOldPos = oldPos;
    553             constraint.mPreLayoutPos = preLayoutPos;
    554             constraint.mPostLayoutPos = postLayoutPos;
    555             return constraint;
    556         }
    557 
    558         public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
    559             PositionConstraint constraint = new PositionConstraint();
    560             constraint.mType = Type.adapterScrap;
    561             constraint.mOldPos = RecyclerView.NO_POSITION;
    562             constraint.mPreLayoutPos = preLayoutPos;
    563             constraint.mPostLayoutPos = position;// adapter pos does not change
    564             return constraint;
    565         }
    566 
    567         public static PositionConstraint adapter(int position) {
    568             PositionConstraint constraint = new PositionConstraint();
    569             constraint.mType = Type.adapter;
    570             constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
    571             constraint.mOldPos = RecyclerView.NO_POSITION;
    572             constraint.mPostLayoutPos = position;// adapter pos does not change
    573             return constraint;
    574         }
    575 
    576         public void assertValidate() {
    577             int expectedValidate = 0;
    578             if (mPreLayoutPos >= 0) {
    579                 expectedValidate ++;
    580             }
    581             if (mPostLayoutPos >= 0) {
    582                 expectedValidate ++;
    583             }
    584             assertEquals("should run all validates", expectedValidate, mValidateCount);
    585         }
    586 
    587         @Override
    588         public String toString() {
    589             return "Cons{" +
    590                     "t=" + mType.name() +
    591                     ", old=" + mOldPos +
    592                     ", pre=" + mPreLayoutPos +
    593                     ", post=" + mPostLayoutPos +
    594                     '}';
    595         }
    596 
    597         public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
    598             mValidateCount ++;
    599             assertNotNull(this + ": result should not be null\n" + log, result);
    600             RecyclerView.ViewHolder viewHolder;
    601             if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
    602                 assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
    603                 viewHolder = result.scrapResult;
    604             } else {
    605                 assertNotNull(this + ": result should come from adapter\n"  + log,
    606                         result.adapterResult);
    607                 assertEquals(this + ": old position should be none when it came from adapter\n" + log,
    608                         RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
    609                 viewHolder = result.adapterResult;
    610             }
    611             if (state.isPreLayout()) {
    612                 assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
    613                         viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
    614                                 viewHolder.mPreLayoutPosition);
    615                 assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
    616                         viewHolder.getLayoutPosition());
    617                 if (mType == Type.scrap) {
    618                     assertEquals(this + ": old position should match\n" + log, mOldPos,
    619                             result.scrapResult.getOldPosition());
    620                 }
    621             } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
    622                     .isRemoved()) {
    623                 assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
    624                         + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
    625             }
    626         }
    627     }
    628 
    629     static class LoggingInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
    630         final RecyclerView.ViewHolder viewHolder;
    631         @RecyclerView.ItemAnimator.AdapterChanges
    632         final int changeFlags;
    633         final List<Object> payloads;
    634 
    635         LoggingInfo(RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
    636             this.viewHolder = viewHolder;
    637             this.changeFlags = changeFlags;
    638             if (payloads != null) {
    639                 this.payloads = new ArrayList<>();
    640                 this.payloads.addAll(payloads);
    641             } else {
    642                 this.payloads = null;
    643             }
    644             setFrom(viewHolder);
    645         }
    646 
    647         @Override
    648         public String toString() {
    649             return "LoggingInfo{" +
    650                     "changeFlags=" + changeFlags +
    651                     ", payloads=" + payloads +
    652                     '}';
    653         }
    654     }
    655 
    656     static class AnimateChange extends AnimateLogBase {
    657 
    658         final RecyclerView.ViewHolder newHolder;
    659 
    660         public AnimateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
    661                 LoggingInfo pre, LoggingInfo post) {
    662             super(oldHolder, pre, post);
    663             this.newHolder = newHolder;
    664         }
    665     }
    666 
    667     static class AnimatePersistence extends AnimateLogBase {
    668 
    669         public AnimatePersistence(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    670                 LoggingInfo post) {
    671             super(viewHolder, pre, post);
    672         }
    673     }
    674 
    675     static class AnimateAppearance extends AnimateLogBase {
    676         public AnimateAppearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    677                 LoggingInfo post) {
    678             super(viewHolder, pre, post);
    679         }
    680     }
    681 
    682     static class AnimateDisappearance extends AnimateLogBase {
    683         public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    684                 LoggingInfo post) {
    685             super(viewHolder, pre, post);
    686         }
    687     }
    688     static class AnimateLogBase {
    689 
    690         public final RecyclerView.ViewHolder viewHolder;
    691         public final LoggingInfo preInfo;
    692         public final LoggingInfo postInfo;
    693 
    694         public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    695                 LoggingInfo postInfo) {
    696             this.viewHolder = viewHolder;
    697             this.preInfo = pre;
    698             this.postInfo = postInfo;
    699         }
    700 
    701         public String log() {
    702             return getClass().getSimpleName() + "[" +  log(preInfo) + " - " + log(postInfo) + "]";
    703         }
    704 
    705         public String log(LoggingInfo info) {
    706             return info == null ? "null" : info.toString();
    707         }
    708 
    709         @Override
    710         public boolean equals(Object o) {
    711             if (this == o) {
    712                 return true;
    713             }
    714             if (o == null || getClass() != o.getClass()) {
    715                 return false;
    716             }
    717 
    718             AnimateLogBase that = (AnimateLogBase) o;
    719 
    720             if (viewHolder != null ? !viewHolder.equals(that.viewHolder)
    721                     : that.viewHolder != null) {
    722                 return false;
    723             }
    724             if (preInfo != null ? !preInfo.equals(that.preInfo) : that.preInfo != null) {
    725                 return false;
    726             }
    727             return !(postInfo != null ? !postInfo.equals(that.postInfo) : that.postInfo != null);
    728 
    729         }
    730 
    731         @Override
    732         public int hashCode() {
    733             int result = viewHolder != null ? viewHolder.hashCode() : 0;
    734             result = 31 * result + (preInfo != null ? preInfo.hashCode() : 0);
    735             result = 31 * result + (postInfo != null ? postInfo.hashCode() : 0);
    736             return result;
    737         }
    738     }
    739 }
    740